小巧。快速。可靠。
三选其二。
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 年 9 月 25 日编写。在接下来的 10 个月中,人们付出了巨大的努力,最终在 2009 年 7 月 25 日实现了 100% 的 MC/DC。TH3 代码持续改进和扩展。

截至 2018 年 5 月 19 日,TH3 源代码树包含超过 500,000 行源代码,分布在 1709 个独立文件中。

2. 操作

TH3 是一个测试程序生成器。TH3 的输出是用 C 代码实现的程序,旨在与正在测试的 SQLite 库链接。生成的测试程序在目标平台上编译和运行,以验证 SQLite 在该平台上的正确操作。

TH3 的输入是用 C 或 SQL 编写的测试模块,以及确定如何初始化 SQLite 的小型配置文件。TH3 软件包包含 1,444 个测试模块和 47 多个配置(截至 2018 年 5 月 19 日)。可以添加新的模块和配置以自定义 TH3 以满足专门的应用程序。每次运行 TH3 时,它都会读取可用测试模块和配置文件的子集,以生成一个自定义的 C 程序,该程序在所有指定的配置下执行所有指定的测试。SQLite 的完整测试通常涉及多次运行 TH3 以生成涵盖 SQLite 操作不同方面的多个测试程序,然后将所有测试程序与一个通用的 SQLite 库链接,并在目标平台上单独运行它们。

TH3 中没有任意限制。可以生成包含所有测试模块和所有配置文件的单个测试程序。但是,这样的测试程序可能太大而无法部署到嵌入式平台上。(截至 2018 年 5 月 19 日,完整的 TH3 测试超过 850,000 行,58 MB 的 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 的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”文件包含一组八个参数传递给 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 在 Linux x86 和 x86_64 硬件上通过gcov 测量获得了100% 的分支测试覆盖率和 100% 的MC/DC。自3.6.17 版(2009 年 8 月 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 未检测到的变异。

变异测试可能很慢,因为每个测试在一个快速的 workstation 上可能需要 5 分钟,并且每个分支指令都有两个测试,并且超过 20,000 个分支指令。正在努力加快操作速度。例如,TH3 的编译方式使其在找到第一个错误时立即退出,并且许多变异很容易被检测到,因此许多循环只需几秒钟即可完成。尽管如此,mutation-test.tcl 脚本包含命令行选项以限制测试的代码行范围,以便变异测试只需要对最近更改的代码块执行。

6. TH3 许可证

SQLite 本身属于公共领域,可以用于任何目的。但 TH3 是专有的,需要许可证。

即使开源用户无法直接访问 TH3,所有 SQLite 用户都间接受益于 TH3,因为每个版本的 SQLite 在发布之前都会在多个平台(Linux、Windows、WinRT、Mac、OpenBSD)上运行 TH3 进行验证。因此,任何使用 SQLite 正式版本的用户都可以自信地部署他们的应用程序,因为他们知道它已经使用 TH3 进行了测试。他们只是无法在没有购买 TH3 许可证的情况下自己重新运行这些测试。

此页面最后修改于 2022-01-08 05:02:57 UTC