lcov 使用方式及参数说明
lcov 是 gcov 覆盖率数据的收集和汇总工具。它读取编译器生成的 .gcda、.gcno 数据,生成 .info 文件。.info 文件再交给 genhtml 生成 HTML 报告。
覆盖率数据链路
带覆盖率选项编译
-> 运行 GoogleTest
-> 生成 .gcda
-> lcov 收集 .info
-> lcov 过滤 .info
-> genhtml 生成 HTML
编译选项
GCC 常用:
g++ --coverage -O0 -g src.cpp test.cpp -lgtest -lgtest_main -pthread -o unit_tests
CMake 中常用:
add_compile_options(--coverage -O0 -g)
add_link_options(--coverage)
--coverage 等价于编译和链接阶段添加 gcov 相关选项。覆盖率构建通常关闭优化或使用低优化等级,因为优化可能改变源码行与机器指令的对应关系。
基本命令
lcov --capture --directory build --output-file coverage.info
lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file coverage.filtered.info
genhtml coverage.filtered.info --output-directory coverage-html
常用参数
| 参数 | 说明 |
|---|---|
--capture |
收集覆盖率数据 |
--directory DIR |
指定搜索 .gcda、.gcno 的目录 |
--output-file FILE |
指定输出 .info 文件 |
--test-name NAME |
为 tracefile 设置测试名称,合并多次测试数据时更容易区分 |
--base-directory DIR |
指定源码基准目录,源码路径解析异常时常用 |
--build-directory DIR |
指定构建目录,源码和构建目录分离时有用 |
--gcov-tool TOOL |
指定 gcov 工具,例如 gcc-13-gcov、arm-none-eabi-gcov |
--remove INFO PATTERN... |
从覆盖率数据中删除匹配路径 |
--extract INFO PATTERN... |
只保留匹配路径 |
--zerocounters |
清零已有计数器 |
--initial |
捕获初始覆盖率,用于包含未执行文件 |
--add-tracefile FILE |
合并多个 .info 文件 |
--list INFO |
在终端列出覆盖率摘要 |
--summary INFO |
输出 tracefile 的摘要信息 |
--branch-coverage |
捕获或处理分支覆盖率,新版本 lcov 常用 |
--no-external |
捕获时排除外部源码文件 |
--external |
捕获时包含外部源码文件 |
--no-recursion |
不递归扫描子目录 |
--follow |
扫描时跟随符号链接 |
--checksum |
tracefile 中记录源码校验信息 |
--no-checksum |
不记录源码校验信息 |
--quiet |
减少输出 |
--version |
输出版本 |
--ignore-errors TYPE |
忽略指定类型错误 |
--rc KEY=VALUE |
临时覆盖 lcov 配置项,例如启用分支覆盖率 |
参数分类
| 目的 | 常用参数 |
|---|---|
| 收集数据 | --capture、--directory、--output-file |
| 处理路径 | --base-directory、--build-directory、--no-external |
| 指定工具链 | --gcov-tool |
| 清理计数 | --zerocounters |
| 包含未执行文件 | --initial |
| 合并数据 | --add-tracefile |
| 过滤数据 | --remove、--extract |
| 查看摘要 | --list、--summary |
| 分支覆盖率 | --branch-coverage 或 --rc lcov_branch_coverage=1 |
推荐流程
lcov --directory build-coverage --zerocounters
ctest --test-dir build-coverage --output-on-failure
lcov --capture --directory build-coverage --output-file coverage.raw.info
lcov --remove coverage.raw.info '/usr/*' '*/_deps/*' '*/tests/*' --output-file coverage.info
lcov --list coverage.info
先清零计数器可以避免之前运行留下的数据污染结果。收集后过滤系统头文件、第三方依赖和测试代码,报告才更接近产品代码覆盖率。
指定 gcov 工具
当系统里存在多个 GCC 版本,或使用 MinGW、交叉编译工具链、Clang gcov 兼容模式时,应显式指定 --gcov-tool。
lcov --capture \
--directory build-coverage \
--gcov-tool gcc-13-gcov \
--output-file coverage.info
交叉编译示例:
lcov --capture \
--directory build-coverage \
--gcov-tool /opt/toolchain/bin/arm-none-eabi-gcov \
--output-file coverage.info
Clang 示例:
lcov --capture \
--directory build-coverage \
--gcov-tool "llvm-cov gcov" \
--output-file coverage.info
gcov 版本应和生成 .gcno、.gcda 的编译器匹配,否则可能出现格式不兼容、覆盖率为空、行号异常等问题。
分支覆盖率
不同 lcov 版本启用分支覆盖率的写法略有差异。常见写法如下:
lcov --capture \
--directory build-coverage \
--output-file coverage.info \
--branch-coverage
兼容写法:
lcov --capture \
--directory build-coverage \
--output-file coverage.info \
--rc lcov_branch_coverage=1
生成 HTML 时,genhtml 也要启用分支覆盖率显示:
genhtml coverage.info --branch-coverage --output-directory coverage-html
分支覆盖率比行覆盖率更严格,适合检查 if、switch、三目运算、短路逻辑等条件是否两侧都被测试。
初始覆盖率
如果只捕获运行后的数据,未被执行且没有产生 .gcda 的文件可能不出现在报告中。可以先捕获初始覆盖率:
lcov --capture --initial --directory build-coverage --output-file coverage.base.info
ctest --test-dir build-coverage --output-on-failure
lcov --capture --directory build-coverage --output-file coverage.test.info
lcov --add-tracefile coverage.base.info --add-tracefile coverage.test.info --output-file coverage.total.info
这种方式能更准确反映“完全没有被测试触达”的文件。
多测试目标合并
大型工程中可能有多个测试目标,例如 core_tests、io_tests、net_tests。可以分别运行并收集,再合并 tracefile。
lcov --directory build-coverage --zerocounters
ctest --test-dir build-coverage -R core_tests --output-on-failure
lcov --capture --directory build-coverage --test-name core --output-file core.info
lcov --directory build-coverage --zerocounters
ctest --test-dir build-coverage -R io_tests --output-on-failure
lcov --capture --directory build-coverage --test-name io --output-file io.info
lcov --add-tracefile core.info \
--add-tracefile io.info \
--output-file coverage.total.info
如果不清零计数器,第二次收集可能包含第一次测试留下的数据。是否清零取决于你希望得到“每组测试单独覆盖率”还是“累计覆盖率”。
路径过滤示例
| 目标 | 示例 |
|---|---|
| 删除系统头文件 | '/usr/*' |
| 删除第三方依赖 | '*/third_party/*'、'*/_deps/*' |
| 删除测试代码 | '*/tests/*' |
| 只保留源码目录 | lcov --extract coverage.info '*/src/*' |
保留产品代码的常见写法
先删除明显无关路径:
lcov --remove coverage.raw.info \
'/usr/*' \
'*/_deps/*' \
'*/third_party/*' \
'*/tests/*' \
--output-file coverage.no_external.info
再只保留产品源码:
lcov --extract coverage.no_external.info \
'*/src/*' \
'*/include/*' \
--output-file coverage.info
这种“两步过滤”比只用 --remove 更稳,因为项目结构变化时,最终报告仍只会保留明确指定的源码目录。
源码路径异常处理
如果报告中出现源码找不到、路径过长、路径前缀不对,可以尝试:
lcov --capture \
--directory build-coverage \
--base-directory . \
--output-file coverage.info
构建目录和源码目录完全分离时,可以加上构建目录:
lcov --capture \
--directory build-coverage \
--base-directory . \
--build-directory build-coverage \
--output-file coverage.info
--base-directory 影响源码路径解析,--directory 影响 .gcda、.gcno 搜索位置。二者不是同一个概念。
忽略错误
有些工程在迁移阶段会遇到部分源码缺失、第三方文件路径异常、旧 .gcda 数据残留等问题。可以临时使用:
lcov --capture \
--directory build-coverage \
--ignore-errors source \
--output-file coverage.info
--ignore-errors 应谨慎使用。它适合让 CI 临时继续运行,但长期看应修复根因,否则覆盖率报告可能掩盖真实问题。
常见问题
| 问题 | 可能原因 | 处理 |
|---|---|---|
没有 .gcda |
测试没有运行,或程序异常退出 | 先确认测试可执行文件正常结束 |
| 覆盖率包含系统头 | 没有过滤路径 | 使用 --remove |
| 行号对应异常 | 编译优化影响 | 使用 -O0 -g |
| 报告缺少未执行文件 | 没有初始捕获 | 使用 --initial |
| 多次运行数据叠加 | 计数器未清零 | 使用 --zerocounters |
| 覆盖率为空 | gcov 工具链不匹配 | 使用 --gcov-tool 指定匹配版本 |
| 分支覆盖率缺失 | lcov 或 genhtml 未启用分支参数 | 添加 --branch-coverage 或对应 --rc |
| 路径不对 | 源码目录和构建目录分离 | 尝试 --base-directory、--build-directory |