libSQL 的 SQLite 测试脚本解释器:.test 脚本规范、命令集与 SQLTester 源码解析

发布时间:2026/9/13 23:06:00
libSQL 的 SQLite 测试脚本解释器:.test 脚本规范、命令集与 SQLTester 源码解析 libSQL 的 SQLite 测试脚本解释器.test 脚本规范、命令集与 SQLTester 源码解析【免费下载链接】libsqllibSQL is a fork of SQLite that is both Open Source, and Open Contributions.项目地址: https://gitcode.com/GitHub_Trending/li/libsql导读在 libSQL 仓库的 Java JNI 绑定libsql-sqlite3/ext/jni中官方 SQLite 团队设计了一套极简的测试脚本语言以.test为后缀的纯文本脚本内书写 SQL 语句与期望结果由解释器逐行执行、比对并报告差异。本文以仓库内的规范文档 test-script-interpreter.md 为核心骨架完整讲解脚本的解析规则、16 个核心命令的语义、结果缓冲区的转义算法与 TEST-GLOB 模式并结合 SQLTester.java 的源码实现与 tests 目录下的真实测试脚本让你既能读懂既有.test脚本也能独立编写、运行和扩展自己的测试用例。概述解释器的定位与设计目标测试脚本解释器Test Script Interpreter是 JNI 绑定自带的轻量级测试框架核心。它的职责非常聚焦读取包含 SQL 命令与期望结果的脚本文件执行脚本中的 SQL检查实际结果与期望结果是否一致报告发现的任何不一致discrepancies。脚本文件格式脚本文件具备如下基本属性是ASCII 文本文件文件名总是以.test结尾每个脚本独立求值上下文不会从一个脚本传递到下一个脚本。例如某个脚本中的--null命令不会改变后续脚本的行为每个脚本结束时所有打开的数据库连接都会被关闭脚本创建的所有数据库文件在脚本结束时都会被删除。也就是说脚本与脚本之间唯一的状态残留只有两类计数器已运行的测试数量和观察到的失败数量。这一点保证了测试的幂等性与可重复性。解析规则解释器如何逐行处理脚本解释器逐行读取脚本一行指直到下一个\n0x0a或文件结尾的字符序列且永远不需要越过当前行末尾向前预读。每一行按以下 8 条规则判定模块类型检查若某行包含 MODULE_NAME:注意M前有空格或MIXED_MODULE_NAME:则该脚本与本规范不兼容立即终止处理verbose 模式下可输出因不兼容的模块类型而放弃脚本的提示信息。正确模块声明若某行包含SCRIPT_MODULE_NAME:说明脚本类型正确可以继续处理。在见到SCRIPT_MODULE_NAME之后步骤 1 中的检查可以可选地停止。必需特性检查若某行包含REQUIRED_PROPERTIES:且其后跟有任何非空白文本则脚本不兼容立即停止处理verbose 模式可输出因不支持的必需特性而放弃脚本。dbtotxt 格式拒绝若某行以|0x7c字符开头表示脚本包含dbtotxt 格式数据库规范与当前规范不兼容立即停止处理。C 预处理器行拒绝任何以#开头的行都是 C 预处理器行解释器无法处理处理应被放弃。verbose 模式下可输出类似script NAME abandoned due to C-preprocessor line: ...的信息。命令识别若一行以恰好两个减号--开头且后跟小写字母则该行是一条命令按下文命令章节处理。其余行累积所有其他行被累积进输入缓冲区input buffer。各个命令都可以访问该缓冲区部分命令会重置它。源码印证指令扫描的实现在 SQLTester.java 的checkForDirective()方法中上述规则被直接翻译为正则表达式patternScriptModuleName Pattern.compile( SCRIPT_MODULE_NAME:[ \\t]*(\\S)\\s*$)patternRequiredProperties Pattern.compile( REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$)patternMixedModuleName Pattern.compile( ((MIXED_)?MODULE_NAME):[ \\t]*(\\S)\\s*$)命中#开头的行抛出IncompatibleDirectiveC-preprocessor input命中---三连横线同样被拒triple-dash。需要注意由于这些正则都要求前面带一个空格测试作者可以通过在关键字前加x前缀如xREQUIRED_PROPERTIES:将其注释化。仓库中的 000-000-sanity.test 正是这样做的** SCRIPT_MODULE_NAME: sanity-check ** xMIXED_MODULE_NAME: mixed-module ** xMODULE_NAME: module-name ** xREQUIRED_PROPERTIES: small fast reliable其中xREQUIRED_PROPERTIES因为前缀x的存在不会匹配正则x之前不是空格从而被安全地跳过。另外源码中checkRequiredProperties()目前以if( true ) return false;硬编码返回不支持即任何真实的REQUIRED_PROPERTIES:都会导致脚本被放弃。命令行的识别同样来自正则patternCommand Pattern.compile(^--(([a-z-])( .*)?)$)随后经getCommandArgv()切分成argvSQLTester.java#L1317-L1372主循环TestScript.run()逐行分发SQLTester.java#L1416-L1432。初始化每个脚本的起点状态解释器在开始处理每个脚本时初始状态等价于已经执行过如下命令序列--close all --db 0 --new test.db --null nil含义是所有数据库连接全部关闭仅保留0 号连接默认连接它打开在一个名为test.db的空数据库上同时将 SQL NULL 列值的显示文本设为字符串nil。与之对应的SQLTester.reset()SQLTester.java#L394-L404在每次测试之间会清空输入缓冲区、结果缓冲区、数据库初始化 SQL关闭全部连接把 NULL 显示值复位为nil、列名输出开关复位为关闭并将当前连接指回 0 号槽位——与规范中的初始化语义完全一致。命令体系一套形似 SQL 注释的迷你 DSL命令的语法形态每个命令都形似 SQL 注释命令从行首开始前面无空格以恰好两个减号--开头命令名由小写字母组成可包含一个或两个-部分命令携带参数参数与命令名之间以一个或多个空格分隔命令可以访问输入缓冲区可能重置它也可以可选地读取并消费命令之后脚本中的额外文本。未知或无法识别的命令表示脚本包含本规范尚未支持的特性处理应立即终止。verbose 模式下可输出类似test script NAME abandoned due to unsupported command: --whatever的信息。但在当前实现中CommandDispatcher 抛出UnknownCommand后由上层决定跳过剩余部分skipUnknownCommands()目前硬编码为true。规范的初始实现仅识别下列命令后续可继续扩展。下表汇总了全部 16 个命令及其作用命令作用参数--testcase NAME开启一个测试用例重置输入/结果缓冲区用例名--result TEXT执行输入缓冲区中的 SQL将结果与期望文本做精确比较期望结果--glob PATTERN同--result但用 TEST-GLOB 模式比较GLOB 模式--notglob PATTERN同--glob但匹配成功时反而报错GLOB 模式--oom声明 OOM 测试意图当前可静默忽略无--tableresult同--glob但模式取自后续行直到--end无正文到--end--new FILE删除旧文件后打开一个全新空数据库文件名--open FILE打开一个已存在的数据库文件文件名--db N切换当前数据库连接N 为 0606 的整数--close [N\|all]关闭指定/当前/全部数据库连接可选--null TEXT设定 NULL 值的显示文本文本--run [N]静默执行输入缓冲区中的 SQL不产生输出可选 06--json TEXT同--result但列值原样输出、精确比较期望 JSON--json-block同--tableresult但逐行精确比较 JSON无正文到--end--print TEXT将参数与正文输出到 stdout带缩进文本/正文--testcase测试用例的边界每个测试用例都以--testcase命令开始。它同时重置输入缓冲区与结果缓冲区其参数是该用例的名字用于日志、调试与错误输出。输入缓冲区随即被设置为该用例的正文。实现上TestCaseCommand.process()会设置用例名并清空两个缓冲区SQLTester.java#L989-L996。--result执行 SQL 并精确比对--result尝试把输入缓冲区中的文本当作 SQL 执行。每行结果的文本按从左到右、逐列的方式追加到结果缓冲区result buffer规则如下若结果缓冲区已有内容先追加一个空格这样所有列值、所有行值之间都以单个空格分隔若sqlite3_column_text()返回 NULL追加nil或--null命令指定的其他文本并跳过其余规则若值为空字符串追加{}跳过其余规则若值不含任何特殊字符直接原样追加跳过其余规则。特殊字符集为0x000x20含、双引号0x22、反斜杠0x5c、花括号0x7b 与 0x7d若值不含花括号则将其放入{...}中追加否则含花括号以双引号包裹追加文本内的与\前加一个\转义小于 0x20 的控制字符用八进制\NNN表示。执行 SQL 遇到错误时先以列值的形式追加错误的符号化 C 预处理器名例如SQLITE_CONSTRAINT再追加错误消息文本同样按列值处理随后停止处理。SQL 执行完毕后将结果缓冲区内容与--result的参数做比较存在差异即报告测试错误。关键细节--result重置输入缓冲区但不清空结果缓冲区。这一点对自身无影响却对紧随其后的--glob/--notglob至关重要——一个用例常包含一段 SQL 后跟多个--glob/--notglob所有 glob 都应针对同一个结果缓冲区求值而 SQL 只应执行一次。正是通过重置输入缓冲区、保留结果缓冲区实现这一语义。转义逻辑在源码 escapeSqlValue() 中原样实现patternSpecial匹配[\x00-\x20\x22\x5c\x7b\x7d]patternSquiggly匹配[{}]命中前者未命中后者时用{...}包裹两者都命中时进入双引号 八进制转义分支。实际执行由 execSql() 完成内部循环使用sqlite3_prepare_v2()逐段 preparesqlite3_step()迭代行sqlite3_column_text16()取列值按ResultBufferModeNONE / ESCAPED / ASIS与ResultRowModeONELINE / NEWLINE决定写入方式。结果比对在ResultCommand.process()中通过result.equals(sArgs)完成SQLTester.java#L905-L923。--glob与--notglobTEST-GLOB 模式匹配--glob与--result几乎相同唯一区别是参数被解释为TEST-GLOB 模式比较使用 glob 匹配而非strcmp()。TEST-GLOB 与标准 UNIX GLOB 略有差异*匹配零个或多个字符?匹配任意单个字符[...]匹配方括号内的单个字符#匹配一个或多个数字——这是与标准 UNIX glob 的主要区别UNIX glob 无此能力之所以加入是因为 SQLite 测试中大量出现数字序列匹配需求。--notglob与--glob完全相同只是当 GLOB 匹配成功时报错而非匹配失败时报错。从源码看GlobCommand通过布尔negate区分两种行为SQLTester.java#L819-L839底层匹配调用 native 方法strglob()Java 侧文档明确指出#匹配一串数字出现在一串数字的开头或中间时不匹配如#23、1#3不匹配但在末尾可以匹配如12#SQLTester.java#L648-L660。native 实现位于 sqlite3-jni.c。--oom内存耗尽测试标记--oom用于 out-of-memory 测试表示应模拟 OOM 错误以验证 SQLite 能否妥善应对。规范允许当前阶段静默忽略后续可能添加支持。在实现中它被映射为NoopCommand无操作占位命令SQLTester.java#L856-L860。--tableresult多行表格式期望--tableresult的工作方式类似--glob但 GLOB 模式取自后续若干行脚本直到下一个--end模式文本中每一段一个或多个空白字符被折叠为单个空格0x20模式首尾空白被去除结束 GLOB 模式的--end不属于模式本身但会从脚本输入中被消费掉。实现上TableResultCommand要求正文必须--end结尾按行切分后逐行与结果缓冲区按行ResultRowMode.NEWLINE做 glob 比较行数不一致也会报错SQLTester.java#L943-L986。--new与--open数据库文件管理--new FILE打开一个初始为空的数据库打开前先删除该文件--open FILE若文件已存在则打开既有数据库。两者在实现中共用OpenDbCommand仅createIfNeeded删除重建 vs 保留打开布尔值不同SQLTester.java#L851-L888。注意初始脚本默认执行的是--new test.db。--db最多 7 条连接的切换解释器最多可同时打开 7 条 SQLite 数据库连接编号 06。--db用于切换当前连接参数是 06 的整数。源码中setCurrentDb()会做编号合法性断言越界会抛异常affirmDbId。--close关闭连接--close关闭一条已打开的数据库连接若该连接当前未打开则为空操作参数为连接编号06越界会失败参数为all时关闭所有打开的连接不传参数时默认关闭当前活动连接。实现见 CloseDbCommand。--null自定义 NULL 显示文本--null改变结果缓冲区中表示 SQL NULL 值的文本默认初始值为nil。例如--null zilch之后NULL 列值将以zilch呈现。--run静默执行 SQL--run把输入缓冲区中的文本当作 SQL 执行但不向结果缓冲区添加任何内容SQL 的任何输出被静默忽略SQL 的错误被静默忽略。--run默认在当前数据库连接上执行若其参数为 06 的整数则在指定的替代连接上执行。源码RunCommand采用ResultBufferMode.NONE仅在 verbose 模式下打印非致命错误SQLTester.java#L926-L940。--json与--json-blockJSON 友好比较--json工作方式同--result--json-block工作方式同--tableresult。两者的共同点是列值原样追加到结果缓冲区——从不用{...}或...包裹也不转义列值中的任何字符比较永远是精确的strcmp()而非 GLOB。这使其非常适合验证json_array()、json_object()等 JSON 函数的输出。实现上对应JsonCommand(ResultBufferMode.ASIS)与JsonBlockCommand(true)SQLTester.java#L841-L849。--print向 stdout 输出--print把其参数以及正文若有输出到 stdout输出每行带缩进。它常用于脚本的进度提示与日志。--column-names列名输出开关--column-names接受参数 0 或 1关闭/开启用于修改 SQL 执行以在输出中包含列名。开启后每个列值之前都会追加列名 单个空格。对应实现为ColumnNamesCommand设置emitColNames标志SQLTester.java#L800-L808在execSql()中由emitColNames分支为每个列追加列名ASIS 模式下原样ESCAPED 模式下同样经过转义规则。补充当前实现还额外支持规范之外的--verbosity N命令设置 0/1/2 三级详细程度它并非本规范的一部分可视为实现的超集特性。实战逐行解读仓库内的真实测试脚本覆盖全部核心命令的 sanity 脚本仓库中的 000-000-sanity.test 是一个近乎完美的命令全览逐段分析--print starting up --close all --oom --db 0 --new my.db --null zilch脚本开头依次演示了--print输出启动提示、--close all、--oom被忽略的占位、--db 0、--new my.db重建名为my.db的空库、--null zilch将 NULL 显示文本改为zilch--testcase 1.0 SELECT 1, null; --result 1 zilch --glob *zil* --notglob *ZIL* SELECT 1, 2; intentional error; --run第一个用例1.0中SELECT 1, null的结果按转义规则生成1 zilchNULL 因--null zilch显示为zilch--result 1 zilch精确匹配随后的--glob *zil*与--notglob *ZIL*复用同一结果缓冲区验证了重置输入缓冲区但保留结果缓冲区的设计最后SELECT 1, 2;与一条语法错误的 SQL 一起交给--run静默执行——错误被忽略。--testcase json-1 SELECT json_array(1,2,3) --json [1,2,3]JSON 用例验证json_array输出[1,2,3]ASIS 模式无转义、精确比较--testcase tableresult-1 select 1, a; select 2, b; --tableresult # [a-z] 2 b --end表格式用例两行结果分别匹配 GLOB# [a-z]与2 b。注意#匹配一个或多个数字这里匹配1[a-z]匹配a--testcase json-block-1 select json_array(1,2,3); select json_object(a,1,b,2); --json-block [1,2,3] {a:1,b:2} --endJSON 多行块用例逐行精确比对--testcase col-names-on --column-names 1 select 1 as a, 2 as b; --result a 1 b 2 --testcase col-names-off --column-names 0 select 1 as a, 2 as b; --result 1 2 --close --print reached the end最后两个用例验证列名开关开启时结果为a 1 b 2关闭时结果为1 2脚本以--close关闭当前连接和--print收尾。被忽略的脚本|触发的拒绝路径000-001-ignored.test 是一个刻意设计为被放弃的脚本它正确声明了SCRIPT_MODULE_NAME: ignored但正文包含一行以|开头的行正好命中解析规则 4dbtotxt 格式拒绝从而验证不兼容指令导致脚本被立即放弃的分支** SCRIPT_MODULE_NAME: ignored |FTS5 集成测试中的转义实战900-001-fts.test 展示了转义规则的威力--testcase 1.0 CREATE VIRTUAL TABLE email USING fts5(sender, title, body); insert into email values(fred,Help!,Dear Sir...); insert into email values(barney,Assistance,Dear Madam...); select * from email where email match assistance; --result barney Assistance {Dear Madam...}barney、Assistance不含特殊字符原样输出Dear Madam...中的...虽不含空格等特殊字符但注意结果缓冲区以空格分隔列值包含空格的文本会被{...}包裹——因此期望值为barney Assistance {Dear Madam...}恰好与转义规则第 5 步一致。该脚本同时说明虚拟表、INSERT 等常规 SQL 语句可以直接放入输入缓冲区参与测试。如何构建与运行测试根据 ext/jni/README.md 的说明构建 JNI 绑定需要 Linux 类环境、GNU Make、支持 Java 8 的 JDK 以及现代 C 编译器gcc 或 clang 均可标准流程为$ export JAVA_HOME/path/to/jdk/root $ make $ make test测试脚本位于 libsql-sqlite3/ext/jni/src/tests 目录以NNN-NNN-name.test形式组织。SQLTester作为主程序入口SQLTester.java 的 main支持-verbose可叠加三次提升详细级别、-keep-going失败后继续等命令行标志运行结束时会汇总输出处理过的测试总数与放弃的脚本数也可通过-internals输出 JNI 内部细节。编写你自己的.test脚本最佳实践清单基于规范与源码编写高质量测试脚本时建议遵循声明模块在文件头部的注释块中写入SCRIPT_MODULE_NAME:需要注释化的检查项统一加x前缀一个用例一个--testcase用例名要可读便于定位失败复用结果缓冲区一段 SQL 后跟多个--glob/--notglob时SQL 只执行一次多个模式共享同一结果善用转义语义含空格的文本会被{...}包裹含花括号或控制字符的文本会被... 八进制转义包裹编写期望值时需按此规则推导JSON 场景用--json/--json-block避免手写转义直接精确比对字面量多行结果用--tableresult/--json-block并以--end结束正文多库场景利用--new/--open打开最多 7 个连接用--db N切换--run N在指定连接上静默执行收尾清理脚本结束时所有连接自动关闭、临时数据库自动删除无需显式清理但中途可显式--close验证关闭路径。小结从规范文档到源码实现libSQL 的 JNI 绑定用一套极简的.test脚本语言将 SQLite 测试中最常见的执行 SQL → 比对结果模式压缩为十几条形似 SQL 注释的命令并通过--glob的#数字通配、{...}/...转义规则与 JSON 精确比较模式覆盖了从普通查询到 OOM 预留、多连接切换、列名输出等丰富的测试场景。理解本文所讲的解析规则与命令语义后你可以直接阅读 tests 目录中的真实脚本作为范例也可以参照规范文档与 SQLTester.java 源码编写属于自己的测试套件。【免费下载链接】libsqllibSQL is a fork of SQLite that is both Open Source, and Open Contributions.项目地址: https://gitcode.com/GitHub_Trending/li/libsql创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询