
libSQL/SQLite testrunner.tcl 并行测试框架完全指南用法、参数与实现原理【免费下载链接】libsqllibSQL is a fork of SQLite that is both Open Source, and Open Contributions.项目地址: https://gitcode.com/GitHub_Trending/li/libsql导读testrunner.tcl是 libSQLSQLite 的开源分支源码树中一个基于 Tcl 的测试调度脚本它能把海量 SQLite 测试用例拆分到多个 CPU 核心上并行执行并在运行过程中将全部结果沉淀为testrunner.log日志与testrunner.db数据库方便事后检索与排查。本文以 testrunner.md 为主线结合本仓库 test/testrunner.tcl、test/permutations.test、test/testrunner_data.tcl 与 main.mk 中的实际实现系统讲解其支持的二进制测试与源码测试两类模式、全部命令行参数、测试失败排查手段以及 CPU 并发控制原理。读完本文你将能够独立使用 testrunner.tcl 在本地完成 veryquick/full/all 测试集、mdevtest/sdevtest/release 等开发验证任务并懂得如何分析其结果数据库。1. 总览testrunner.tcl 是什么testrunner.tcl是一个 Tcl 脚本位于 libsql-sqlite3/test/testrunner.tcl它的职责是使用多个并行任务job来运行大量 SQLite 测试。它支持两类测试Tcl 测试脚本*.test文件通过make命令执行的测试例如make mdevtest、make releasetest、make sdevtest、make testrunner。需要特别说明的是仓库根目录下并没有名为testrunner.tcl的文件它通常是由make testrunner目标见 main.mk通过如下命令间接调用testrunner: testfixture$(EXE) ./testfixture$(EXE) $(TOP)/test/testrunner.tcl也就是说make testrunner实际就是构建 testfixture 之后用 testfixture 运行 testrunner.tcl。1.1 两个输出物testrunner.log 与 testrunner.dbtestrunner.tcl 会把所有测试与构建的输出通过管道写入当前工作目录下的testrunner.log日志文件。排查错误时建议用以下 grep 命令定位grep ^! testrunner.log grep failed testrunner.log同时testrunner.tcl 会向testrunner.db一个 SQLite 数据库写入测试元数据其中记录了所有已运行、运行中、待运行的测试任务。一个常用的查询示例SELECT * FROM script WHERE statefailed说明该示例来自上游文档实际实现中记录任务的是jobs表而非script表见下文 1.3因此在真实库上可把查询写为SELECT * FROM jobs WHERE statefailed。1.2 status 命令与 watch 组合在包含testrunner.db的目录下执行以下命令testrunner.tcl 会运行一组内部查询输出一段关于当前运行状态的简明报告./testfixture $(TESTDIR)/testrunner.tcl status在另一个终端用watch包裹它是观察长时间运行的测试进度的好办法watch ./testfixture $(TESTDIR)/testrunner.tcl status其中$(TESTDIR)指本仓库的libsql-sqlite3/test/目录。在源码实现中status命令由 testrunner.tcl 处理它还支持两个附加参数-d SECS每隔 N 秒自动刷新屏幕配合--cls使用 VT100 清屏控制码以及-cls。show_status过程会实时汇总各状态的任务数、已完成任务数、累计错误数/用例数并在完成超过 100 个任务且占总任务 10% 以上时给出预计剩余时间估算值下限为已用时间的 2%。1.3 底层实现jobs 表结构从源码的数据库模式定义testrunner.tcl可以看到testrunner.db的核心表结构CREATE TABLE jobs( jobid INTEGER PRIMARY KEY, -- 任务唯一标识 displaytype TEXT NOT NULL, -- 任务类别tcl/fuzz/make/bld 等 displayname TEXT NOT NULL, -- 人类可读的任务名 build TEXT NOT NULL DEFAULT , -- 所用构建配置名如 Win32-MemDebug dirname TEXT NOT NULL DEFAULT , -- 任务专用目录名空则用匿名 testdirN cmd TEXT NOT NULL, -- 要执行的 shell/batch 命令 depid INTEGER, -- 依赖的前置任务 jobid priority INTEGER NOT NULL, -- 优先级数值越大越先运行 starttime INTEGER, -- 开始时间毫秒时间戳 endtime INTEGER, -- 结束时间 state TEXT CHECK( state IN (,ready,running,done,failed,omit) ), ntest INT, -- 已运行的测试用例数 nerr INT, -- 报告的错误数 svers TEXT, -- 报告的 SQLite 版本 pltfm TEXT, -- 报告的主机平台 output TEXT -- 测试输出 ); CREATE TABLE config( name TEXT COLLATE nocase PRIMARY KEY, value ) WITHOUT ROWID;任务状态机包含未就绪、ready就绪、running运行中、done完成、failed失败、omit因前置任务失败而跳过六种状态。任务之间存在depid依赖关系例如 Tcl 测试任务总是依赖构建出对应 testfixture的bld任务只有构建成功依赖它的测试任务才会从变为ready。priority字段用于调度排序——源码中慢速slow测试优先级为 2、超慢速superslow为 4、fuzz 大文件数据为 5而且调度器会让编号为奇数的 worker 按优先级升序、偶数的按降序取任务以避免所有 worker 一窝蜂抢占同一批高优先级任务。1.4 解释器选择testfixture 还是 tclshtestrunner.tcl 有时用运行它的testfixture二进制直接跑测试见第 2 节Binary Tests有时则自行构建 testfixture 及其他二进制、以特定配置来测试见第 3 节Source Tests。在实现层面脚本开头的find_interpreter过程testrunner.tcl负责解释器探测首先尝试package require sqlite3要求解释器支持 SQLite 3.31.1 或更新的 Tcl 绑定若失败且当前可执行文件不是 testfixture而当前目录下存在可执行的./testfixture则自动用 testfixture 重新启动自身若仍失败会尝试make tclextension现场编译 Tcl 扩展最终仍不可用则报错退出并提示运行make tclextension或make testfixture。2. 二进制测试Binary Tests本节描述的命令都是用运行 testrunner.tcl 的那个 testfixture 二进制来跑各种 Tcl 测试脚本的组合——它们不会调用编译器构建新二进制也不会用make去跑非 Tcl 的测试。因此流程固定为两步用方便的方式构建出testfixtureWindows 下为testfixture.exe二进制用该二进制运行 testrunner.tcl 测试它可附加各种选项。在 main.mk 中可以看到 testfixture 的构建规则它由测试源文件、libsqlite3.a与src/tclsqlite.c链接而成还需要系统 Tcl 库。2.1 Tcl 测试的组织方式Tcl 测试存放在文件名匹配*.test的文件中分布在源码树的$TOP/test/目录以及$TOP/ext/的各个子目录下如 libsql-sqlite3/test 下有大量*.testext/fts5/test/、ext/rtree/等也有自己的测试。并非所有*.test文件都直接包含测试用例——少数是用于调用其他*.test文件的 Tcl 调度脚本例如all.test、full.test、extraquick.test等。veryquick 测试集源码树中所有 Tcl 测试脚本的一个子集包含大多数测试但排除了一些非常慢的用例几乎所有故障注入测试OOM、IO 错误响应类都被排除。其定义在 test/permutations.test 中test_suite veryquick -prefix -description { Very quick test suite. Runs in minutes on a workstation. } -files [ test_set $allquicktests -exclude *malloc* *ioerr* *fault* *bigfile* *_err* \ *fts5corrupt* *fts5big* *fts5aj* *rbucrash* ]即在allquicktests由 permutations.test 从alltests排除async2.test、crash*.test、fuzz.test、thread00x.test等慢速用例后得到基础上再剔除所有名字含malloc、ioerr、fault、bigfile、_err的模式。full 测试集源码树中全部Tcl 测试脚本。运行full意味着运行能找到的所有 Tcl 测试脚本。permutations排列permutations.test 定义了许多测试排列。每个排列由两部分组成一组 Tcl 测试脚本子集运行每个测试脚本前要应用的运行时配置例如启用 auto-vacuum、关闭 lookaside 等。排列通过test_suite过程注册到全局::testspec数组其可用选项包括-description、-initialize测试前执行的脚本、-shutdown测试后执行的脚本、-presql测试前执行的 SQL、-files测试文件列表、-prefix、-dbconfig。例如valgrind排列就用-initialize设置::G(valgrind)并在-shutdown中清除它mmap排列则通过-presql { pragma mmap_size 268435456; }预先开启内存映射。all全部运行 full 测试集的全部用例再加上约一打dozen排列。all所包含的具体排列定义在 test/testrunner_data.tcl 的all_configs列表中共 20 种full no_optimization memsubsys1 memsubsys2 singlethread multithread onefile utf16 exclusive persistent_journal persistent_journal_error no_journal no_journal_error autovacuum_ioerr no_mutex_try fullmutex journaltest inmemory_journal pcache0 pcache10 pcache50 pcache90 pcache100 prepare mmap。以排列方式运行单个脚本时实际执行的是./testfixture testrunner.tcl $PERMUTATION $SCRIPTtestrunner.tcl 中有单文件运行分支它会source tester.tcl、按排列设置::G(perm:dbconfig)/::G(perm:presql)等全局变量、执行-initialize脚本最后source目标测试脚本。2.2 运行测试的命令运行veryquick测试集以下两种写法等价不带参数时默认为 veryquick./testfixture $TESTDIR/testrunner.tcl ./testfixture $TESTDIR/testrunner.tcl veryquick运行full测试套件./testfixture $TESTDIR/testrunner.tcl full运行 full 套件中文件名匹配指定模式的子集例如所有以 fts5 开头的测试以下两种写法都行./testfixture $TESTDIR/testrunner.tcl fts5% ./testfixture $TESTDIR/testrunner.tcl fts5*严格来说匹配规则是模式必须按 Tcl 的[string match]规则匹配脚本文件名不含目录部分匹配之前模式中出现的所有%字符会被转换为*见 testrunner.tcl 的命令行解析string map {% *}。另外在运行主流程时add_tcl_jobs 会对每个模式做进一步处理若不以^开头则在前面补*若不以$结尾则在末尾补*从而实现前缀/后缀模糊匹配。运行allfull 全部排列./testfixture $TESTDIR/testrunner.tcl all注意all、full、veryquick以及任意自定义排列都要求使用testfixture而非普通 tclsh运行——must_be_testfixture 会检查解释器是否提供sqlite3_soft_heap_limit命令否则报错 Use testfixture, not tclsh。2.3 排查二进制测试失败某个测试失败时testrunner.tcl 会把失败测试的 Tcl 脚本名若适用还包括排列名打印到 stdout同样可以从testrunner.log或testrunner.db中查到。若失败不在任何排列中可直接单独运行该脚本复现./testfixture $PATH_TO_SCRIPT若失败发生在某个排列中./testfixture $TESTDIR/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT这里的$PERMUTATION是排列名$PATH_TO_SCRIPT是目标测试脚本的路径例如./testfixture test/testrunner.tcl valgrind test/thread001.test。进阶排查技巧运行结束后还可以直接查询结果数据库。errors或任意err*前缀命令testrunner.tcl会从jobs表提取所有statefailed任务的输出默认只打印以!开头或含failed的出错行-v/--verbose显示完整输出-s/--summary仅列出失败任务名还可追加模式过滤。而joblist命令则按状态标签READY/DONE/FAILED/OMIT/RUNNING列出所有任务。3. 源码测试Source Code Tests本节命令会调用 C 编译器从源码树构建二进制再用这些二进制运行 Tcl 及其他测试。其优势在于可以用一条命令测试多种构建配置确保测试始终使用同一组编译选项产出的二进制。本节命令既可以用 testfixture或 testfixture.exe运行也可以用任何支持 SQLite 3.31.1 或更新版本、能package require sqlite3的 Tcl 解释器运行。3.1 运行 SQLite 测试的命令mdevtest命令等价于对两个--enable-all构建一个开调试、一个不开各执行一次 veryquick 测试加make fuzztesttclsh $TESTDIR/testrunner.tcl mdevtest等价于手工执行$TOP/configure --enable-all --enable-debug make fuzztest make testfixture ./testfixture $TOP/test/testrunner.tcl veryquick # 然后清理上述测试产生的文件 $TOP/configure --enable-all OPTS-O0 make fuzztest make testfixture ./testfixture $TOP/test/testrunner.tcl veryquick在源码中mdevtest 分支使用All-Debug与All-O0两个构建配置testrunner.tcl它们在 testrunner_data.tcl 中定义为set build(All-Debug) { --enable-debug --enable-all -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES } set build(All-O0) { -O0 --enable-all }注意mdevtest的流程对每个构建先add_build_job生成构建脚本、构建 testfixture再add_tcl_jobs挂上 veryquick 的 Tcl 测试若命令行未指定模式还会add_fuzztest_jobs追加 fuzz 任务。这对应 main.mk 中的devtest/mdevtest目标——devtest在运行 testrunner 前还会先执行srctree-check校验源码树中生成文件是否最新。sdevtest与 mdevtest 完全相同唯一区别是第二个构建是sanitizer 构建用OPTS-fsanitizeaddress,undefined替代OPTS-O0tclsh $TESTDIR/testrunner.tcl sdevtest对应源码中 sdevtest 分支使用All-Debug与All-Sanitize后者的定义testrunner_data.tcl为set build(All-Sanitize) { -DSQLITE_OMIT_LOOKASIDE1 --enable-all -fsanitizeaddress,undefined -fno-sanitize-recoverundefined }即开启 ASAN地址消毒器 UBSAN未定义行为消毒器并在未定义行为发生时立即终止-fno-sanitize-recoverundefined。release命令会在大量构建下运行大量测试。它根据运行平台是 Linux、Windows 还是 OSX 选择不同的构建 × 测试组合具体细节见 testrunner_data.tcltclsh $TESTDIR/testrunner.tcl release从 testrunner_data.tcl 可以看到平台相关的测试分配例如 Linux 平台包含Fast-One、Debug-One、Have-Not、Secure-Delete、Sanitize、Valgrind等构建其中linux.Default运行all_plus_autovacuum_crash即all_configs再加autovacuum_crashlinux.Valgrind运行valgrind排列Windows 平台则包含Stdcall、Have-Not、Windows-Memdebug、Windows-Win32Heap、Windows-Sanitize且win.Default运行 full 测试集。额外的make测试通过extra()数组配置例如linux.Debug-One会额外跑fuzztest sourcetest mptest。与源码测试一样以上任何命令mdevtest、sdevtest 或 release都可以追加一个或多个模式此时只运行匹配该模式的Tcl 测试不再跑 fuzz 等其他测试。例如只在 release 支持的所有构建与配置下运行 rtree 的 Tcl 测试tclsh $TESTDIR/testrunner.tcl release rtree%3.2 运行 ZipVFS 测试testrunner.tcl 可以构建一个启用 zipvfs 的 testfixture并用它运行 ZipVFS 项目的测试tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS它也可以与 mdevtest、sdevtest 或 release 中的任意一个组合用一条命令同时测试 SQLite 与 ZipVFStclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest源码中add_zipvfs_jobstestrunner.tcl会sourceZipVFS 目录下的test/zipvfs_testrunner.tcl、为Zipvfs构建配置创建 testfixture 构建任务并为每个zipvfs_testrunner_files返回的测试脚本创建 Tcl 任务同时把SQLITE_TEST_DIR环境变量设置为测试目录避免大量临时文件产生文件名冲突每个任务的工作目录还会单独设置SQLITE_TMPDIR。3.3 排查源码测试失败排查源码测试阶段的失败是两步走重建出失败时的构建配置重新运行实际测试。重建构建配置使用 testrunner.tcl 的script命令它会生成一个构建脚本——Linux/OSX 上是 bash 脚本Windows 上是*.bat文件。例如# 生成在 Linux/OSX 上重建构建配置 Device-One 的脚本 tclsh $TESTDIR/testrunner.tcl script Device-One make.sh # 生成在 Windows 上重建构建配置 Have-Not 的脚本 tclsh $TESTDIR/testrunner.tcl script Have-Not make.bat生成的脚本接受一个参数要构建的 makefile 目标。它既可以用来直接运行make命令测试也可以用来构建 testfixture或 testfixture.exe再用它按 2.3 节的方式运行 Tcl 测试脚本。例如tclsh $TESTDIR/testrunner.tcl script Device-One make.sh bash make.sh testfixture # 构建该配置下的 testfixture各构建配置的定义集中在 testrunner_data.tcl它们体现了 SQLite 官方为覆盖不同平台/编译选项而设计的组合例如Have-Not把所有-UHAVE_xxx选项全部关掉HAVE_FDATASYNC0、HAVE_GMTIME_R0、HAVE_ISNAN0等验证代码在缺少这些系统服务时依然可用Device-One / Device-Two模拟资源受限的嵌入式设备小页大小、小缓存、限制MAX_PAGE_SIZE、禁用某些特性Secure-Delete开启SQLITE_SECURE_DELETEUnlock-Notify开启SQLITE_ENABLE_UNLOCK_NOTIFYAndroid / Apple对应移动平台的编译宏集合Valgrind配合 valgrind 运行CONFIG_SLOWDOWN_FACTOR8.0放慢执行以便检测内存问题。4. 其他 testrunner.tcl 选项本节选项对源码测试和二进制测试都适用开关列表可直接在 testrunner.tcl 的usage帮助文本中查看。--buildonly只构建测试所需的二进制不运行任何测试。例如# 只构建 release 测试所需的二进制 tclsh $TESTDIR/testrunner.tcl --buildonly release实现上handle_buildonlytestrunner.tcl在构建完所有bld任务后会从 jobs 表中删除所有非bld类型的任务。--dryrun不构建任何二进制、不运行任何测试只把本应执行的 shell 命令写入 testrunner.log。示例# 把 mdevtest 的 shell 命令记录到日志 tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest--explain与 --dryrun 类似同样不构建、不运行但区别是它会在标准输出打印一份人类可读的摘要说明将会运行哪些构建和测试。示例# 展示 mdevtest 将会运行的构建与测试 tclsh $TESTDIR/testrunner.tcl --explain mdevtestexplain_teststestrunner.tcl按依赖层次递归打印bld任务显示为在哪个目录构建什么测试任务则以(配置) 文件名形式列出。其余可用开关来自 usage 帮助与命令行解析testrunner.tcl开关短形式作用--jobs NUM-j用 NUM 个独立进程运行测试--config CONFIGS-c只使用逗号分隔列表 CONFIGS 中的构建配置--omit CONFIGS-c省略逗号分隔列表 CONFIGS 中的构建配置--zipvfs DIR-z指定 ZipVFS 源码目录--stop-on-error—出现任何错误后立即停止--stop-on-coredump—任何测试段错误core dump后立即停止--status—运行过程中显示完整 status 报告Windows 上不可用源码会提示改用status -d 2开另一个窗口另外还有两个实用子命令help打印完整帮助list列出所有合法的 PERMUTATION 取值mdevtest、sdevtest、release等。5. 控制 CPU 核心利用率无论运行二进制测试还是源码测试testrunner.tcl 都会在 stdout 报告它打算使用的任务数job 数。例如$ ./testfixture $TESTDIR/testrunner.tcl splitting work across 16 jobs ... more output ...默认 job 数的确定逻辑在 testrunner.tcl若环境变量NJOB存在且 ≥ 1则直接采用$NJOB的值否则探测机器真实核心数Linux 用nprocmacOS 用sysctl -n hw.logicalcpuWindows 读NUMBER_OF_PROCESSORS探测失败时保守取 4若核心数 ≤ 2则只用 1 个辅助进程否则使用核心数的一半int(nCore*0.5)作为默认 job 数。也就是说文档中所说的默认设置为机器真实核心数在实际代码里体现为取核心数的一半并受NJOB环境变量优先覆盖这为操作系统和其他进程留出了余量。可以通过--jobs或-j开关覆盖默认值$ ./testfixture $TESTDIR/testrunner.tcl --jobs 8 splitting work across 8 jobs ... more output ...运行过程中也可以动态调整 job 数在包含 testrunner.log 和 testrunner.db 的目录下执行$ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBSnjob命令testrunner.tcl会把新值写入config表的njob项然后打印当前值不带参数时仅查询当前值。参数必须是 0128 之间的整数超出会报错 parameter must be an integer between 0 and 128。正在运行的调度循环launch_some_jobs每完成一个任务就会重新读取config表中的njob因此新值会尽快生效。数值设为 0 时调度器将不再启动新任务正在运行的任务会跑完后自然收尾。6. 端到端实战一条典型的开发验证流水线结合以上全部内容给出一个在 libSQL 源码树中实际可行的验证流程假设已安装 Tcl 与构建工具# 1) 构建 testfixture二进制测试的基础 make -C libsql-sqlite3 testfixture # 2) 快速冒烟veryquick16 核机器上默认拆成 8 个并行 job ./libsql-sqlite3/testfixture libsql-sqlite3/test/testrunner.tcl veryquick # 3) 开发提交前标准动作mdevtest等价于两个 --enable-all 构建 fuzztest veryquick cd libsql-sqlite3 tclsh test/testrunner.tcl mdevtest # 4) 只跑某类测试例如全部 rtree 相关用例带 sanitizer tclsh test/testrunner.tcl sdevtest rtree% # 5) 想看 release 测试会做什么而不真正执行 tclsh test/testrunner.tcl --explain release # 6) 运行期间另开终端持续观察进度 watch ./testfixture test/testrunner.tcl status运行结束后在日志与数据库中检索结果grep ^! testrunner.log # 带 ! 前缀的错误行 grep failed testrunner.log sqlite3 testrunner.db SELECT displayname, state FROM jobs WHERE statefailed从 main.mk 的清理目标可以看到测试会产生testrunner_bld_*、testdir*、testrunner.*等文件可用make clean等价目标一并清理。总结testrunner.tcl是 libSQL/SQLite 测试体系中的总调度器它通过testrunner.db的jobs任务表统一管理构建、Tcl 测试、fuzz 测试、make 测试四类任务及其依赖关系用多个 worker 进程并行执行二进制测试模式veryquick/full/all直接复用现有 testfixture源码测试模式mdevtest/sdevtest/release则从零构建多种配置的二进制再跑测试--dryrun/--explain让你在不执行的前提下审查测试计划status/njob让你在长任务运行中实时监控并动态调整并行度。掌握了这些命令与背后的数据结构你就能像 SQLite 核心开发者一样在提交代码前用最低的成本获得最全面的回归保障。【免费下载链接】libsqllibSQL is a fork of SQLite that is both Open Source, and Open Contributions.项目地址: https://gitcode.com/GitHub_Trending/li/libsql创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考