SQLite:Testing
SQLite의 Testing 관련 내용.
SQLite는 어떻게 테스트되는가
SQLite는 철저한 자동화 테스트 체계를 통해 높은 신뢰성과 견고성을 유지하며, 코드보다 590배 많은 테스트 코드가 존재함
- 네 가지 독립 테스트 하니스(TCL, TH3, SQL Logic Test, dbsqlfuzz) 가 핵심 라이브러리를 검증하며, 수억 건의 테스트를 수행
- 이상 상황 테스트(OOM, I/O 오류, 크래시 시뮬레이션) 와 퍼즈 테스트(fuzz testing) 를 통해 비정상 입력과 시스템 장애에도 안정적으로 동작함을 확인
- 100% 분기 및 MC/DC 커버리지, 리소스 누수 검출, Valgrind·정적 분석·체크리스트 등 다층적 검증 절차를 유지
- 이러한 체계적 테스트 덕분에 SQLite는 상용 DB 수준의 신뢰성과 품질을 확보한 오픈소스 데이터베이스로 평가됨
개요
- SQLite의 신뢰성과 견고성은 세밀한 테스트 과정에서 비롯됨
- 버전 3.42.0 기준, SQLite는 약 155.8 KSLOC의 C 코드와 92053.1 KSLOC의 테스트 코드로 구성
- OOM, I/O 오류, 크래시, 퍼즈, 경계값, 회귀, 비정상 DB 파일, 최적화 비활성화 테스트 등 다수 항목 포함
테스트 하니스
- TCL Tests
- SQLite 개발 중 주로 사용되는 공개 테스트 세트
- 27.2 KSLOC의 C 코드와 1390개 스크립트 파일(23.2MB)로 구성
- 약 5만여 개 테스트 케이스, 매개변수화로 전체 실행 시 수백만 건 수행
- 상용 C 기반 테스트 세트로 100% 분기 및 MC/DC 커버리지 달성
- 임베디드 환경에서도 동작하며, 1055.4 KSLOC·약 5만여 케이스 포함
- 전체 커버리지 테스트 시 약 2.4백만 건, 릴리스 전 2.48억 건 soak 테스트 수행
- SQLite와 PostgreSQL, MySQL, SQL Server, Oracle 10g 결과를 비교
- 7.2백만 쿼리, 1.12GB 데이터로 구성
- SQL과 데이터베이스 파일을 동시에 변형하는 libFuzzer 기반 퍼저
- 하루 약 10억 번의 변이 테스트 수행, 악의적 입력에 대한 견고성 검증
- speedtest1.c, mptester.c, threadtest3.c, fuzzershell.c, jfuzz 등
- 모든 테스트는 다중 플랫폼·컴파일 설정에서 통과해야 릴리스 가능
이상 상황 테스트
- OOM 테스트
- malloc() 실패를 시뮬레이션하여 메모리 부족 시 정상 복구 여부 검증
- 실패 시점 카운터를 증가시키며 반복 수행
- 가상 파일 시스템(VFS)을 이용해 디스크 오류를 시뮬레이션
- 오류 후 PRAGMA integrity_check로 데이터 손상 여부 확인
- 전원 차단·OS 크래시 상황을 시뮬레이션
- TCL 하니스는 자식 프로세스 기반, TH3는 메모리 기반 VFS 사용
- 트랜잭션의 완전 롤백 또는 완전 완료 여부 검증
- 크래시 후 OOM 또는 I/O 오류가 연속 발생하는 상황까지 검증
퍼즈 테스트
- SQL Fuzz
- 문법적으로 유효하지만 비정상적인 SQL을 생성해 SQLite 반응 검증
- 2014년 도입된 프로파일 기반 퍼저로, 새로운 제어 경로를 탐색
- SQLite의 assert 실패·크래시·잘못된 결과를 다수 발견
- 2016년부터 Google 인프라에서 자동 퍼징 수행
- 신규 커밋에서 간헐적 문제를 탐지
- 2018년 이후 내부 퍼저로 도입, SQL과 DB 파일 동시 변이
- 하루 5억 건 이상 테스트, 외부 퍼저의 버그 리포트 거의 소멸
- 2024년부터 jfuzz가 JSONB 입력 검증 추가
- 외부 연구자(예: Manuel Rigger)가 잘못된 결과 계산 사례 다수 발견
- fuzzcheck 유틸리티가 과거 퍼즈 케이스 중 ‘흥미로운’ 수천 건을 재검증
- MC/DC는 방어 코드 최소화, 퍼즈는 방어 코드 필요
- SQLite는 두 접근을 병행해 정상·악의적 입력 모두에 견고한 코드 유지
회귀 테스트
- 보고된 버그는 수정 후 반드시 새 테스트 케이스로 추가
- 과거 버그의 재발 방지 목적
자동 리소스 누수 검출
- TCL·TH3 하니스가 메모리·파일·스레드·뮤텍스 누수 자동 감시
- OOM·I/O 오류 후에도 메모리 누수가 없어야 함
테스트 커버리지
- SQLite 코어는 TH3 기준 100% 분기 커버리지 달성
- FTS3, RTree 등 확장은 제외
- 분기 커버리지는 문장 커버리지보다 엄격하며, 모든 조건 분기를 양방향으로 검증
- ALWAYS(), NEVER() 매크로로 방어 조건을 명시
- 세 가지 정의 형태로 테스트를 반복해 일관성 검증
- testcase() 매크로로 조건의 양·음 결과 모두 검증
- 1184개 testcase() 사용
- testcase() 매크로를 통해 모든 조건의 독립적 영향 검증
- -fprofile-arcs -ftest-coverage 옵션으로 커버리지 측정
- 결과 비교를 통해 컴파일러 버그나 정의되지 않은 동작 탐지
- 분기 명령을 변경해 테스트가 이를 감지하는지 확인
- 최적화 분기(/OPTIMIZATION-IF-TRUE/)는 예외 처리
- 모든 분기 테스트 덕분에 코드 변경 시 부작용 최소화
- 유지 비용은 높지만, 광범위 배포되는 인프라 라이브러리로서 정당화됨
동적 분석
- Assert()
- 6754개 assert 문으로 전·후조건 및 루프 불변식 검증
- SQLITE_DEBUG 빌드에서만 활성화
- 메모리 오류·스택 오버플로·초기화되지 않은 메모리 접근 탐지
- 릴리스 전 veryquick 및 TH3 테스트를 Valgrind로 실행
- SQLITE_MEMDEBUG 빌드 시 메모리 오류 감시용 래퍼 삽입
- Valgrind보다 빠르게 반복 검증 가능
- sqlite3_mutex_held() 등으로 멀티스레드 동기화 검증
- 롤백 저널이 DB보다 먼저 기록되는지 확인, 트랜잭션 원자성 보장
- -ftrapv, -fsanitize=undefined, /RTC1 등으로 비정의 동작 탐지
- 32/64비트, 엔디언, 다양한 CPU 아키텍처에서 반복 수행
최적화 비활성화 테스트
- sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS)으로 최적화 끄기
- 최적화 유무에 관계없이 동일 결과를 산출해야 함
- 일부 성능 측정용 테스트는 예외
체크리스트
- 릴리스 전 약 200개 항목의 수동 체크리스트 검증
- 일부는 수초, 일부는 수시간 소요
- 문제 발견 시 즉시 항목 추가, 지속적 개선
정적 분석
- GCC·Clang·MSVC에서 경고 없이 컴파일
- Clang Static Analyzer에서도 유효 경고 없음
- 정적 분석은 실제 버그 탐지 효과가 제한적
요약
- SQLite는 오픈소스임에도 상용 수준의 품질과 낮은 결함률을 유지
- 철저한 테스트와 코드 설계가 핵심 요인
- 모든 릴리스는 위 절차를 거쳐 미션 크리티컬 환경에서도 신뢰 가능한 DB 엔진으로 제공됨