轶哥

📚 Having fun with AI Agent. Always learning.

C++ LLVM生成测试覆盖率
  •   更新:2023-05-25 17:13:13
  •   首发: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。

打赏
交流区(3)
六个蛋挞

您好,感谢分享,想讨论一个问题。我使用了方法二,为什么我使用指定版本的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文件?

2024年9月25日 20:05回复
轶哥

Clang 并不修改这些文件的格式版本号,导致即使你使用的是 Clang 18.1.4,生成的文件仍会显示为与 GCC 4.8 兼容的格式。这是因为 .gcno 和 .gcda 文件格式的定义是由 GCC 工具链控制的,Clang 在生成时沿用了 GCC 的版本号标识。

2024年9月26日 00:14回复
六个蛋挞

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就会有不兼容的问题

2024年9月30日 03:23回复
尚未登陆
发布
  上一篇 (在Windows平台上使用C++执行外部命令的两种方法)
下一篇 (Rust单元测试完成后自动执行覆盖率测试)  

评论回复提醒