小巧、快速、可靠。
任選三項。
TH3

1. 概述

SQLite 測試架構 #3(以下簡稱「TH3」)是 三個測試架構 之一,用於測試 SQLite。TH3 符合下列目標

TH3 最初僅撰寫用於驗證測試,但隨後也用於開發測試和除錯,並證明在這些角色中非常有幫助。在工作站上進行全涵蓋測試不到五分鐘,因此在 SQLite 程式碼庫的日常維護期間,可作為快速的回歸測試。

1.1. 歷史

TH3 起源於測試 SymbianOS 上的 SQLite 的工作。
在 TH3 之前,所有 SQLite 測試都是使用 TCL 腳本語言執行的,但 TCL 無法(輕鬆地)編譯在 SymbianOS 上,這使得測試變得困難。解決此問題的第一次嘗試是「TH1」(測試架構 #1)腳本語言 - 以更具可攜性的形式重新實現 TCL 語言的部分,以便在 SymbianOS 上編譯和執行,足以執行 SQLite 測試。TH1 沒有作為 SQLite 的標準測試工具而存在,但它確實持續作為用於自訂 Fossil 版本控制系統的腳本語言。還有一個「測試架構 #2」,這是嘗試使用運算子前綴表示法建立一個簡單的腳本語言來驅動測試。TH3 是第三次嘗試。

大約在同時間,一些航空電子製造商對 SQLite 表示興趣,這促使 SQLite 開發人員設計 TH3 以支援 DO-178B 的嚴格測試標準。

TH3 的第一個程式碼於 2008-09-25 訂定。接下來 10 個月的努力,讓 TH3 在 2009-07-25 達到 100% MC/DC。TH3 程式碼持續改良和擴充。

截至 2018-05-19,TH3 原始碼樹包含 1709 個獨立檔案中超過 500,000 行的原始碼。

2. 操作

TH3 是一個測試程式產生器。TH3 的輸出是一個以 C 程式碼實作的程式,並打算連結到受測的 SQLite 函式庫。產生的測試程式會在目標平台上編譯並執行,以驗證 SQLite 在該平台上的正確操作。

TH3 的輸入是使用 C 或 SQL 編寫的測試模組,以及決定如何初始化 SQLite 的小型組態檔。TH3 套件包含 1,444 個測試模組和 47 個以上的組態(截至 2018-05-19)。可以新增新的模組和組態,以自訂 TH3 以符合特殊應用程式。每次執行 TH3 時,它會讀取可用測試模組和組態檔的子集,以產生一個自訂的 C 程式,在所有指定的組態下執行所有指定的測試。完整的 SQLite 測試通常涉及執行 TH3 多次,以產生涵蓋 SQLite 操作不同面向的多個測試程式,然後將所有測試程式連結到一個共用的 SQLite 函式庫,並在目標平台上分別執行它們。

TH3 中沒有任意的限制。可以產生一個包含所有測試模組和所有組態檔的單一測試程式。然而,此類測試程式可能太大,無法部署在嵌入式平台上。(截至 2018-05-19,一個完整的 TH3 測試超過 850,000 行和 58MB 的 C 程式碼。)TH3 提供將測試模組函式庫分解成較小、較容易消化的片段的能力。

每個個別測試模組可能包含數十、數百或數千個獨立的測試。測試模組可以用 C 編寫,或作為 SQL 程式碼或兩者的混合體。現有的測試模組中約有三分之二是用純 SQL 編寫,其餘的則是用純 C 或 C 和 SQL 的組合編寫。

每個測試模組檔都包含一個標頭,描述測試有效的環境。對於特定組態,只會執行與組態相容的模組。

3. 產生測試程式

TH3 程式產生器是一個名為 "mkth3.tcl" 的 TCL 程式碼。要產生一個測試程式,只要執行這個程式碼,並在命令列中提供包含測試模組和組態的檔案名稱即可。測試模組是使用 ".test" 字尾的檔案,而組態是使用 ".cfg" 字尾的檔案。mkth3.tcl 的一般呼叫可能會類似以下

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

mkth3.tcl 程式碼的輸出是一個 C 程式,其中包含執行測試所需的一切,除了 SQLite 函式庫本身。產生的測試程式包含測試模組使用的所有支援介面的實作,以及驅動測試的 main() 常式。要將測試程式轉換為可執行的執行檔,只要針對 SQLite 編譯即可

cc -o testprog1 testprog1.c sqlite3.c

上面立即顯示的編譯步驟只是一個代表。在一個工作安裝中,通常會想要在編譯器命令列中指定最佳化參數和編譯時間開關。

對於嵌入式系統的測試,mkth3.tcl 程式碼和上面顯示的編譯步驟會在一個使用交叉編譯器的普通工作站上執行,然後將產生的測試程式傳輸到要執行的裝置上。

產生測試程式後,執行時不帶任何引數以執行測試。進度資訊以及錯誤診斷會顯示在標準輸出中。(對於缺乏標準輸出通道的嵌入式裝置,可以使用編譯時間選項進行其他輸出安排。)如果沒有錯誤,程式會傳回零,如果偵測到任何問題,則會傳回非零。

單一 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_SOURCE_ID 報告開始(再使用 sqlite3_sourceid() 交叉檢查),針對測試中的 SQLite,以及使用 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」檔案包含一組八個 th3make 參數,用於執行快速(3 分鐘)的完整涵蓋範圍測試。這允許操作員輸入「./th3make quick.rc」作為輸入所有必需的命令列選項的捷徑。以下是 40 多個可用 *.rc 檔案中的一部分

TH3 儲存庫也包含「multitest.tcl」腳本,這是另一個用於自動化工作站上 TH3 測試的 TCL 腳本。Multitest.tcl 會自動編譯 SQLite,然後重複執行 ./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 取得 100% 分支測試涵蓋範圍 和 100% MC/DC,這是由 Linux x86 和 x86_64 硬體上的 gcov 所測量。自 版本 3.6.17(2009-08-10)以來的所有 SQLite 發行版本都已根據此標準進行測試。SQLite 開發人員致力於為所有未來的 SQLite 發行版本維持 100% 分支涵蓋範圍和 MC/DC。

用於取得 100% 分支測試涵蓋範圍的 cov1 測試組只是目前使用 TH3 實作的測試子集。會定期新增新的測試模組。

5. 突變測試

TH3 原始碼樹包含一個腳本名稱「mutation-test.tcl」,用於自動化 突變測試 的程序。

mutation-test.tcl 這個指令碼會處理執行變異測試的所有細節

  1. 這個指令碼會在必要時將 TH3 測試架構編譯成機器碼(「th3.o」)。
  2. 這個指令碼會在必要時將 sqlite3.c 原始檔編譯成組合語言(「sqlite3.s」)。
  3. 這個指令碼會迴圈處理組合語言檔中的指令,以找出分支運算。
    1. 這個指令碼會複製一份原始的 sqlite3.s 檔。
    2. 這份複製會被編輯,將分支指令改成無動作或無條件跳躍。
    3. 這份 sqlite3.s 複製會被組譯成 sqlite3.o,然後再與 th3.o 連結,以產生「th3」可執行檔。
    4. 「th3」二進位檔會被執行,並檢查輸出是否有錯誤。
  4. 這個指令碼會顯示前一個步驟的每個循環進度,然後在最後顯示「倖存者」摘要。「倖存者」是 TH3 沒有偵測到的變異。

變異測試可能會很慢,因為每個測試在快速的電腦工作站上可能需要花費 5 分鐘,而且每個分支指令有兩個測試,而分支指令超過 20,000 個。我們會努力加快運作。例如,TH3 會以一種方式編譯,讓它在找到第一個錯誤時就退出,而且由於許多變異很容易偵測到,許多循環只會花費幾秒鐘。儘管如此,mutation-test.tcl 指令碼包含命令列選項,以限制測試的程式碼行範圍,這樣變異測試只需要針對最近變更的程式碼區塊執行。

6. TH3 授權

SQLite 本身是 公有領域,可用於任何目的。但 TH3 是專有的,需要授權。

即使開源使用者無法直接存取 TH3,SQLite 的所有使用者都能間接受益於 TH3,因為每個版本的 SQLite 都會在發布之前使用 TH3 在多個平台(Linux、Windows、WinRT、Mac、OpenBSD)上驗證執行。因此,使用 SQLite 官方版本的任何人都可以安心部署他們的應用程式,因為他們知道它已經使用 TH3 進行過測試。他們只是無法在不購買 TH3 授權的情況下自行重新執行這些測試。

此頁面最後修改於 2022-01-08 05:02:57 UTC