- 首发:2023-05-25 17:13:13
- 教程
- 5029
测试覆盖率是衡量软件测试质量的一个重要指标,它表示在运行测试用例时,被执行到的代码占总代码数的百分比。通过测试覆盖率,我们可以评估测试集对源码的覆盖程度及潜在漏洞。
本文将介绍两种使用LLVM实现C++测试覆盖率的方法:
- 方案1 使用lcov和llvm-cov将gcda文件转换为lcov.info文件。
- 方案2 使用grcov。
方案1:使用lcov和llvm-cov实现gcda -> gcov -> lcov.info
1. 环境准备
首先,确保已安装LLVM、Clang、lcov以及其相关工具。
各平台安装方法参考https://github.com/yi-ge/cpp-practice。
其中Windows平台安装lcov:
choco install lcov
然后执行lcov命令的时候需要手动使用perf
执行安装目录下bin/lcov
程序。
也可以在WSL2
或者其他可以执行Shell脚本的类 Unix 终端(例如Git Bash、Cygwin等)安装lcov
。
2. 编译代码与生成.gcda文件
在这里,我们提供两种方法来生成.gcda文件:
方法一:
使用以下命令启用覆盖率信息收集,并编译你的C++项目:
clang++ -fprofile-instr-generate -fcoverage-mapping -o your_program your_source.cpp
其中-fprofile-instr-generate
和-fcoverage-mapping
选项用于激活覆盖率信息收集。
方法二:
在CMakeLists.txt文件中添加coverage相关参数(LLVM 8+),例如:
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -fno-inline -O0 -fprofile-arcs -ftest-coverage")
接下来按照普通流程编译和运行项目即可生成对应的.gcda文件。
3. 执行程序
接下来,运行编译好的可执行文件:
./your_program
运行完毕后,会在当前目录下生成.profraw
文件(方法一),或 .gcda
文件(方法二)。该文件记录了覆盖率原始数据。
4. 转换.profraw文件为.gcda文件(仅方法一)
如果使用方法一,请使用llvm-profdata
工具将.profraw
文件转换为.gcda
格式:
llvm-profdata merge -sparse default.profraw -o your_program.gcda
5. 使用llvm-cov生成.gcov文件
通过llvm-cov gcov
命令将.gcda
文件转换为.gcov
文件:
llvm-cov gcov -f -b your_source.gcda
6. 使用lcov生成lcov.info文件
现在我们已经得到了.gcov文件,接下来可以用lcov合并覆盖率数据并生成lcov.info
文件:
lcov --capture --directory . --output-file lcov.info
由于在基于llvm的项目中依次执行llvm-cov gcov
很麻烦,除了递归脚本外,还推荐另外一种方法,具体实现方案如下:
llvm-gcov.sh文件:
#!/bin/bash
uNames=$(uname -s)
osName=${uNames:0:4}
if [ "$osName" == "Darw" ]; then # Darwin
exec llvm-cov gcov "$@"
elif [ "$osName" == "Linu" ]; then # Linux
exec llvm-cov-16 gcov "$@"
elif [ "$osName" == "MING" ]; then # MINGW, windows, git-bash
exec llvm-cov-16 gcov "$@"
fi
llvm-gcov.bat文件:
@echo off
:DetectOS
if "%OS%" == "Windows_NT" (
set "osName=Win"
) else (
echo Unsupported platform: %OS%
exit /b 1
)
:RunLLVMCov
if "%osName%" == "Win" (
llvm-cov gcov %*
) else (
echo Unsupported platform: %osName%
exit /b 1
)
创建名为scripts的文件夹,并将提供的llvm-gcov.sh (macOS, Linux)或llvm-gcov.bat (Windows) 文件放入其中。这些脚本用于在不同平台上调用正确的llvm-cov工具。
参考以下bash脚本生成覆盖率测试文件lcov.info:
#!/bin/bash
# 使用bash shell执行此脚本
COVERAGE_FOLDER=coverage
COVERAGE_FILE=lcov.info
REPORT_FOLDER=report
# 删除已存在的覆盖率文件夹,然后创建一个新的覆盖率文件夹
rm -rf ${COVERAGE_FOLDER}
mkdir ${COVERAGE_FOLDER}
# 获取操作系统名称并截取前四个字符
uNames=$(uname -s)
osName=${uNames:0:4}
# 删除与当前单元测试无关的.gcda文件
# find build -name "*.gcda" -not -path "*flip_columns_for_maximum_number_of_equal_rows*" -exec rm {} \;
# 设置临时覆盖率文件路径
TMP_COVERAGE_FILE=${COVERAGE_FOLDER}/${COVERAGE_FILE}_tmp
# 根据操作系统类型执行相应的lcov命令
if [ "$osName" == "Darw" ]; then # Darwin
echo "Mac"
lcov --gcov-tool $PWD/scripts/llvm-gcov.sh --rc lcov_branch_coverage=1 -c -d build -o ${TMP_COVERAGE_FILE}
elif [ "$osName" == "Linu" ]; then # Linux
echo "Linux"
lcov --gcov-tool $PWD/scripts/llvm-gcov.sh --rc lcov_branch_coverage=1 -c -d build -o ${TMP_COVERAGE_FILE}
elif [ "$osName" == "MING" ]; then # MINGW, windows, git-bash
echo "Windows"
lcov --gcov-tool $PWD/scripts/llvm-gcov.sh --rc lcov_branch_coverage=1 -c -d build -o ${TMP_COVERAGE_FILE}
fi
# 从临时覆盖率文件中移除包含目录
echo "从临时覆盖率文件中移除包含目录"
lcov --remove ${TMP_COVERAGE_FILE} -o ${TMP_COVERAGE_FILE} "*/include/*"
# 从临时覆盖率文件中提取src目录下的覆盖率数据,并保存到最终的覆盖率文件
lcov --rc lcov_branch_coverage=1 -e ${TMP_COVERAGE_FILE} "*src*" -o ${COVERAGE_FOLDER}/${COVERAGE_FILE}
# 生成覆盖率报告(注释掉,如需启用,请取消注释)
# genhtml --rc genhtml_branch_coverage=1 ${COVERAGE_FOLDER}/${COVERAGE_FILE} -o ${COVERAGE_FOLDER}/${REPORT_FOLDER}
在当前目录下执行此脚本,生成覆盖率测试文件lcov.info
。
7. 生成可视化报告
使用genhtml
命令将lcov.info
转换为HTML格式:
genhtml -o coverage_report --branch-coverage lcov.info
这将在coverage_report
目录下生成一个包含覆盖率详细信息的HTML报告。通过浏览器打开index.html
来查看报告。
方案2:使用grcov实现C++测试覆盖率
grcov是一个用Rust编写的覆盖率收集工具,可以直接分析.gcda
文件并生成覆盖率报告。
首先安装grcov。
如果已经配置好Rust环境以及对应的cargo工具:
cargo install grcov
否则可以直接从github仓库下载编译好的二进制文件,可以参考跨平台自动下载及安装脚本https://github.com/yi-ge/cpp-practice/blob/main/scripts/grcovDownloader.cpp。
然后使用以下命令运行测试并生成覆盖率报告:
LLVM_PROFILE_FILE="your_program-%p-%m.profraw" ./your_program
grcov . --binary-path ./ --source-dir . --output-path cover/lcov.info --output-type lcov
这将生成一个名为lcov.info
的文件,其中包含覆盖率数据。同样,你可以使用直接生成HTML可视化报告:
grcov . --binary-path ./ --source-dir . --output-path coverage/ --output-type html
总结
如上所述,在方案1中我们可以使用lcov和llvm-cov工具链生成覆盖率报告,或者通过添加coverage flags在CMakeLists.txt中直接生成.gcda文件。在方案2中,我们可以使用grcov直接分析.gcda文件并生成覆盖率报告。这两种方法都可以满足不同的需求,方案1更适用于熟悉lcov工具链的开发者,而方案2对于喜欢尝试新工具的开发者来说是一个很好的选择。
无论采用哪种方案,测试覆盖率是提高软件质量、降低潜在风险的关键指标,因此建议在项目开发过程中充分利用这些工具和方法,确保代码的健壮性和可靠性。
另外,grcov的优点是跨平台更方便。尤其是对于Windows平台,在不考虑wsl或者类Unix终端的情况下,安装perf来直接运行lcov程序非常容易出现问题。因此,更推荐使用方案2。
您好,感谢分享,想讨论一个问题。我使用了方法二,为什么我使用指定版本的clang++版本(18.1.4)加上指定flag 比如: /clang+llvm-18.1.4-x86_64-linux-gnu-ubuntu-18.04/bin/clang++ -fno-inline -O0 -fprofile-arcs -ftest-coverage -o foo test.cc
为什么run foo会生成gcc '4.8' version的gcno和gcda文件?
Clang 并不修改这些文件的格式版本号,导致即使你使用的是 Clang 18.1.4,生成的文件仍会显示为与 GCC 4.8 兼容的格式。这是因为 .gcno 和 .gcda 文件格式的定义是由 GCC 工具链控制的,Clang 在生成时沿用了 GCC 的版本号标识。
Clang 并不修改这些文件的格式版本号,导致即使你使用的是 Clang 18.1.4,生成的文件仍会显示为与 GCC 4.8 兼容的格式。这是因为 .gcno 和 .gcda 文件格式的定义是由 GCC 工具链控制的,Clang 在生成时沿用了 GCC 的版本号标识。
感谢回复! Clang 在生成时沿用了 GCC 的版本号标识,我是不是可以理解为Clang 18.1.4生成时使用的就是GCC4.8,所以我后续使用gcc 9.4 gcov
就会有不兼容的问题
感谢回复! Clang 在生成时沿用了 GCC 的版本号标识,我是不是可以理解为Clang 18.1.4生成时使用的就是GCC4.8,所以我后续使用gcc 9.4
gcov
就会有不兼容的问题抱歉,这块我也不太清楚,尝试寻求AI的帮助吧。
我在这个过程中遇到了各种问题- -,现在在UDC core: g_serial: couldn't find an available UDC卡住了,请问大佬有什么解决方案吗,还是说我前置的设置就错了呢,> 这个需求很特殊。是可以的,但是比较困难,需要修改驱动配置。
好思路呀!!
关于hex编辑器,网上没找到特别好用的(小白没办法),最后在vscode上扩展一搜hex,第一个安装一下就可以用vscode进行hex编译了