TH3

TH3

TH3(SQLite Test Harness #3)는 SQLite를 테스트하는 데 사용되는 테스트 하네스 중 하나예요. TH3는 워크스테이션의 지원 인프라가 없는 임베디드 플랫폼에서도 실행할 수 있고, 배포된 그대로의 구성에서 컴파일된 목적 코드를 테스트하며, 100% 분기 테스트 커버리지와 100% MC/DC를 달성해요. 이 문서는 TH3의 개요, 작동 방식, 테스트 프로그램 생성, 테스트 커버리지, 변이 테스트, 그리고 라이선스를 설명해요.

출처: 문서

본문

1. 개요

SQLite Test Harness #3 (이하 "TH3")은 SQLite를 테스트하는 데 사용되는 테스트 하네스 중 하나예요. TH3는 다음 목표를 충족해요:

  • TH3는 워크스테이션의 지원 인프라가 없는 임베디드 플랫폼에서도 실행될 수 있어요.
  • TH3는 공개되고 문서화된 인터페이스만 사용해 배포된 그대로의 구성에서 SQLite를 테스트해요. 즉 TH3는 소스 코드가 아니라 컴파일된 목적 코드를 테스트해, 컴파일러 버그로 인한 문제가 도입되지 않았음을 검증해요. "네가 날아다니는 것을 테스트하고, 네가 테스트한 것을 날아라."
  • TH3는 SQLite의 메모리 부족 오류, 디스크 I/O 오류, 트랜잭션 커밋 중 정전에 대한 반응을 검사해요.
  • TH3는 다양한 런타임 구성(UTF8 vs UTF16, 다른 페이지 크기, 다양한 저널 모드 등)에서 SQLite를 실행해요.
  • TH3는 SQLite 코어에 대해 100% 분기 테스트 커버리지(그리고 100% MC/DC)를 달성해요. (FTS, RTREE 같은 확장의 테스트 커버리지는 100% 미만이에요.)

TH3는 원래 검증 테스트만을 위해 작성됐지만, 이후 개발 테스트와 디버깅에도 사용됐고 그 역할에서 매우 유용함이 입증됐어요. 전체 커버리지 테스트는 워크스테이션에서 5분 미만이 걸리므로, SQLite 코드 베이스의 일상적인 유지 관리 중 빠른 회귀 테스트로 사용돼요.

1.1. 역사

TH3는 SymbianOS에서 SQLite를 테스트하려는 노력에서 시작됐어요. TH3 이전에는 모든 SQLite 테스트가 TCL 스크립트 언어로 실행됐지만, TCL은 SymbianOS에서 (쉽게) 컴파일되지 않아 테스트가 어려웠어요. 이 문제를 해결하려는 첫 시도는 "TH1" (Test Harness #1) 스크립팅 언어였어요 — SymbianOS에서 컴파일되고 실행될 수 있는 더 이식 가능한 형태로 TCL 언어의 일부를 재구현한 것으로, SQLite 테스트를 실행하기에 충분했어요. TH1은 SQLite의 표준 테스트 도구로는 살아남지 못했지만, Fossil 버전 관리 시스템을 사용자화하는 데 사용되는 스크립팅 언어로 계속 사용됐어요. 또한 테스트를 구동하기 위해 연산자 접두 표기법을 사용하는 간단한 스크립팅 언어를 만들려는 시도인 "Test Harness #2"도 있었어요. TH3는 세 번째 시도였어요.

거의 같은 시기에 일부 항공전자 제조업체가 SQLite에 관심을 표현했고, 이는 SQLite 개발자들이 DO-178B의 엄격한 테스트 표준을 지원하도록 TH3를 설계하게 만들었어요.

TH3의 첫 코드는 2008-09-25에 놓여졌어요. 이후 10개월간의 집중적인 노력으로 TH3는 2009-07-25에 100% MC/DC를 달성했어요. TH3 코드는 계속 개선되고 확장되고 있어요.

2018-05-19 기준으로 TH3 소스 트리는 1709개의 개별 파일에 50만 줄이 훨씬 넘는 소스 코드로 구성돼 있어요.

2. 작동 방식

TH3는 테스트 프로그램 생성기예요. TH3의 출력은 C 코드로 구현되고 테스트 대상 SQLite 라이브러리에 링크되도록 의도된 프로그램이에요. 생성된 테스트 프로그램은 목표 플랫폼에서 컴파일 및 실행되어, 그 플랫폼에서 SQLite가 올바르게 작동하는지 검증해요.

TH3의 입력은 C나 SQL로 작성된 테스트 모듈과 SQLite를 초기화하는 방법을 결정하는 작은 구성 파일이에요. TH3 패키지는 (2018-05-19 기준) 1,444개의 테스트 모듈과 47개 이상의 구성을 포함해요. 새 모듈과 구성을 추가해 전문 애플리케이션에 맞게 TH3를 사용자화할 수 있어요. TH3가 실행될 때마다 사용 가능한 테스트 모듈과 구성 파일의 하위 집합을 읽어, 모든 지정된 구성에서 모든 지정된 테스트를 수행하는 사용자 지정 C 프로그램을 생성해요. SQLite의 완전한 테스트는 보통 TH3를 여러 번 실행해 SQLite 작동의 다양한 측면을 다루는 여러 테스트 프로그램을 생성한 다음, 모든 테스트 프로그램을 공통 SQLite 라이브러리에 링크하고 목표 플랫폼에서 별도로 실행하는 것을 포함해요.

TH3에는 임의의 한도가 없어요. 모든 테스트 모듈과 모든 구성 파일을 포함하는 단일 테스트 프로그램을 생성할 수도 있어요. 하지만 그런 테스트 프로그램은 임베디드 플랫폼에 배포하기에는 너무 클 수 있어요. (2018-05-19 기준, 전체 TH3 테스트는 85만 줄이 넘고 58MB의 C 코드야.) TH3는 테스트 모듈 라이브러리를 더 작고 더 쉽게 소화할 수 있는 조각으로 쪼갤 수 있는 능력을 제공해요.

각 개별 테스트 모듈은 수십, 수백, 또는 수천 개의 별도 테스트를 포함할 수 있어요. 테스트 모듈은 C로 작성하거나 SQL 스크립트로 작성하거나 둘의 혼합으로 작성할 수 있어요. 기존 테스트 모듈의 약 3분의 2는 순수 SQL로 작성되고, 나머지는 순수 C 또는 C와 SQL의 조합이에요.

각 테스트 모듈 파일에는 테스트가 유효한 상황을 설명하는 헤더가 포함돼요. 특정 구성에 대해, 그 구성과 호환되는 모듈만 실행돼요.

3. 테스트 프로그램 생성

TH3 프로그램 생성기는 "mkth3.tcl"이라는 TCL 스크립트예요. 테스트 프로그램을 생성하려면 이 스크립트를 실행하고 명령줄에 테스트 모듈과 구성을 포함하는 파일 이름을 제공하기만 하면 돼요. 테스트 모듈은 ".test" 접미사를 사용하는 파일이고, 구성은 ".cfg" 접미사를 사용하는 파일이에요. mkth3.tcl의 일반적인 호출은 대략 다음과 같아요:

tclsh mkth3.tcl *.test *.cfg >testprog1.c

mkth3.tcl 스크립트의 출력은 테스트를 실행하는 데 필요한 모든 것 — SQLite 라이브러리 자체를 제외한 모든 것 — 을 포함하는 C 프로그램이에요. 생성된 테스트 프로그램은 테스트 모듈이 사용하는 모든 지원 인터페이스의 구현을 포함하고 테스트를 구동하는 main() 루틴을 포함해요. 테스트 프로그램을 작동하는 실행 파일로 변환하려면 SQLite에 대해 컴파일하기만 하면 돼요:

cc -o testprog1 testprog1.c sqlite3.c

바로 위에 보인 컴파일 단계는 단지 대표적인 것이에요. 실제 설치에서는 보통 컴파일러 명령줄에 최적화 매개변수와 컴파일 타임 스위치를 지정하고 싶을 거예요.

임베디드 시스템 테스트의 경우, 위에 보인 mkth3.tcl 스크립트와 컴파일러 단계는 일반 워크스테이션에서 크로스 컴파일러를 사용해 수행된 다음, 결과 테스트 프로그램이 실행을 위해 장치로 전송돼요.

테스트 프로그램이 생성되면, 인자 없이 실행되어 테스트를 수행해요. 진행 정보와 오류 진단이 표준 출력에 나타나요. (표준 출력 채널이 없는 임베디드 장치의 경우 컴파일 타임 옵션으로 대체 출력 구성을 만들 수 있어요.) 프로그램은 오류가 없으면 0을, 문제가 감지되면 0이 아닌 값을 반환해요.

단일 TH3 테스트 프로그램 실행의 일반적인 출력은 다음과 같아요:

With SQLite 3.8.11 2015-05-15 04:13:15 56ef98a04765c34c1c2f3ed7a6f03a732f3b886e
-DSQLITE_COVERAGE_TEST
-DSQLITE_NO_SYNC
-DSQLITE_SYSTEM_MALLOC
-DSQLITE_THREADSAFE=1
Config-begin c1.
Begin c1.pager08
End c1.pager08
Begin c1.build33
End c1.build33
Begin c1.orderby01
End c1.orderby01
... 15014 lines of output omitted ....
Begin 64k.syscall01
End 64k.syscall01
Begin 64k.build01
End 64k.build01
Begin 64k.auth01
End 64k.auth01
Config-end 64k. TH3 memory used: 6373738
Config-begin wal1.
Begin wal1.wal37
End wal1.wal37
Config-end wal1. TH3 memory used: 100961
All 226 VDBE coverage points reached
th3: 0 errors out of 1442264 tests in 213.741 seconds. 64-bit little-endian
th3: SQLite 3.8.11 2015-05-15 04:13:15 56ef98a04765c34c1c2f3ed7a6f03a732f3b886e

출력은 테스트 대상 SQLite의 SQLITE_SOURCE_ID(sqlite3_sourceid()와 교차 확인)와 sqlite3_compileoption_get()이 보고한 사용된 컴파일 타임 옵션의 보고로 시작돼요. 출력은 테스트 결과 요약과 SQLITE_SOURCE_ID 반복으로 끝나요. 어떤 오류가 감지되면 추가 줄이 문제를 자세히 설명해요. 오류 보고 줄은 항상 단일 공백 문자로 시작해, 다음으로 큰 출력 파일에서 빠르게 추출할 수 있어요:

grep "^ "

기본 출력은 각 구성과 테스트 모듈 조합의 시작과 끝을 보여줘요. 위 예시에서 "c1"과 "64k"는 구성이고 "pager08", "build33", "orderby01" 등은 테스트 모듈이에요. 컴파일 타임과 런타임 옵션으로 출력량을 늘리거나 줄일 수 있어요. 각 테스트 모듈 내의 각 테스트 케이스를 보여줌으로써 출력을 늘릴 수 있어요. 출력은 단계적으로 줄일 수 있어요: 테스트 모듈 시작·중지 생략, 구성 시작·중지 생략, 마지막으로 모든 출력 생략.

3.1. 테스트 자동화 스크립트

TH3에는 워크스테이션에서 테스트 프로세스를 자동화하는 데 도움이 되는 추가 TCL 스크립트가 함께 온다. "th3make" 스크립트는 "mkth3.tcl"과 "gcc"를 자동으로 실행한 다음 결과 테스트 프로그램을 실행하고 결과를 검사해요. th3make의 인자에는 테스트에 포함될 모든 ".test" 테스트 모듈과 ".cfg" 구성이 포함돼요. th3make의 추가 옵션은 테스트 프로그램을 다른 컴파일러(GCC, Clang, MSVC)로 컴파일하게 하거나, 다른 출력 상세 수준을 사용하게 하거나, valgrind 아래에서 테스트 프로그램을 실행하게 하거나, gcov로 커버리지를 검사하게 하는 등이에요. th3make 스크립트는 인자로 "*.rc" 파일 이름도 받아들여요. 이 *.rc 파일은 단일 목적을 위해 흔히 함께 사용되는 다른 인자들의 모음일 뿐이에요. 예를 들어 "quick.rc" 파일은 빠른(3분) 전체 커버리지 테스트를 실행하는 th3make에 대한 8개 인자 집합을 포함해요. 이를 통해 운영자는 필요한 모든 명령줄 옵션을 입력하는 대신 "./th3make quick.rc"라고 입력할 수 있는 단축키를 얻어요. 다음은 40개가 넘는 사용 가능한 *.rc 파일 중 몇 가지예요:

  • alignmentN.rc — 이 파일들은 다양한 주목할 만한 다운스트림이 사용하는 컴파일러에 대한 -D 옵션을 포함해요.
  • cov.rc — 테스트 커버리지 측정 옵션
  • extensions.rc — FTS4, R-Trees, STAT4를 활성화하는 옵션
  • fast.rc — 커버리지에 필요하지 않은 테스트를 포함해 대부분의 테스트를 실행하되 soak 테스트만 건너뛰고, 배포 컴파일러 옵션(예: -O3) 사용
  • memdebug.rc — test.rc와 같지만 -DSQLITE_MEMDEBUG도 활성화
  • min.rc — 100% 커버리지에 필요한 최소 테스트 집합 실행
  • quick.rc — -Os와 -DSQLITE_DEBUG를 사용해 100% 커버리지 테스트에 필요한 모든 테스트 실행
  • test.rc — fast.rc와 같은 테스트를 실행하지만 컴파일러 최적화 없이, -DSQLITE_DEBUG와 -DSQLITE_ENABLE_EXPENSIVE_ASSERT 같은 옵션 활성화
  • test-ex.rc — 장시간 실행 soak 테스트

TH3 저장소에는 워크스테이션에서 TH3 테스트를 자동화하는 데 사용되는 또 다른 TCL 스크립트인 "multitest.tcl" 스크립트도 포함돼요. Multitest.tcl은 SQLite를 자동으로 컴파일한 다음, 다양한 정렬(alignment)로 ./th3make를 반복 실행하고 출력을 간결한 요약 화면에 담아요. 일반적인 multitest.tcl 실행은 다음과 같은 출력을 생성해요:

./multitest.tcl -q --jobs 3
start-time: 2018-05-19 03:17:12 UTC
file mkdir sqlite3bld
cd sqlite3bld
exec sh /ramdisk/sqlite/configure
file copy -force config.h ../config.h
exec make clean sqlite3.c
file rename sqlite3.c ../sqlite3.c
file rename sqlite3.h ../sqlite3.h
exec make clean sqlite3.c OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1
file rename sqlite3.c ../sqlite3udl.c
exec make clean sqlite3.c OPTS=-DSQLITE_SMALL_STACK=1
file rename sqlite3.c ../sqlite3ss.c
cd ..
*******************************************************************************
t01: cov.rc.................................................... Ok   (00:03:42)
t02: cov.rc ++STAT4 ++DESERIALIZE -D_HAVE_SQLITE_CONFIG_H...... Ok   (00:04:45)
t03: vfs-cov.rc................................................ Ok   (00:03:59)
t04: demo.rc................................................... Ok   (00:00:05)
t07: test.rc ../th3private/*.test.............................. Ok   (00:00:21)
t08: test.rc ../th3private/*.test ++STAT4...................... Ok   (00:01:41)
t05: quick.rc.................................................. Ok   (00:04:26)
t09: quick.rc ~TEST_REALLOC_STRESS -funsigned-char............. Ok   (00:05:39)
t10: quick.rc ~THREADSAFE=0 -DLONGDOUBLE_TYPE=double........... Ok   (00:03:24)
t06: quick.rc extensions.rc -D_HAVE_SQLITE_CONFIG_H............ Ok   (00:09:03)
t11: quick.rc sqlite3ss.c ~MAX_ATTACHED=125.................... Ok   (00:04:39)
t12: quick.rc ~BYTEORDER=0 ++RTREE............................. Ok   (00:07:28)
t13: quick.rc ~DISABLE_INTRINSIC ++RTREE....................... Ok   (00:07:31)
t16: quick.rc ~TRACE_SIZE_LIMIT=15 cov1/main16.test............ Ok   (00:00:22)
t14: quick.rc ~DIRECT_OVERFLOW_READ -fsigned-char.............. Ok   (00:04:35)
t15: quick.rc ~UNTESTABLE ~EXTRA_IFNULLROW..................... Ok   (00:01:44)
t17: quick.rc ~MAX_MMAP_SIZE=0................................. Ok   (00:04:46)
t18: quick.rc ++NULL_TRIM ++OFFSET_SQL_FUNC.................... Ok   (00:04:47)
t19: quick.rc ++BATCH_ATOMIC_WRITE ++DESERIALIZE............... Ok   (00:05:41)
t20: lean1.rc quick.rc......................................... Ok   (00:03:09)
t22: test.rc alignment2.rc sqlite3udl.c........................ Ok   (00:44:22)
t21: test.rc alignment1.rc..................................... Ok   (01:02:32)
t23: memdebug1.rc extensions.rc................................ Ok   (01:49:58)
t25: valgrind1.rc -O3 extensions.rc............................ Ok   (00:56:08)
t24: memdebug2.rc extensions.rc................................ Ok   (01:43:34)
t27: test-ex1.rc............................................... Ok   (00:45:00)
t26: valgrind2.rc -O3 extensions.rc............................ Ok   (01:02:52)
t29: test-ex3.rc............................................... Ok   (00:31:48)
t28: test-ex2.rc............................................... Ok   (01:12:03)
t30: test-ex4.rc............................................... Ok   (01:09:47)
t32: test.rc alignment4.rc -m32 CC=clang....................... Ok   (00:48:31)
t31: test.rc alignment3.rc sqlite3udl.c........................ Ok   (01:22:29)
t34: test.rc alignment6.rc..................................... Ok   (00:35:31)
t33: test.rc alignment5.rc extensions.rc....................... Ok   (00:59:33)
t35: test.rc alignment7.rc..................................... Ok   (00:44:10)
t40: fast.rc alignment2.rc sqlite3udl.c........................ Ok   (00:15:46)
t39: fast.rc alignment1.rc extensions.rc -m32.................. Ok   (00:33:19)
t36: test.rc ~MUTATION_TEST.................................... Ok   (00:35:45)
t42: fast.rc alignment4.rc..................................... Ok   (00:13:03)
t43: fast.rc alignment5.rc..................................... Ok   (00:13:32)
t44: fast.rc alignment6.rc..................................... Ok   (00:11:41)
t41: fast.rc alignment3.rc sqlite3udl.c........................ Ok   (00:26:31)
t45: fast.rc alignment7.rc..................................... Ok   (00:12:57)
t46: fast.rc -fsanitize=undefined.............................. Ok   (00:38:18)
*******************************************************************************
0 failures on 44 th3makes and 198583082 tests in (07:16:01) 3 cores on bella
SQLite 3.24.0 2018-05-18 17:58:33 c6071ac99cfa4b6272ac4d739fc61a85acb544f6c1c2a

위에서 볼 수 있듯이, multitest.tcl의 단일 실행은 th3make를 수십 번 호출하고 12에서 24 CPU 시간이 걸려요. 출력의 중간 부분은 각 개별 th3make 실행의 인자와 그 th3make의 결과 및 경과 시간을 보여줘요. 별도의 th3make 실행에 대한 모든 빌드 산출물과 출력은 테스트 후 분석을 위해 하위 디렉터리에 담겨요. 하단의 두 줄 요약은 모든 th3make 실행에 대한 총 오류 및 테스트 수와 총 경과 시간, 그리고 테스트된 SQLite 버전에 대한 SQLITE_SOURCE_ID 정보를 보여줘요. 이 요약 정보는 최종 테스트 중 릴리스 체크리스트에 기록돼요.

축약이 multitest.tcl 출력에 적용되어 각 th3make 호출이 단일 80열 출력 줄에 들어가게 해요. 첫 "th3make" 동사는 생략돼요. "~"는 "-DSQLITE_"의 약자이고 "++"는 "-DSQLITE_ENABLE"을 나타내요. 따라서 multitest.tcl 출력 줄

quick.rc ~DISABLE_INTRINSIC ++RTREE

은 실제로 다음을 의미해요:

th3make quick.rc -DSQLITE_DISABLE_INTRINSIC -DSQLITE_ENABLE_RTREE

4. 테스트 커버리지

사용 가능한 TH3 테스트 모듈의 특정 하위 집합("cov1" 테스트)을 사용해, SQLite는 Linux x86 및 x86_64 하드웨어에서 gcov로 측정했을 때 100% 분기 테스트 커버리지와 100% MC/DC를 얻었어요. 3.6.17 (2009-08-10) 이후의 모든 SQLite 릴리스가 이 표준으로 테스트됐어요. SQLite 개발자들은 향후 모든 릴리스에 대해 100% 분기 커버리지와 MC/DC를 유지하기로 약속했어요.

100% 분기 테스트 커버리지를 얻는 데 사용되는 cov1 테스트 집합은 현재 TH3로 구현된 테스트의 하위 집합일 뿐이에요. 새 테스트 모듈이 정기적으로 추가돼요.

5. 변이 테스트

TH3 소스 트리에는 변이 테스트(mutation testing) 과정을 자동화하는 "mutation-test.tcl"이라는 스크립트가 포함돼 있어요.

mutation-test.tcl 스크립트는 변이 테스트 실행의 모든 세부 사항을 처리해요:

  • 스크립트는 필요하면 TH3 테스트 하네스를 기계어("th3.o")로 컴파일해요.
  • 스크립트는 필요하면 sqlite3.c 소스 파일을 어셈블리 언어("sqlite3.s")로 컴파일해요.
  • 스크립트는 어셈블리 언어 파일의 명령을 반복하며 분기 연산을 찾아요.
  • 스크립트는 원본 sqlite3.s 파일의 사본을 만들어요.
  • 사본이 편집되어 분기 명령을 no-op 또는 무조건 점프로 변경해요.
  • sqlite3.s의 사본이 sqlite3.o로 어셈블된 다음 th3.o에 링크되어 "th3" 실행 파일을 생성해요.
  • "th3" 바이너리가 실행되고 오류가 있는지 출력이 검사돼요.
  • 스크립트는 이전 단계의 각 주기에 진행 상황을 보여준 다음 끝에 "survivor(생존자)" 요약을 표시해요. "survivor"는 TH3가 감지하지 못한 변이예요.

변이 테스트는 느릴 수 있어요. 각 테스트는 빠른 워크스테이션에서 최대 5분이 걸릴 수 있고, 각 분기 명령에 대해 두 개의 테스트가 있으며, 2만 개가 넘는 분기 명령이 있기 때문이에요. 작동을 신속하게 하기 위한 노력이 이루어져요. 예를 들어 TH3는 첫 오류를 찾는 즉시 종료하도록 컴파일되고, 많은 변이가 쉽게 감지되므로 많은 주기가 몇 초 만에 발생해요. 그럼에도 mutation-test.tcl 스크립트에는 테스트되는 코드 줄의 범위를 제한하는 명령줄 옵션이 포함되어 있어, 변이 테스트가 최근에 변경된 코드 블록에 대해서만 수행될 수 있어요.

6. TH3 라이선스

SQLite 자체는 퍼블릭 도메인이고 어떤 목적으로든 사용할 수 있어요. 하지만 TH3는 독점적이며 라이선스가 필요해요.

오픈소스 사용자가 TH3에 직접 접근할 수는 없지만, 모든 SQLite 사용자는 각 SQLite 버전이 릴리스 전에 여러 플랫폼(Linux, Windows, WinRT, Mac, OpenBSD)에서 TH3를 실행해 검증되므로 TH3의 혜택을 간접적으로 받아요. 그래서 공식 릴리스의 SQLite를 사용하는 사람은 누구나 TH3로 테스트됐다는 확신을 가지고 애플리케이션을 배포할 수 있어요. 다만 TH3 라이선스를 구매하지 않고서는 스스로 그 테스트를 다시 실행할 수 없을 뿐이에요.

더 알아보기 (Learn more)