ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入 zstd CLI 测试框架:run.py 运行器、测试用例编写与精确/通配输出匹配机制

深入 zstd CLI 测试框架:run.py 运行器、测试用例编写与精确/通配输出匹配机制 深入 zstd CLI 测试框架run.py 运行器、测试用例编写与精确/通配输出匹配机制【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/moldzstd 仓库自带一套专注于CLI命令行行为验证的测试框架位于third-party/zstd/tests/cli-tests/。本文以该目录下的官方文档README.md为主体结合运行器run.py的源码实现与真实测试用例系统讲解测试运行器的全部命令行参数、测试用例的组织与编写规范、setup/teardown生命周期以及.exact/.glob/.ignore三类期望文件的匹配语义。读完本文你可以直接运行这套测试、读懂任意现有用例并为新的命令行参数或行为编写规范化的回归测试。在 mold 仓库中zstd 以第三方依赖形式随项目分发test/目录下的compress-debug-sections-zstd.sh、compress-debug-sections-zstd-level.sh等用例也印证了 mold 对--compress-debug-sectionszstd压缩调试信息场景的依赖理解这套 CLI 测试框架有助于在排查压缩相关行为时快速定位 zstd 命令行层面的问题。测试框架定位只测 CLI不测库README.md开篇即明确了这套测试的边界测试对象是zstd 命令行工具programs/目录下的代码意图是简单直接地验证CLI 及其参数按宣传的方式工作它不是库测试不会刻意去触发库内部的特定条件库代码只会被间接覆盖incidental coverage如果目标是针对lib/中的某个特定压缩路径做单元级验证应使用其他测试工具而不是本框架。也就是说这是一套黑盒式的端到端命令行回归测试跑一个可执行脚本捕获其退出码、stdout、stderr再与期望值比对。运行器 run.py 的完整使用手册测试运行器是third-party/zstd/tests/cli-tests/run.pyPython 3 脚本约 730 行。它默认对树内in-tree构建的zstd与datagen运行测试因此运行前必须先构建好这两个二进制zstd由programs/构建datagen由tests/构建。指定被测二进制与执行前缀命令行参数默认值说明--zstd /path/to/zstdprograms/zstd指定被测的 zstd 主程序--datagen /path/to/datagentests/datagen指定测试数据生成器--zstdgrep /path/to/zstdgrepprograms/zstdgrep指定 zstdgrep 工具--zstdless /path/to/zstdlessprograms/zstdless指定 zstdless 工具--exec-prefix valgrind -q无给zstd 调用加前缀如 valgrind、qemu两点关键语义--exec-prefix只作用于zstd及其符号链接datagen、zstdgrep、zstdless不使用EXEC_PREFIX这些路径通过环境变量ZSTD_SYMLINK_DIR、DATAGEN_BIN、ZSTDGREP_BIN、ZSTDLESS_BIN、EXEC_PREFIX传给测试脚本测试脚本内直接调用zstd、datagen等命令名即可无需关心真实路径。在 run.py 的main中可以看到这些参数的默认值来自仓库相对路径ZSTD_PATH os.path.join(PROGRAMS_DIR, zstd) ZSTDGREP_PATH os.path.join(PROGRAMS_DIR, zstdgrep) ZSTDLESS_PATH os.path.join(PROGRAMS_DIR, zstdless) DATAGEN_PATH os.path.join(TESTS_DIR, datagen)运行全部测试不带任何参数即运行全部测试并输出汇总结果./run.py ./run.py --preserve ./run.py --zstd ../../build/programs/zstd --datagen ../../build/tests/datagen默认情况下每个测试执行完毕后其 scratch临时目录会被删除--preserve会保留这些目录并把测试的退出码、stdout、stderr 分别写入 scratch 目录下的exit、stdout、stderr三个文件便于调试或更新期望输出。运行指定测试可以传入一个或多个测试名只执行其中的子集——在编写或调试某个用例时尤其有用./run.py basic/help.sh ./run.py --preserve basic/help.sh basic/version.sh ./run.py --preserve --verbose basic/help.sh测试名有两种写法测试文件的路径或相对测试目录的测试名resolve_listed_tests()中先按原样解析若文件不存在则拼上测试目录再解析仍不存在则报Test {name} does not exist!。传入的名字必须落在测试目录之内有assert test.startswith(options.test_dir)越界保护。更新精确输出当某个用例因.stderr.exact/.stdout.exact与最新行为不再吻合而失败时可以用--set-exact-output让运行器把实际输出写回期望文件./run.py --set-exact-output ./run.py basic/help.sh --set-exact-output从源码看该逻辑位于TestCase._check_output_exact()比对失败且开启了set_exact_output时直接把actual字节写入对应的.exact文件。这是先跑通、再固化期望的工作流核心配合--preserve使用效果更佳。其余参数--verbose打印每个测试的详细执行信息包括注入的环境变量$KEYvalue与逐项检查结果--timeout N单个用例超时秒数默认 200 秒设为0或负数则禁用超时--test-dir DIR指定测试根目录默认即cli-tests/同时决定bin/加入$PATH以及scratch/的位置。退出码语义运行器退出码与测试结果挂钩全部通过退出0任一失败退出1run_tests()返回布尔值后在main中sys.exit(0/1)可直接接入 CI。测试目录结构速览cli-tests/下每个子目录是一个测试套件suite套件内直接存放用例脚本子目录中的用例不属于该套件cli-tests/ ├── bin/ # 加入 $PATH 的辅助脚本zstd、datagen、die、println、cmp_size… ├── common/ # 可被 source 的公共 shell 库platform.sh、format.sh、mtime.sh、permissions.sh ├── scratch/ # 运行时临时目录gitignore 忽略--preserve 时保留 ├── basic/ # 帮助、版本、参数错误等基础用例 ├── compression/ # 压缩级别、多线程、长距离匹配等用例 ├── decompression/ # 解压行为用例 ├── dictionaries/ # 字典训练/使用用例演示 setup_once/setup 的经典场景 ├── progress/ # 进度条显示/隐藏行为用例 ├── file-stat/ # stdin/stdout/文件互转的文件状态用例 └── run.py # 测试运行器.gitignore中忽略了scratch/与bin/symlinks运行期动态生成的 zstd 符号链接目录并显式保留bin/下的跟踪脚本。编写一个测试用例测试的本质测试用例是任意可执行文件通常用 shell 脚本编写。脚本执行结束后运行器会对三样东西做校验退出码exit codestderr 内容stdout 内容。每个用例运行在一个干净的专属目录中scratch/测试名/可用于存放中间文件目录在用例结束后被清理除非传了--preserve。此外套件级setup脚本可以在用例开始前预先准备目录。期望文件的约定默认期望如下退出码期望为0若想改为其他值提供$TEST.exit文件内容为期望的退出码stderr、stdout 期望为空若想覆盖默认提供以下三类文件之一期望文件匹配语义$TEST.{stdout,stderr}.exact输出与期望逐字节完全一致byte-for-byte$TEST.{stdout,stderr}.glob期望文件逐行按 glob 语法匹配单独一行...匹配任意多行直到下一个期望行匹配$TEST.{stdout,stderr}.ignore忽略输出不做校验源码中_check_output()按.exact→.glob→ 默认忽略即期望为空的顺序解析若三者都不存在则按输出应为空处理并直接通过该项检查。glob匹配的底层实现值得展开run.py 中的glob_diff()逐行弹出实际输出与期望行用 Python 的fnmatch.fnmatchcase做单行比对遇到...行时会持续消费实际输出行直到某一行与...之后的下一期望行匹配否则判定失败并输出差异表示期望行、表示实际行。注意它还允许末尾多余的换行allow extra newlines避免行尾差异造成误报。通过用例示例全部来自 README1. 期望退出码为 1exit-1.sh --- #!/bin/sh exit 1 --- exit-1.sh.exit --- 1 ---2. 精确匹配 stdoutecho.sh --- #!/bin/sh echo hello world --- echo.sh.stdout.exact --- hello world ---3. 用 glob 匹配随机输出*匹配任意内容random.sh --- #!/bin/sh head -c 10 /dev/urandom | xxd 2 --- random.sh.stderr.glob --- 00000000: * * * * * * ---4. 用...匹配不定行数random-num-lines.sh --- #!/bin/sh echo hello seq 0 $RANDOM echo world --- random-num-lines.sh.stdout.glob --- hello 0 ... world ---失败用例示例帮助理解断言方向脚本exit 1但没有.exit文件 → 期望退出码 0实际 1失败脚本echo hello world但无任何 stdout 期望文件 → 期望 stdout 为空实际有输出失败脚本输出world到 stderr但.stderr.exact写的是hello→ 精确比对失败。测试环境bin/ 辅助命令、common/ 公共库与环境变量$PATH 前置 bin/ 目录测试脚本的$PATH会被前置bin/子目录run.py 主流程中env[PATH] bin_dir : os.getenv(PATH, )。bin/内提供了一组包装脚本zstd根据EXEC_PREFIX决定是否加前缀再转发到ZSTD_SYMLINK_DIR下的真实 zstdbin/symlinks/由 run.py 在启动时用os.symlink为zstd、zstdmt、unzstd、zstdcat、zcat、gzip、gunzip、gzcat、lzma、unlzma、xz、unxz、lz4、unlz4等 14 个命令名创建符号链接全部指向被测的 zstddatagen、zstdgrep、zstdless分别转发到DATAGEN_BIN、ZSTDGREP_BIN、ZSTDLESS_BINunzstd、zstdcat指向zstd的符号链接依赖 zstd 按argv[0]自动切换模式的特性die向 stderr 打印消息并以 1 退出println ${*} 12; exit 1用于断言某命令必须失败printlnprintf %b\n的封装cmp_size比较两个文件大小的工具支持-eq/-ne/-lt/-le/-gt/-ge六种关系运算用于验证更高压缩级别产出更小文件这类大小关系断言且不会在set -x下打印文件大小。zstd-symlinks/zstdcat.sh就是围绕unzstd/zstdcat符号链接行为的用例zstdcat hello.zst输出解压内容zstdcat hello.zst world混合文件与压缩文件输入时按序输出并用ln -s验证本地符号链接同样生效对应zstdcat.sh.stdout.exact的 8 行精确输出。common/ 公共 shell 库common/下是可在用例中source的公共库README 以source $COMMON/library.sh示意实际文件按职责拆分platform.sh跨平台环境探测——按uname区分 macOS/BSD/Linux设定MD5SUM、DIFFSunOS 用gdiff、DEVDEVICE/dev/random或/dev/zero、INTOVOID/dev/null或 Windows 的NUL并探测多线程支持hasMT通过zstd -v -T2的输出判断format.shzstd_supports_format查询zstd -h是否包含--format...与format_extension格式名到扩展名的映射如zstd→zst、gzip→gzmtime.shassertSameMTime断言两个文件修改时间一致兼容 BSD 的stat -f %mpermissions.shassertFilePermissions/assertSamePermissions断言文件权限位兼容 BSD 的stat -f %Lp。progress/progress.sh与progress/no-progress.sh展示了公共库的实际用法先source $COMMON/platform.sh再利用$INTOVOID把压缩到管道等场景的 stdout 重定向掉用println 2输出步骤标记最后以.stderr.glob断言进度信息的显示/隐藏逻辑。运行器注入的环境变量用--verbose运行会列出注入到每个测试子进程的环境变量主要包括环境变量含义EXEC_PREFIXzstd 调用前缀来自--exec-prefixZSTD_SYMLINK_DIRbin/symlinks 符号链接目录ZSTD_REPO_DIRzstd 仓库根目录DATAGEN_BIN/ZSTDGREP_BIN/ZSTDLESS_BIN各工具的绝对路径COMMONcommon/ 目录路径供source $COMMON/...使用PATH前置bin/后的路径LC_ALL固定为C保证输出可预期值得注意的细节_test_environment()会过滤掉所有ZSTD*开头的环境变量if not k.startswith(ZSTD)注释明确说明这是为了让测试跨环境一致避免宿主环境中的ZSTD_CLEVEL之类的设置污染用例结果。此外run.py 还支持可选的$TEST.stdin文件作为用例的 stdin 输入存在则打开作为子进程 stdin否则为/dev/null这是一个 README 未展开但源码确认的附加能力。setup 与 teardown套件级与用例级生命周期每个测试目录套件可以附带 4 个可选脚本脚本执行时机工作目录setup_once套件内所有用例之前执行一次套件的 scratch 目录teardown_once套件内所有用例之后执行一次套件的 scratch 目录setup每个用例执行前该用例的 scratch 目录teardown每个用例执行后该用例的 scratch 目录setup_once/teardown_once适合做套件间共享的、开销较大的准备工作以提升效率setup/teardown适合做用例间共享的轻量准备。源码中TestSuite以上下文管理器实现进入套件时_setup_once()先删除旧 scratch 再重建退出时_teardown_once()每个用例经由test_case()上下文管理器包裹先_setup(test_basename)创建scratch/套件/用例名/并运行setupfinally 中运行teardown并非--preserve时删除该目录。_remove_scratch_dir()带有安全断言目录名必须包含scratch且位于套件 scratch 之下防止误删。README 给出两个典型例子basic 套件用setup预生成输入文件basic/setup --- #!/bin/sh datagen file datagen file0 datagen file1 --- basic/test.sh --- #!/bin/sh zstd file file0 file1 ---dictionaries 套件用setup_oncesetup分层准备字典dictionaries/setup_once --- #!/bin/sh set -e mkdir files/ dicts/ for i in $(seq 10); do datagen -g1000 files/$i done zstd --train -r files/ -o dicts/0 --- dictionaries/setup --- #!/bin/sh # 运行在用例的 scratch 目录中 # setup_once 工作的套件 scratch 目录是其父目录。 cp -r ../files ../dicts . ---仓库中实际的dictionaries/setup_once比 README 示例更完整它用datagen -g1000 -s$seed生成 100 个确定性的种子样本训练出两个字典并断言二者不同cmp dicts/0 dicts/1 die dictionaries must not match!再用第三个非样本文件验证字典外数据的处理setup则通过cp -r ../files .把共享产物复制进每个用例目录既方便使用又避免用例意外修改共享数据。实战用例赏析compression/levels.shcompression/levels.sh是这套框架以测试驱动 CLI 行为回归的典型代表几乎覆盖了本文介绍的所有机制用datagen file生成随机测试数据依赖setup与bin/datagen用zstd --fast10、--fast1、-1、-19、--max产出不同压缩级别的文件再用zstd -t逐一校验可解压性用cmp_size -le/-lt断言压缩级别越高、文件越小的单调关系验证--fast等价于-1、-0即默认级别、-99被钳制clamp到 19、--fast200000可用用zstd -5000000000 -f file die Level too large, must fail断言超界级别必须失败系统性地验证ZSTD_CLEVEL环境变量负值、19、99、非法值-、、a、-a、3a7、5000000000以及命令行优先级高于环境变量通过配套的levels.sh.stderr.exact精确固化每次调用的 stderr包括error: numeric value overflows 32-bit unsigned int、Ignore environment variable setting ZSTD_CLEVEL...等消息——这份精确期望文件本身就充当了 zstd 错误提示文案的回归基线。类似的还有compression/multi-threaded.sh-T0/-T2/--auto-threadslogical|physical/--rsyncable等线程相关旗标组合、basic/args.sh非法参数--blah、-xz、--adaptmin1,maxx2等必须以退出码 1 失败并以.stderr.glob匹配Incorrect parameter: ...与...通配的 Usage 文本、file-stat/系列stdin/stdout/文件两两组合的 8 种输入输出路径及其.stderr.exact等均可作为编写新用例的直接参考。运行器实现要点小结结合 run.py 源码可以归纳出这套框架的几个核心设计测试发现get_all_tests()用os.walk遍历测试目录排除bin/、common/、scratch/三个目录排除setup、setup_once、teardown、teardown_once、README.md、run.py、.gitignore这些文件名并排除.exact、.glob、.ignore、.exit后缀的期望文件——剩下的可执行文件即测试用例干净环境每个用例在独立 scratch 目录中执行cwd指向该目录stdin 默认/dev/null环境变量经过过滤与注入保证结果可复现可扩展的校验退出码默认 0、输出默认空.exit/.exact/.glob/.ignore文件逐级覆盖默认--set-exact-output可一键固化最新行为可并行化TestCase.launch()与TestCase.analyze()分离launch 只启动子进程不阻塞analyze 再回收结果文档注释明确说明可借此并行启动多个用例run()只是二者的串行组合可调试--preserve保留 scratch 目录并落盘exit/stdout/stderr--verbose输出环境与逐项检查明细配合--timeout防止挂死。在 mold 仓库中使用与进一步阅读本框架位于third-party/zstd/tests/cli-tests/相关文件均可在仓库内直接查看与运行仓库只读仅作查阅与本地构建验证运行器run.py官方说明README.md辅助脚本目录bin/含zstd转发脚本、die、println、cmp_size公共库common/platform.sh、format.sh、mtime.sh、permissions.sh示例套件basic/、compression/、dictionaries/、progress/运行前提是先在 zstd 构建目录中构建zstd与datagen随后在cli-tests/目录内执行./run.py或按需追加--zstd、--datagen、--preserve、--verbose等参数。若需借助 valgrind、qemu 等工具对 zstd 加壳运行使用--exec-prefix即可无需修改任何测试脚本——这正是本框架简单、直接、贴近 CLI 用户视角的设计初衷。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表