- SQLite duy trì độ tin cậy và độ bền vững rất cao nhờ hệ thống kiểm thử tự động cực kỳ chặt chẽ, với lượng mã kiểm thử nhiều gấp 590 lần mã nguồn
- Bốn test harness độc lập (TCL, TH3, SQL Logic Test, dbsqlfuzz) xác minh thư viện cốt lõi, thực hiện hàng trăm triệu lượt kiểm thử
- Thông qua kiểm thử tình huống bất thường (OOM, lỗi I/O, mô phỏng crash) và fuzz testing, SQLite được xác nhận vẫn hoạt động ổn định ngay cả với đầu vào bất thường và sự cố hệ thống
- SQLite duy trì quy trình xác minh nhiều lớp gồm 100% branch coverage và MC/DC, phát hiện rò rỉ tài nguyên, Valgrind, phân tích tĩnh và checklist
- Nhờ hệ thống kiểm thử có tổ chức này, SQLite được đánh giá là cơ sở dữ liệu mã nguồn mở đạt độ tin cậy và chất lượng ở mức cơ sở dữ liệu thương mại
1. Tổng quan
- Độ tin cậy và độ bền vững của SQLite bắt nguồn từ quy trình kiểm thử tỉ mỉ
- Tính đến phiên bản 3.42.0, SQLite gồm khoảng 155.8 KSLOC mã C và 92053.1 KSLOC mã kiểm thử
- Hệ thống kiểm thử bao gồm 4 harness độc lập, 100% branch coverage và hàng triệu test case
- Bao gồm nhiều hạng mục như OOM, lỗi I/O, crash, fuzz, giá trị biên, hồi quy, tệp DB bất thường, kiểm thử khi tắt tối ưu hóa, v.v.
2. Test harness
- TCL Tests
- Bộ kiểm thử công khai được sử dụng chủ yếu trong quá trình phát triển SQLite
- Gồm 27.2 KSLOC mã C và 1390 tệp script (23.2MB)
- Khoảng hơn 50 nghìn test case, khi chạy toàn bộ với tham số hóa sẽ thực hiện hàng triệu lượt kiểm thử
- TH3
- Bộ kiểm thử thương mại viết bằng C, đạt 100% branch coverage và MC/DC coverage
- Hoạt động được cả trong môi trường nhúng, gồm 1055.4 KSLOC và khoảng hơn 50 nghìn case
- Khi chạy kiểm thử coverage đầy đủ sẽ có khoảng 2.4 triệu lượt, và trước khi phát hành thực hiện 248 triệu lượt soak test
- SQL Logic Test (SLT)
- So sánh kết quả của SQLite với PostgreSQL, MySQL, SQL Server và Oracle 10g
- Gồm 7.2 triệu truy vấn và 1.12GB dữ liệu
- dbsqlfuzz
- Fuzzer dựa trên libFuzzer có thể đồng thời biến đổi SQL và tệp cơ sở dữ liệu
- Thực hiện khoảng 1 tỷ lượt kiểm thử đột biến mỗi ngày, xác minh độ bền vững trước đầu vào độc hại
- Công cụ bổ sung
- speedtest1.c, mptester.c, threadtest3.c, fuzzershell.c, jfuzz, v.v.
- Mọi bài kiểm thử đều phải vượt qua trên nhiều nền tảng và cấu hình biên dịch thì mới có thể phát hành
3. Kiểm thử tình huống bất thường
- Kiểm thử OOM
- Mô phỏng lỗi
malloc() để xác minh khả năng phục hồi bình thường khi thiếu bộ nhớ
- Lặp lại bài kiểm thử bằng cách tăng bộ đếm thời điểm thất bại
- Kiểm thử lỗi I/O
- Dùng virtual file system (VFS) để mô phỏng lỗi đĩa
- Sau lỗi, dùng
PRAGMA integrity_check để xác nhận có xảy ra hỏng dữ liệu hay không
- Kiểm thử crash
- Mô phỏng tình huống mất điện hoặc OS crash
- TCL harness dùng tiến trình con, còn TH3 dùng VFS dựa trên bộ nhớ
- Xác minh giao dịch либо được rollback hoàn toàn, либо được hoàn tất hoàn toàn
- Kiểm thử lỗi kết hợp
- Xác minh cả những tình huống mà sau crash lại tiếp tục xảy ra OOM hoặc lỗi I/O
4. Fuzz testing
- SQL Fuzz
- Tạo ra SQL hợp lệ về mặt cú pháp nhưng bất thường để kiểm tra phản ứng của SQLite
- American Fuzzy Lop (AFL)
- Fuzzer dựa trên profiling được đưa vào từ năm 2014 để khám phá các đường điều khiển mới
- Đã phát hiện nhiều trường hợp assert fail, crash và kết quả sai trong SQLite
- Google OSS Fuzz
- Từ năm 2016, tự động fuzzing trên hạ tầng của Google
- Phát hiện các vấn đề gián đoạn trên các commit mới
- dbsqlfuzz / jfuzz
- Được đưa vào làm fuzzer nội bộ từ sau năm 2018, biến đổi đồng thời SQL và tệp DB
- Kiểm thử hơn 500 triệu lượt mỗi ngày, khiến báo cáo lỗi từ fuzzer bên ngoài gần như biến mất
- Từ năm 2024, jfuzz bổ sung xác minh đầu vào JSONB
- Fuzzer bên thứ ba và fuzzcheck
- Các nhà nghiên cứu bên ngoài (ví dụ Manuel Rigger) đã phát hiện nhiều trường hợp tính toán kết quả sai
- Tiện ích fuzzcheck tái xác minh hàng nghìn case “thú vị” từ các ca fuzz trước đây
- Mối căng thẳng giữa MC/DC và fuzz testing
- MC/DC khuyến khích tối thiểu hóa mã phòng vệ, còn fuzz thì cần mã phòng vệ
- SQLite theo đuổi song song cả hai hướng để duy trì mã đủ bền vững với cả đầu vào bình thường lẫn độc hại
5. Kiểm thử hồi quy
- Mọi lỗi đã được báo cáo sau khi sửa xong đều обязательно được thêm thành test case mới
- Mục đích là ngăn lỗi cũ tái diễn
6. Tự động phát hiện rò rỉ tài nguyên
- TCL và TH3 harness tự động giám sát rò rỉ bộ nhớ, tệp, luồng và mutex
- Ngay cả sau OOM hay lỗi I/O cũng không được có rò rỉ bộ nhớ
7. Độ bao phủ kiểm thử
- Phần lõi của SQLite đạt 100% branch coverage theo TH3
- Không bao gồm các phần mở rộng như FTS3, RTree
- Statement vs Branch Coverage
- Branch coverage nghiêm ngặt hơn statement coverage, vì xác minh mọi nhánh điều kiện theo cả hai hướng
- Độ bao phủ của mã phòng vệ
- Dùng các macro ALWAYS(), NEVER() để biểu thị điều kiện phòng vệ
- Lặp lại kiểm thử với ba kiểu định nghĩa khác nhau để xác minh tính nhất quán
- Kiểm thử giá trị biên và vector boolean
- Dùng macro testcase() để xác minh cả kết quả dương và âm của điều kiện
- Có 1184 lần sử dụng testcase()
- Đạt MC/DC
- Thông qua macro testcase(), xác minh ảnh hưởng độc lập của mọi điều kiện
- Đo lường dựa trên gcov
- Đo coverage với tùy chọn
-fprofile-arcs -ftest-coverage
- So sánh kết quả để phát hiện lỗi compiler hoặc undefined behavior
- Mutation Testing
- Thay đổi lệnh rẽ nhánh để xem bài kiểm thử có phát hiện được hay không
- Nhánh tối ưu hóa (
/*OPTIMIZATION-IF-TRUE*/) được xử lý như ngoại lệ
- Kinh nghiệm với độ bao phủ hoàn toàn
- Nhờ kiểm thử mọi nhánh mà tác dụng phụ khi thay đổi mã được giảm thiểu
- Chi phí bảo trì cao, nhưng là điều hợp lý đối với một thư viện hạ tầng được triển khai rộng khắp
8. Phân tích động
- Assert()
- 6754 câu lệnh assert dùng để xác minh tiền điều kiện, hậu điều kiện và bất biến vòng lặp
- Chỉ được kích hoạt trong bản dựng
SQLITE_DEBUG
- Valgrind
- Phát hiện lỗi bộ nhớ, stack overflow và truy cập bộ nhớ chưa được khởi tạo
- Trước khi phát hành, chạy veryquick và TH3 test bằng Valgrind
- Memsys2
- Khi build với
SQLITE_MEMDEBUG, sẽ chèn wrapper để giám sát lỗi bộ nhớ
- Có thể kiểm tra lặp lại nhanh hơn Valgrind
- Mutex Asserts
- Xác minh đồng bộ hóa đa luồng bằng
sqlite3_mutex_held() và các hàm tương tự
- Journal Tests
- Kiểm tra rollback journal có được ghi trước DB hay không để bảo đảm tính nguyên tử của giao dịch
- Undefined Behavior Checks
- Dùng
-ftrapv, -fsanitize=undefined, /RTC1, v.v. để phát hiện undefined behavior
- Lặp lại trên môi trường 32/64-bit, endian khác nhau và nhiều kiến trúc CPU
9. Kiểm thử khi tắt tối ưu hóa
- Tắt tối ưu hóa bằng
sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS)
- Dù có hay không có tối ưu hóa cũng phải cho ra cùng một kết quả
- Một số bài kiểm thử dùng để đo hiệu năng là ngoại lệ
10. Checklist
- Trước khi phát hành, xác minh checklist thủ công khoảng 200 hạng mục
- Có mục chỉ mất vài giây, có mục mất vài giờ
- Khi phát hiện vấn đề sẽ lập tức thêm hạng mục mới để cải tiến liên tục
11. Phân tích tĩnh
- Biên dịch không có cảnh báo trên GCC, Clang và MSVC
- Cũng không có cảnh báo hợp lệ trong Clang Static Analyzer
- Hiệu quả phát hiện lỗi thực tế của phân tích tĩnh là khá hạn chế
12. Tóm tắt
- Dù là mã nguồn mở, SQLite vẫn duy trì chất lượng cấp thương mại và tỷ lệ lỗi thấp
- Kiểm thử nghiêm ngặt và thiết kế mã là các yếu tố cốt lõi
- Mọi bản phát hành đều đi qua các quy trình trên để cung cấp một DB engine đáng tin cậy ngay cả trong môi trường mission-critical
4 bình luận
Bài nên đọc cùng: Câu chuyện ít người biết về SQLite
Đây là bài viết tóm tắt một cuộc phỏng vấn với Richard Hipp, nhà phát triển của SQLite.
Các nhà phát triển SQLite cho biết họ đã biết đến Do-178 trong thời gian làm việc với Rockwell Collins và bắt đầu tuân theo quy trình này. Một trong số đó là đạt 100% MC/DC.
Do-178 thực sự là một bộ hướng dẫn rất hữu ích, nên tôi khuyến nghị bất kỳ nhà phát triển nào cũng nên đọc.
Cái này phải không? https://alm.parasoft.com/hubfs/…
Có vẻ như thứ bạn đã liên kết là tài liệu đào tạo về DO-178.
Bạn có thể xem tài liệu gốc tại liên kết này.
https://studylib.net/doc/27132454/rtca-do-178b
Ý kiến trên Hacker News
Hơn 10 năm trước, người bảo trì SQLite đã có một bài nói chuyện tại OSCON về thực hành kiểm thử
Điều đặc biệt gây ấn tượng là sức mạnh của checklist. Chính là công cụ mà phi công dùng trước mỗi chuyến bay
Ông ấy cũng nhắc đến trường hợp của Bác sĩ Không Biên giới (Doctors Without Borders), nơi đội ngũ y tế không biết tên nhau và còn khác ngôn ngữ, nên kết quả phẫu thuật kém
Giải pháp rất đơn giản — tạo một checklist trước phẫu thuật để mỗi người nói tên và vai trò của mình. Nghi thức nhỏ này đã nâng tỷ lệ sống sót nhờ cải thiện giao tiếp chứ không phải công nghệ
Tài liệu liên quan: ví dụ checklist của SQLite
Cần có thêm thảo luận về sự khác biệt giữa checklist tốt và checklist tệ. Nó có vẻ đơn giản như một công thức đẹp trong toán học, nhưng lại rất khó tìm ra
Đặc biệt, tôi đã đọc tài liệu FM22-100 của Lục quân Mỹ nhiều lần, và nó hiện đại cũng như truyền cảm hứng đến mức đáng ngạc nhiên
Xem tài liệu FM22-100
Liên kết sách
Ngoài test và CI, tôi còn làm theo một checklist phát hành viết bằng Markdown. Tôi thậm chí không lưu lại kết quả, chỉ thực hiện từng bước
Tôi không hiểu vì sao người khác lại không làm một việc đơn giản như vậy
Nếu có trang chính thức nào nói về trường hợp của MSF thì tôi rất muốn xem. Tôi tìm Google mà không thấy
Đây là bộ sưu tập các cuộc thảo luận trước đây về việc kiểm thử của SQLite
Danh sách các thread HN từ 2009~2024
Có vẻ bài này được đăng lại theo chu kỳ khoảng 1 năm
Tôi vừa ghen tị vừa kinh ngạc trước quá trình mài giũa phần mềm như SQLite đến mức hoàn hảo
Đây là một tác phẩm toát lên tinh thần thủ công bậc thầy
Theo thời gian, tiêu chuẩn chất lượng sẽ tăng lên, và với cùng một nỗ lực bạn sẽ nhận được phần thưởng lớn hơn
Không ai ghét một người để lại phần mình chạm vào sạch sẽ hơn một chút
SQLite thực sự là phần mềm tuyệt vời. Tôi cũng thích việc website chính thức của nó tập trung vào thông tin thay vì marketing
Tuy vậy, khá thú vị khi gần đây trên HN, các trang từ site chính thức cứ lần lượt được đưa lên
Nếu gom các liên kết kiểu này lại thì chắc sẽ khá vui
Điều thú vị là SQLite là phần mềm công khai nhưng lại dùng bộ kiểm thử không công khai
Giờ tôi mới nhận ra rằng một dự án mã nguồn mở vẫn có thể có kiểm thử đóng
Có cảm giác mô hình này có thể trở thành một mô hình kinh doanh mới tương tự open-core
Ví dụ: giấy phép railgunlabs/unicorn
Độ bao phủ nhánh 100% của SQLite ấn tượng chẳng kém gì chính dự án này
Đặc biệt là việc họ duy trì nó liên tục
Việc test không công khai thật đáng chú ý. Trong bối cảnh lập trình với LLM đang phát triển, chúng ta đang bước vào thời đại mà test quan trọng hơn phần triển khai
Gần đây tôi thấy trường hợp simonw gần như tự động chuyển đổi engine justHTML từ Python sang JS, và điều đó làm tôi nhớ đến chiến lược kiểm thử của SQLite
Gần đây tôi đã thảo luận với LLM về khả năng tương thích giữa SQLite và DuckDB, và đi đến kết luận rằng SQLite tốt hơn ở khía cạnh xử lý đồng thời
Tôi ngạc nhiên vì trong tài liệu kiểm thử của SQLite, phần nhắc đến kiểm thử hồi quy hiệu năng (performance regression) lại khá ít
Tính đúng đắn là quan trọng, nhưng suy giảm hiệu năng trên một số đường truy vấn nhất định cũng có thể là chí mạng
Tôi tò mò không biết có dự án nào xem mục tiêu này là sứ mệnh cốt lõi hay không
Nhìn vào độ ổn định của SQLite, tôi muốn biết thêm cách họ thực hiện kiểm thử bất thường (anomaly)
Nhưng bài viết hầu như không đề cập đến. Dù vậy, SQLite vẫn là một trong những phần mềm vững chắc nhất được dùng trên mọi loại thiết bị