zstd 回归测试(Regression tests)完全指南:从 nightly 守护到 results.csv 重建
【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo
zstd 的回归测试框架通过固定数据集、固定压缩配置与多种压缩 API 组合,持续监控压缩后的总字节数,防止任何改动悄悄劣化压缩率。本文以 src/third_party/zstandard/zstd/tests/regression/README.md 为核心,结合框架源码逐层拆解其数据缓存、配置矩阵、方法(Method)抽象与结果比对机制,并给出完整的本地重建results.csv实战步骤,帮助读者在遇到 nightly 失败时快速判断"是真回归还是基线更新"。
一、回归测试在做什么
回归测试(regression tests)的核心思想非常朴素:用大量固定场景反复运行 zstd,并确保压缩结果的体积不发生变化。也就是说,它守护的是 zstd 的压缩率(compression ratio)——这是压缩库最宝贵的资产之一。任何一个提交如果让某个场景的压缩输出变大,都可能意味着压缩率回退,需要开发者立刻察觉并定位。
从 test.c 的main()可以看出,整个测试流程由四个阶段组成:
parse_args()解析命令行参数;are_names_bad()校验各模块(method / data / config)的名字里不能包含逗号(因为逗号会破坏 CSV 格式),并顺便统计最长名字以对齐输出列宽;method_set_zstdcli()设置 zstd CLI 路径、data_init()初始化(必要时下载)测试数据集;run_all()遍历所有method × data × config组合,把每个组合的压缩总字节数写入输出文件。
每个(数据、配置、方法)三元组的唯一产物是Total compressed size(压缩后的总字节数),逐行追加写进results.csv,同时实时回显到 stderr。这就是整个回归测试的输出格式——一张巨大的矩阵表。
二、测试怎么跑:CI 与本地两种途径
2.1 每天深夜的 CircleCI 巡检
README 明确指出,这些测试每晚由 CircleCI 自动执行。这意味着:
- 任何合并进主干的改动,第二天一早就会被这套矩阵跑一遍;
- 如果 job 失败,先阅读 job 打印的 diff,判断这次改动到底是不是压缩率回退;
- 如果确认一切正常(比如有意识地调整了默认参数、更新了数据集或修复了 bug 改变了输出),可以下载
results.csv构建产物,并把新结果作为新的基线提交; - 或者完全在本机按照下面的流程自行重建
results.csv。
2.2 本地重建 results.csv(README 原始步骤)
README 给出了从 zstd 仓库根目录开始的标准流程(在本仓库中,zstd 仓库根目录即src/third_party/zstandard/zstd/):
# 构建 zstd 二进制 make clean make -j zstd # 构建回归测试二进制 cd tests/regression make clean make -j test # 运行回归测试 ./test --cache>for (size_t method = 0; methods[method] != NULL; ++method) for (size_t datum = 0; data[datum] != NULL; ++datum) for (size_t config = 0; configs[config] != NULL; ++config) // 跳过不适用的组合,然后压缩并记录 total_size3.1 Data:被压缩的固定数据集
data.c 中定义了 4 个数据对象:
| 数据名 | 类型 | 是否有字典 |
|---|---|---|
silesia | 目录(dir) | 否 |
silesia.tar | 文件(file) | 否 |
github | 目录(dir) | 是(github.dict) |
github.tar | 文件(file) | 是(github.dict) |
每个数据对象都带有一个固定的 XXH64 校验和。这些数据不是随测试一起提交的,而是在data_init()首次运行时由 libcurl 自动下载(源码通过curl_easy_perform下载、popen("zstd -dc | tar -x -C ...")现场解压还原,下载过程中curl_write回调会边写文件边累积 XXH64 哈希),下载完成后与期望的xxhash64比对,不一致即报错——这保证了全世界任何机器上跑出来的结果都基于逐字节完全相同的输入数据。
缓存的复用依赖缓存目录里的STAMP文件:stamp_check()用所有数据的名字、哈希、类型计算一个汇总 XXH64 哈希,与STAMP中记录的值比对;匹配则直接复用已有数据(输出 "stamp matches: reusing the cached data"),不匹配或不存在则重新下载并写新STAMP。这是"可以反复重跑且结果可复现"的关键机制。
3.2 Config:压缩配置矩阵
config.c 用 C 宏批量生成了覆盖范围极广的配置,每个 config 同时携带面向 CLI 的cli_args和面向高级 API 的param_values(ZSTD_cParameter → value键值对)。主要配置族包括:
- 快速档位:
FAST_LEVEL(5/3/1),即level -5 / -3 / -1,对应 CLI 参数--fast=N; - 标准档位:
LEVEL(0/1/3/4/5/6/7/9/13/16/19),其中 0 表示默认档;每个档位都衍生出 5 个带字典的变体:with dict:普通字典;with dict dms:ZSTD_c_enableDedicatedDictSearch=0+ZSTD_dictForceAttach(强制 Attach 字典、禁用专用字典搜索);with dict dds:ZSTD_c_enableDedicatedDictSearch=1+ZSTD_dictForceAttach(启用专用字典搜索);with dict copy:ZSTD_dictForceCopy(强制复制字典);with dict load:ZSTD_dictForceLoad(强制加载字典);
- 行哈希(row hash)变体:
ROW_LEVEL(5/7/11/12, 1/2),分别强制启用/禁用ZSTD_c_useRowMatchFinder,覆盖 16/32/64 行条目(row entries)三种形态,用于守护行哈希匹配器这个较新的实现路径; - 特殊场景:
no source size(不声明源大小)、long distance mode(--long)、multithreaded(-T2)、multithreaded long distance mode(-T2 --long)、small window log(wlog=10)、small hash log、small chain log、explicit params(显式指定 strategy/wlog/hlog/clog/tlen)、uncompressed literals(--no-compress-literals)、uncompressed literals optimal(-19组合)、huffman literals、multithreaded with advanced params。
具体档位清单定义在 levels.h 中,注释说明了选取原则:"所选档位要能触发每种 strategy 在各种源大小下的路径,外加若干快速档和默认档"。
其中config_skip_data()负责矩阵剪枝:凡是use_dictionary=1而数据没有字典的(config, data)组合都会被跳过,避免无意义的空跑。
3.3 Method:压缩 API 的多种调用方式
method.c 定义了 10 种 method,本质是zstd 各种压缩 API 的调用入口,它们都应产生一致或可预期的输出体积:
| Method 名 | 对应实现 | 覆盖的 API |
|---|---|---|
compress simple | simple_compress | 单次ZSTD_compress/ZSTD_decompress |
compress cctx | compress_cctx_compress | ZSTD_compressCCtx/ZSTD_compress_usingDict/ZSTD_compress_advanced |
zstdcli | cli_compress | 直接 fork 出'<zstd>' -cqr <args> ...子进程 |
advanced one pass | advanced_one_pass_compress | ZSTD_compress2(一次压缩) |
advanced one pass small out | advanced_one_pass_compress_small_output | 输出缓冲区容量刻意少 1 字节的ZSTD_compress2,考验动态扩容路径 |
advanced streaming | advanced_streaming_compress | ZSTD_compressStream2流式压缩 |
old streaming | old_streaming_compress | 旧版ZSTD_compressStream/ZSTD_endStream |
old streaming advanced | old_streaming_compress_advanced | 旧版高级参数流式接口 |
old streaming cdict | old_streaming_compress_cdict | ZSTD_createCDict+ 流式字典压缩 |
old streaming advanced cdict | old_streaming_compress_cdict_advanced | ZSTD_createCDict_advanced+ 流式高级压缩 |
method_t是一个典型的"接口 + 状态"结构:create()负责做只跟数据有关的昂贵准备工作(比如把整个数据集读进内存、预分配ZSTD_compressBound大小的输出缓冲),compress()逐 config 执行压缩并返回result_t,destroy()释放状态。buffer_state_t通过container_of宏把各方法共用的缓冲区挂在基类method_state_t之下,实现缓冲区在多次压缩调用间的复用。
特别值得说明的是"压缩后解压回环校验":多数方法(simple、cctx、advanced、old streaming 等)在记录压缩体积之前,都会解压并逐字节比对输入输出(data_buffer_compare)。一旦回环不一致,会返回round trip error。也就是说,这套框架不只守护压缩率,还守护"压出来的东西必须能完好解回去"的正确性。
result_t的结果状态在 result.c 中可查:okay、skip(组合被有意跳过,如 CLI 方法不支持 advanced-only 配置)、system error、compression error、decompression error、round trip error。这些错误字符串会直接以文本形式写入 CSV 的 "Total compressed size" 列,因此查看results.csv时也能一眼看到哪些组合是 skip 或报错。
四、results.csv:一张 1480 行的基线矩阵
仓库中已提交的基线文件 results.csv 共 1480 行,表头为:
Data, Config, Method, Total compressed size从开头部分可以看到典型行:
silesia.tar, level -5, compress simple, 6861055 silesia.tar, level 3, compress simple, 4854086 silesia.tar, level 19, compress simple, 4265911 github.tar, level 19, compress simple, 32276规律一目了然:同一数据同一方法下,压缩级别越高(-5 → 3 → 19),总压缩体积越小(6861055 → 4854086 → 4265911),这正是 zstd 压缩率与速度权衡曲线的直接体现。这张表就是压缩率回归的黄金基线——任何改动如果让这些数字普遍变大,就需要警惕。
五、遇到失败怎么处理
当 CI 报错时,按 README 的指引分两步走:
- 读 diff:对比 job 打印出的新旧结果差异。如果只有个别组合的数字变化,先判断是否与本次改动有关(例如动了字典策略、调整了默认参数、修复了特定 level 的匹配器);
- 确认无误后更新基线:下载
results.csvartifact,或者按第三节的本地流程重建,git diff检查无异常后连同代码改动一起提交 PR。
这里需要特别强调提交纪律:"提交新 results.csv"永远不应该是掩盖回归的借口。只有当你确信"输出体积变化是本次改动的预期结果"(例如新增了压缩级别、数据集的 XXH64 哈希因为换数据而更新、修复 bug 使某个场景的压缩率提升)时,才应更新基线;否则数字变大就意味着压缩率回退,应当回头修复实现而不是更新基线。
六、构建与运行前提
Makefile 揭示了编译期依赖:
- 依赖 libcurl(通过
curl-config --cflags / --libs自动探测),用于下载测试数据; - 依赖 zstd 库本体与
xxhash、util等内部模块,编译时通过-I$(PROGDIR) -I$(LIBDIR)指向../../programs与../../lib; test目标会先构建libzstd.a-mt再静态链接出最终二进制。
因此本地重建前需要保证:系统安装了 curl 开发库;zstd 源码树完整(本仓库内位于src/third_party/zstandard/zstd/);首次运行有网络权限下载回归数据集(之后由STAMP缓存复用)。整个流程的产物(test二进制与results.csv)都在src/third_party/zstandard/zstd/tests/regression/目录内生成,方便git diff直接审阅。
七、小结
zstd 的回归测试框架用"固定数据 × 丰富配置 × 多 API 调用方式"构成一张高覆盖矩阵,用Total compressed size这一个简单到极致的指标守护压缩率,并用results.csv作为可提交、可 diff、可审计的基线。理解data.c的缓存与校验、config.c的宏展开配置矩阵、method.c的 API 覆盖策略之后,无论是本地重建基线、定位 CI 失败,还是未来为 zstd 新增压缩路径时补齐回归覆盖,都有章可循。若想继续深入,可依次阅读 data.h、config.h 与 method.h 的接口注释,再对照 test.c 的主循环把整条链路串起来。
【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考