|
ZeroErr
|
可选的插桩构建用于统计每个顶层 TEST_CASE 执行了哪些业务代码。普通构建不依赖 LLVM 覆盖率运行时。
已有 TEST_CASE 不用改写。首次使用 Clang 和 -DZEROERR_ENABLE_COVERAGE=ON 配置构建后:
这一条命令会运行测试、采集 profile、统计并打印覆盖比例、最慢的十个测试和覆盖相同的候选组。 每次在工作目录下创建独立的 zeroerr-coverage/run-<id>,保存 report.json、 report.summary.txt 和原始 profile,无需手动调用 LLVM 或 Python。 测试失败仍生成报告并返回失败状态;采集或分析错误返回 2。--list-test-cases 只列出测试。
CMake 在配置阶段查找 Python 3.9+ 和配套 LLVM 工具。默认统计接入项目源码根目录, 排除已执行测试所在源码文件、zeroerr 框架源码和头文件、third_party/_deps。 业务代码与测试在同一文件时,需明确选择该文件。显式 include 会取代自动排除规则; 用户指定的 exclude 始终生效:
项目可以添加方便复用的 CMake 目标:
之后运行 cmake --build build --target my_tests-coverage,即可完成构建、运行与统计。 该函数还支持 INCLUDE "正则"、EXCLUDE "正则" ...、ARGS "--testcase=parser.*" ...。 zeroerr 自身的目标为 unittest-coverage,显式选择 zeroerr 自身作为被测业务代码。
仓库内提供了可以直接运行的独立示例:
示例为独立业务库插桩,覆盖 sign 函数的所有分支结果,并识别两个正数输入测试的相同覆盖。
手动编译或单头文件模式下,把 scripts/coverage-report.py 放到可执行文件旁并命名为 zeroerr-coverage.py,或通过环境变量 ZEROERR_COVERAGE_SCRIPT 指定位置。 Python 3 和配套的 llvm-cov/llvm-profdata 需要在 PATH 中;Python 名称不同时设置 ZEROERR_COVERAGE_PYTHON。此模式默认源码根目录为当前目录。 也可直接运行启动器: python coverage-report.py --run ./my_tests --source-root . -- --testcase='parser.*'。 启动器支持用 --llvm-cov/--llvm-profdata 指定带版本号的工具。 该辅助脚本是外部依赖,移动二进制时需一起部署;原 checkout 可用时优先使用 CMake 配置的脚本位置。
使用与编译器配套的 llvm-profdata 和 llvm-cov,可通过 --llvm-profdata /path/to/llvm-profdata-21、--llvm-cov /path/to/llvm-cov-21 指定路径。统计脚本需要 Python 3.9+,支持带分支信息的 LLVM JSON 导出格式 2 和 3。 当前已在 Linux 的 Clang/LLVM 21 上验证,其他平台尚需验证。
应用项目链接 CMake 的 zeroerr target 后,测试目标会继承覆盖率编译、链接选项。 对于单独编译的业务库,还需在引入 zeroerr 子目录后调用 zeroerr_instrument_coverage(my_library)。已编译且未插桩的库无法采集。 覆盖率构建使用原子计数更新,避免工作线程丢失计数;所有工作线程必须在测试返回前结束。 当前后端面向单个可执行文件及静态链接代码,不采集具有独立插桩运行时的动态库。
单头文件方式需要在实现翻译单元定义 ZEROERR_ENABLE_COVERAGE,为被测代码添加 -fprofile-instr-generate -fcoverage-mapping -fprofile-update=atomic, 链接时添加 -fprofile-instr-generate。覆盖率模式需要 C++17 的 std::filesystem。
每次 --coverage-output 必须指定**新目录**,已存在的目录会被拒绝且保持原样。 已有测试过滤选项继续生效。列出测试不会创建目录,跳过的测试不产生 profile。 失败测试和捕获的异常也会保存 profile 及结果;统计是独立步骤,可以在测试失败后执行。
执行器在每个顶层测试之前清零 LLVM 计数,测试结束后通过 LLVM profile buffer API 保存快照。嵌套 SUB_CASE、fixture 构造和析构归入父测试;测试前的全局初始化不计入。 边界内执行的框架代码也可能出现在 profile 中,应通过业务源码范围过滤。 目前不声称提供独立子用例归属。不要在同一进程内并发执行多个测试,也不要让后台工作跨越测试边界。 fork 子进程及外部子进程的覆盖率不在采集范围内。
目录包含 manifest.jsonl(名称、源码位置、耗时、结果、profile 文件名)和每个测试的 .profraw 文件。完成标记用于识别中断或崩溃的不完整采集。 采集 I/O 失败返回退出码 2;未启用覆盖率的构建收到采集选项也返回 2。 LLVM 可能在退出时另外生成普通整体 profile,本统计器不会使用它。
离线分析时 --include 为必填的源码路径正则,路径统一使用正斜杠。可重复传入 --exclude REGEX。 比较业务覆盖时,应排除测试实现、断言辅助函数、第三方代码。 没有匹配的插桩代码、采集未完成、LLVM 诊断或缺少分支数据都会报错。
JSON 报告通过共享源码特征字典和每测试特征 ID 集合,分别保存三个指标:
每项指标提供覆盖数、总数,以及相对于本次采集集合的逐测试独有覆盖数。 总数来自可执行文件中匹配的覆盖映射,包括所有测试均未命中的映射;未进入二进制的代码不计入。 行、条件、MC/DC、路径明确标为未采集。耗时来自插桩构建中的测试执行,不包含 profile 序列化, 不能当作普通构建的性能基准。
identical_coverage_candidates 将三个指标的覆盖集合均相同的成功测试分组,空集合不参与。 独有覆盖计算包含所有已采集测试,包括失败测试。 覆盖相同不代表输入、断言或缺陷检测能力等价;不会自动删除或合并测试。 分组采用覆盖签名,避免所有测试两两比较。
必须使用**采集时的原始可执行文件**分析,并将 profile 与构建一起保存。 报告记录分析时二进制的 SHA-256,但如果二进制在分析前已被替换,无法据此证明它就是采集时的版本。 特征 ID 仅在单份报告内有效,不可跨构建直接比较。延后分析时建议同时归档二进制。 原始 profile 空间随测试数与插桩二进制 profile 大小的乘积增长,当前离线分析逐测试调用 LLVM 工具。 建议按需诊断采集,不在每次编辑后运行。
参考 Clang 源码覆盖率 和 llvm-cov export。