尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入 zstd CLI 测试框架:基于 run.py 的命令行端到端回归测试实战

深入 zstd CLI 测试框架:基于 run.py 的命令行端到端回归测试实战 深入 zstd CLI 测试框架基于 run.py 的命令行端到端回归测试实战【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo导读本文围绕 MongoDB 仓库内 vendored 的 zstd 源码zstd 仓库目录中的 CLI 测试套件 展开系统讲解其设计定位、测试运行器run.py的全部命令行参数、测试用例的编写规范以及setup/teardown脚本体系。读完本文你将掌握如何运行 zstd CLI 的全部或单个回归测试、如何编写一个带有精确输出/glob 输出/忽略输出断言的可执行测试用例并理解其底层实现原理从而能够为 zstd 命令行工具位于 programs/贡献新的 CLI 测试。一、CLI 测试的设计定位只测命令行不测库该测试套件的核心定位在 README.md 中讲得很清楚聚焦 zstd 命令行工具本身这些测试专注验证zstdCLI 及其参数行为是否符合文档承诺as advertised。测试目标是programs/目录下的代码测试只针对 CLI 层而非压缩库本身即lib/下的实现。库代码只获得附带覆盖库函数在测试过程中会被间接执行但如果你的目标是触发库内部的某个特定状态应该使用库级单元测试而不是 CLI 测试。这一边界非常重要它决定了测试的组织方式和断言粒度。CLI 测试关心的是用户输入什么参数、进程退出码是多少、stdout/stderr 输出什么而不过问库内部的压缩算法细节。从源码结构看CLI 相关实现集中在 zstdcli.c命令行参数解析与主流程以及 fileio.c文件输入输出与压缩/解压调度这正好对应 README 所述只测programs/下代码的边界。二、测试运行器 run.py全部用法详解测试运行器是 run.py一个纯 Python 3 脚本无需第三方依赖。其用法在 README 中逐项说明结合源码我们可以还原每个参数的完整语义。2.1 默认行为与前置条件run.py默认针对仓库内构建的zstd和datagen运行测试。从 run.py 的 main 入口 可以看到默认路径推导逻辑ZSTD_PATH os.path.join(PROGRAMS_DIR, zstd) # programs/zstd ZSTDGREP_PATH os.path.join(PROGRAMS_DIR, zstdgrep) # programs/zstdgrep ZSTDLESS_PATH os.path.join(PROGRAMS_DIR, zstdless) # programs/zstdless DATAGEN_PATH os.path.join(TESTS_DIR, datagen) # tests/datagen因此在运行测试前必须先构建zstd与datagen。datagen是 zstd 自带的测试数据生成器源码位于 programs/datagen.c构建产物默认在tests/datagen用于生成可复现的随机测试文件。2.2 核心命令行参数README 与run.py的参数定义argparse 部分共同给出了完整参数表参数默认值作用--zstd /path/to/zstdprograms/zstd指定 zstd 二进制路径会以环境变量ZSTD_BIN形式传递给测试实际实现中通过符号链接目录ZSTD_SYMLINK_DIR间接引用--exec-prefix valgrind -q无设置EXEC_PREFIX环境变量为每次 zstd CLI 调用增加前缀典型用途是套上valgrind、qemu等工具做内存检查或跨架构模拟--datagentests/datagen指定 datagen 二进制路径对应DATAGEN_BIN环境变量--zstdgrepprograms/zstdgrep指定 zstdgrep 二进制路径对应ZSTDGREP_BIN环境变量--zstdlessprograms/zstdless指定 zstdless 二进制路径对应ZSTDLESS_BIN环境变量--preserve关闭保留scratch/目录及每个用例的exit/stdout/stderr产物便于调试--verbose关闭输出每个检查项的详细结果与匹配信息--timeout N200 秒单用例超时设为0表示禁用超时--test-dir DIR本目录指定测试目录其中bin/会被加入$PATHscratch/位于其下--set-exact-output关闭为失败的用例自动重写.stdout.exact/.stderr.exact位置参数tests无仅运行指定测试文件路径或相对测试目录的名称一个关键细节--exec-prefix只作用于 zstd 调用。README 明确说明datagen、zstdgrep等工具不经过EXEC_PREFIX前缀。这从 bin/zstd 包装脚本 的实现可以印证if [ -z $EXEC_PREFIX ]; then $ZSTD_SYMLINK_DIR/$zstdname $ else $EXEC_PREFIX $ZSTD_SYMLINK_DIR/$zstdname $ fi而 bin/datagen 包装脚本 则直接执行$DATAGEN_BIN $确实不经过前缀。2.3 scratch 目录与 --preserve 的作用每个测试都在独立的临时工作目录中运行路径模式为scratch/test/name。例如scratch/basic/help.sh/。目录中的scratch相对于测试目录--test-dir生成默认为tests/cli-tests/scratch/。默认情况下测试结束后目录会被清理加上--preserve后目录被保留并且测试的退出码、stdout、stderr 会分别保存为scratch/test/name/exit、stdout、stderr三个文件。从 run.py 的实现 可以看到--preserve时会在_check_output与_check_exit中把实际输出落盘。这在你编写新测试、更新期望输出的场景下非常有用——先跑一遍拿真实输出再对照生成.exact或.glob期望文件。2.4 运行全部测试不带任何位置参数时run.py会递归扫描测试目录排除bin、common、scratch三个目录见 EXCLUDED_DIRS运行所有测试并汇总报告./run.py ./run.py --preserve ./run.py --zstd ../../build/programs/zstd --datagen ../../build/tests/datagen上面最后一个示例假设你把 zstd 构建到了仓库外的build/目录。最终汇总逻辑在 run_tests全部通过输出PASSED all N tests!并返回 0否则列出失败的测试并返回非零退出码。2.5 运行特定测试测试名可以是测试文件的路径如basic/help.sh相对测试目录的测试名与路径形式相同。这在编写或调试单个用例时特别有用搭配--preserve效果最佳./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-dir再尝试仍不存在则直接报错解析后会按套件目录 → 用例文件名组织执行。2.6 更新精确输出--set-exact-output当测试失败原因是.stderr.exact或.stdout.exact与真实输出不再一致时可以一键修正./run.py --set-exact-output ./run.py basic/help.sh --set-exact-output其实现位于 run.py 的_check_output_exact当精确匹配失败且设置了set_exact_output时把真实输出直接写回.exact文件。注意该机制只作用于精确匹配.ignore或.glob已存在的用例不受影响见--set-exact-output的帮助文本。使用前建议先人工核对差异避免把错误行为固化进期望文件。三、编写一个测试用例3.1 用例的本质任意可执行文件README 明确测试用例是任意可执行文件可以是任何语言但通常是 shell 脚本。脚本执行后运行器会依次比对三个维度退出码默认期望为 0可用.exit文件覆盖stderr默认期望为空可用.stderr.exact/.stderr.glob/.stderr.ignore覆盖stdout默认期望为空可用.stdout.exact/.stdout.glob/.stdout.ignore覆盖。每个用例运行在一个全新且干净的目录中测试脚本可以在其中自由创建中间文件目录在测试结束后清理除非加了--preserve。3.2 期望文件的三种形态期望文件匹配语义$TEST.{stdout,stderr}.exact逐字节精确匹配byte-for-byte$TEST.{stdout,stderr}.glob每行按 glob 通配语法匹配单独一行...表示跳过任意行直到下一行期望内容出现$TEST.{stdout,stderr}.ignore完全忽略该输出流.exit文件则包含一个整数作为期望退出码不存在时默认期望 0。从 run.py 的_check_output可以看出期望文件的查找顺序先找.exact再找.glob都没有则视为忽略即期望输出为空——空输出匹配空输出自然通过。.exact/.glob/.ignore/.exit后缀以及setup、setup_once、teardown、teardown_once、README.md、run.py等文件名都在 EXCLUDED_BASENAMES / EXCLUDED_SUFFIXES 中不会被误当作测试用例。...通配行的底层实现在 glob_diff遇到...\n时弹出下一行期望内容然后持续消费实际输出行直到找到能匹配该下一行期望内容的行若耗尽实际输出仍未匹配则判定失败。逐行 glob 匹配则用fnmatch.fnmatchcaseglob_line_matches。因此*、?等 glob 字符可以直接用于模糊匹配版本号、时间戳、随机字节等不稳定输出。3.3 通过示例可直接参考退出码断言默认期望退出码 0脚本exit 1会失败若确有非零退出意图用.exit文件声明# exit-1.sh #!/bin/sh exit 1# exit-1.sh.exit 1stdout 精确匹配# echo.sh #!/bin/sh echo hello world# echo.sh.stdout.exact hello worldstderr glob 匹配随机数据用 glob 模糊# random.sh #!/bin/sh head -c 10 /dev/urandom | xxd 2# random.sh.stderr.glob 00000000: * * * * * *多行跳过匹配...匹配不定行数的中间输出# random-num-lines.sh #!/bin/sh echo hello seq 0 $RANDOM echo world# random-num-lines.sh.stdout.glob hello 0 ... world仓库中basic/套件就是很好的活例help.sh 执行zstd -h/zstd -H/zstd --help并用 help.sh.stdout.glob 断言短帮助输出逐行精确、长帮助的 Advanced options 段落用...跳过version.sh 用 glob 断言版本字符串。而compression/levels.sh、file-stat/下的多个用例如compress-file-to-dir-without-write-perm.sh、compress-stdin-to-stdout.sh则使用.stderr.exact精确断言错误消息progress/下的用例使用.stderr.glob匹配进度条中的动态数据。3.4 失败示例帮助理解断言语义脚本exit 1但未提供.exit文件 → 期望退出码 0实际 1失败脚本echo hello world但未提供任何 stdout 期望文件 → 期望 stdout 为空实际有输出失败提供了.stderr.exact为hello但脚本输出world→ 精确匹配失败。这三类失败分别对应退出码、stdout 空期望、stderr 精确期望三种校验路径是理解断言体系的最直观案例。四、bin/ 辅助脚本、common/ 公共库与环境变量4.1 $PATH 前置与辅助命令运行测试时run.py会把测试目录下的bin/前置到$PATHmain 中的 env 组装env[PATH] bin_dir : os.getenv(PATH, )。bin/下提供了一系列便于测试的包装脚本bin/zstd按$EXEC_PREFIX前缀调用$ZSTD_SYMLINK_DIR下的同名符号链接注意它是通过basename $0复用因此unzstd、zstdcat等符号链接调用同一脚本bin/datagen调用$DATAGEN_BINunzstd、zstdgrep、zstdcat、zstdless同名辅助命令供测试脚本直接使用bin/printlnprintf %b\n ${*}输出带换行的文本配合set -x调试时不会污染真实输出bin/cmp_size比较两个文件大小支持-eq/-ne/-lt/-le/-gt/-ge操作符用于断言压缩率相对关系bin/die向 stderr 打印消息并以退出码 1 终止用于显式断言某命令不应成功。例如 compression/levels.sh 中就用cmp_size -lt file-19.zst file-1.zst断言级别越高压缩后越小用zstd -5000000000 -f file die Level too large, must fail断言超范围级别必须失败。4.2 符号链接与 zstd 多命令形态setup_zstd_symlink_dir 会在bin/symlinks/下为zstd、zstdmt、unzstd、zstdcat、zcat、gzip、gunzip、lzma、xz、lz4等名称创建指向真实 zstd 二进制的符号链接完整列表见 ZSTD_SYMLINKS。这正是 zstd 支持一个二进制多种命令形态的测试基础zstd-symlinks/套件的 setup 与zstdcat.sh用例专门验证了通过符号链接调用zstdcat的行为。4.3 环境变量一览README 指出测试环境会提供一系列环境变量可通过run.py --verbose打印_test_environment中逐条_vlog见 run.py L288-L298。结合 main 的组装逻辑完整列表如下环境变量含义EXEC_PREFIX由--exec-prefix设置zstd 调用前缀ZSTD_SYMLINK_DIRbin/symlinks/目录包装脚本通过它找到真实 zstdZSTD_REPO_DIRzstd 仓库根目录tests/cli-tests/../..DATAGEN_BINdatagen 二进制绝对路径ZSTDGREP_BINzstdgrep 二进制绝对路径ZSTDLESS_BINzstdless 二进制绝对路径COMMONcommon/目录绝对路径供公共库脚本 sourcePATH前置了bin/的完整路径LC_ALL固定为C保证输出区域设置一致值得注意的是_test_environment会剔除所有以ZSTD开头的宿主环境变量以保证测试跨环境一致例如宿主的ZSTD_CLEVEL不会污染用例。不过用例内部仍可自行设置ZSTD_CLEVEL等变量来测试 CLI 行为——compression/levels.sh 就系统性验证了ZSTD_CLEVEL的取值、非法值回落默认级别、以及命令行参数对它的覆盖优先级。4.4 common/ 公共脚本库README 提到的公共库位于common/目录实际仓库中包括 platform.sh、format.sh、mtime.sh、permissions.sh 等脚本通过source $COMMON/xxx.sh方式引入。例如 format.sh 提供了zstd_supports_format探测当前 zstd 是否支持某格式与format_extension格式名到扩展名映射两个函数供compression/format.sh、compression/gzip-compat.sh等用例判断当前构建的特性。README 中示例写作source $COMMON/library.sh实际引用时以common/目录下具体脚本文件名为准。五、setup 与 teardown 脚本体系5.1 套件级与用例级的两层脚本测试目录中每个目录是一个测试套件test-suite包含该目录下不包含子目录的所有用例。每个套件最多可携带 4 个脚本脚本执行时机工作目录setup_once套件内所有用例之前仅一次套件级 scratch 目录各用例 scratch 目录的父目录teardown_once套件内所有用例之后仅一次同上setup每个用例执行之前该用例的 scratch 目录teardown每个用例执行之后该用例的 scratch 目录套件级脚本用于只做一次的共享准备工作以提升测试效率用例级脚本用于每个用例都需要的前置工作让用例脚本本身更简洁。从 TestSuite 的实现 可以看到__enter__执行_setup_once先清理再重建套件 scratch 目录test_case上下文管理器在用例前后执行_setup/_teardown__exit__执行_teardown_once且仅在未设置--preserve时清理目录。5.2 官方示例逐段解读用例级 setup 的典型用法为多个用例准备同一批输入文件# basic/setup #!/bin/sh # Create some files for testing with datagen file datagen file0 datagen file1# basic/test.sh #!/bin/sh zstd file file0 file1仓库中 compression/setup 与这个示例完全一致先用datagen生成file、file0、file1供compression/套件内的basic.sh、multiple-files.sh、multi-threaded.sh等用例直接压缩使用cltools/setup则先echo 1234 file再zstd file为zstdgrep.sh/zstdless.sh准备压缩好的测试数据。套件级 setup_once 用例级 setup 的配合dictionaries/套件# dictionaries/setup_once #!/bin/sh set -e . $COMMON/platform.sh mkdir files/ dicts/ for seed in $(seq 50); do datagen -g1000 -s$seed files/$seed done zstd --train -r files -o dicts/0 -qq for seed in $(seq 51 100); do datagen -g1000 -s$seed files/$seed done zstd --train -r files -o dicts/1 -qq cmp dicts/0 dicts/1 die dictionaries must not match! datagen -g1000 files/0# dictionaries/setup #!/bin/sh set -e # Runs in the test cases scratch directory. # The test suites scratch directory that # setup_once operates in is the parent directory. cp -r ../files . cp -r ../dicts .这个组合展示了完整的效率设计setup_once一次性训练两个不同的字典dicts/0与dicts/1并断言两者确实不同setup在每个用例前把字典与文件复制到用例自己的 scratch 目录——既让用例脚本可以直接使用又确保用例对共享数据的修改不会污染其他用例从../相对路径可以确认用例级 scratch 目录正是套件级 scratch 目录的子目录。六、仓库内现有测试套件一览以当前仓库 tests/cli-tests/ 目录为准现存的套件与主题包括套件目录覆盖主题basic/帮助信息help.sh、版本version.sh、内存限制memlimit.sh、输出目录output_dir.shcltools/zstdgrep、zstdless这两个配套工具的搜索/分页行为compression/压缩级别与 clamp、--fast、多线程、多文件、长距离匹配、行匹配查找器、窗口调整、流大小、gzip 兼容、golden 数据decompression/解压 golden 数据、非 zstd 数据的 pass-through 行为dict-builder/字典构建对空输入/无输入的错误处理dictionaries/字典匹配、字典不匹配的错误提示、golden 测试file-stat/压缩/解压时文件到文件 / 文件到 stdout / stdin 到文件 / stdin 到 stdout的完整矩阵以及无写权限目录的错误处理progress/进度条输出与--no-progress的行为zstd-symlinks/通过符号链接形态调用zstdcat等命令其中compression/levels.sh是一个内容密度很高的参考用例它验证了级别间压缩大小的偏序、--fast与-1等价、-0与默认级别等价、级别 clamp-99收敛到 19、--fast200000可正常工作、超范围级别报错以及ZSTD_CLEVEL环境变量的完整语义——包括合法值生效、非法值回退默认级别、命令行参数优先于环境变量。想理解 glob 断言的多种写法progress/progress.sh.stderr.glob 与 compression/verbose-wlog.sh.stdout.glob 都值得通读。七、运行与调试的推荐工作流综合 README 与源码实现一个可复制的开发循环如下构建前置二进制按 zstd 标准的 make 流程构建programs/zstd以及zstdgrep、zstdless与tests/datagen如构建到非默认位置用--zstd/--datagen/--zstdgrep/--zstdless显式指定。先跑全量./run.py确认基线全绿输出PASSED all N tests!。调试单个用例./run.py --preserve --verbose basic/help.sh观察每个检查项check_exit、check_stderr、check_stdout的 PASS/FAIL 与差异信息需要时可到scratch/basic/help.sh/下查看保留的exit/stdout/stderr文件。更新期望输出确认新输出行为正确后用./run.py --set-exact-output重写失配的.exact文件注意审阅 diff避免固化错误行为。跨工具验证需要内存检查时用--exec-prefix valgrind -q需要跨架构模拟时换成qemu前缀。八、结语这套框架带给 CLI 测试的启示从这份 README 与其实现 run.py 可以看出zstd 的 CLI 测试框架有四个值得借鉴的设计点其一严格划清CLI 层与库层的测试边界让测试意图单一明确其二期望文件与测试脚本分离.exact/.glob/.ignore三种匹配粒度覆盖了从字节级到忽略级的全部断言需求...通配行则优雅地处理了行数不确定的输出其三套件级与用例级两层 setup/teardown在共享成本与隔离性之间取得平衡其四通过符号链接与$PATH前置构造多命令形态与透明执行前缀让同一套测试天然支持valgrind、qemu等外部工具注入。理解这套机制后你不仅可以为 zstd 的 CLI 行为编写高质量回归测试也能为其他命令行工具的测试基础设施设计提供直接参考。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表