C/C++ 代码覆盖率常使用 gcov/lcov/gcovr 等工具生成,它们用起来非常方便,根据下面的参考文档你也能快速搭建起测试环境:
简单来说,你需要:
步骤 2 编译完成后,在 .o 目录下会生成 .gcno 文件;步骤 3 运行可执行文件后,会生成 .gcda 文件;最后配合源码文件,gcov 等工具就能生成报告了。
由于 lcov 和 gcovr 都是基于 gcov 二次开发的工具,因此最重要的是理解 gcov 工具是如何工作的。接下来将详细说明 gcov 的工作流程。
c/c++ 代码编译时生成 gcno ,gcno 生成的位置与 .o 位置保持一致,以 关于代码覆盖lcov的使用 代码为例,在笔者电脑中,gcno 的位置如下图,可以看到一个 .o 就有一个 .gcno 文件
gcda 文件在执行程序后生成,那么它的生成路径在哪呢?其实这个位置已经在被写到执行程序中,通过 strings
命令,你可以查看二进制文件中字符串内容,以上述例子中的 main
程序为例,你可以看到两行关于 .gcda 的信息:
啊哈,你看,gcda 的位置居然是写死的。这可不妙,因为我们想要在 Android 下拿到 gcda 文件,可 android 手机上可没有这个固定路径,会导致 gcda 文件无法生成。
幸运的是,我们可以通过设置 GCOV_PREFIX
环境变量来修改 gcda 的生成路径。例如,指定 gcda 生成在 /Users/user/Downloads/gcov_test
目录下,接着运行程序:
export GCOV_PREFIX=/Users/user/Downloads/gcov_test
./main
运行结束后,在 /Users/user/Downloads/gcov_test
就生成了 main.cpp.gcda 和 a.cpp.gcda。但 gcda 仍然保持的完整路径,也就是实际路径为:
/Users/user/Downloads/gcov_test/Users/user/Documents/develop/lcov_test/cmake-build-debug/CMakeFiles/main.dir/main.cpp.gcda
/Users/user/Downloads/gcov_test/Users/user/Documents/develop/lcov_test/cmake-build-debug/CMakeFiles/main.dir/src/a.cpp.gcda
好家伙,这路径也太长了。幸好,我们可以指定 GCOV_PREFIX_STRIP
来裁剪绝对路径中的级数,例如设置成 export GCOV_PREFIX_STRIP=8
可以得到:
/Users/user/Downloads/gcov_test/main.cpp.gcda
/Users/user/Downloads/gcov_test/src/a.cpp.gcda
当然,我们还可以将 GCOV_PREFIX_STRIP
设置成例如 100 这样的非常大的数,让所有 gcda 都存放在同一个目录下。
想要使用 gcvo 获取代码覆盖率信息需要三要素:
其中,gcno 和 gcda 应该一一对应,在同一个目录层级下,例如 a.cpp.gcno 和 a.cpp.gcda 要在同一目录下。源码的位置则在编译时就被写入到了 gcno 中,通过 strings
命令,你可以发现源码的位置:
很好,这样所有信息都能被串起来了,总结下:
strings
命令,可以在 gcno 找到源码的文件路径strings
命令,可以在二进制文件中找到 gcda 生成的路径;通过设置环境变量 GCOV_PREFIX
和 GCOV_PREFIX_STRIP
我们可以较为方便的修改 gcda 生成的位置有了上面的铺垫,你已经知道了 gcov 的工作原理,想要在 android apk 下获取 gcov 的代码覆盖率,流程上大体相同:
GCOV_PREFIX
和 GCOV_PREFIX_STRIP
指定 gcda 在手机上生成的位置具体的 demo 你可以在 AndroidNativeCodeCoverageExample 找到。在 Android apk 上拿到代码覆盖率的具体步骤如下:
在 app/CMakeLists.txt 中 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
设置编译选项
在 Android Studio 编译 apk 后,可以在编译目录找到 *.gcno 文件
通过 strings
命令查看源码位置,发现是一个相对路径,请注意这个相对路径,如果你执行 gcov 命令却提示找不到源码时,请确保 gcno/gcda 文件与源码的是配对的。
此外,通过 strings
命令查看生成的 .o 文件,可以查询到 *.gcda 生成的目录:
在 android 代码中指定 GCOV_PREFIX
和 GCOV_PREFIX_STRIP
,确保 GCOV_PREFIX
是有权限写入的。在示例中 GCOV_PREFIX
被指定为应用缓存目录;GCOV_PREFIX_STRIP=100
裁剪掉掉前 100 个目录,我们让所有 gcda 文件都在同一级目录下,方便处理。
在 apk 的退出时,调用需要调用 __gcov_flush
让 gcov 生成 gcda 文件。这篇文章 解释了为什么要这么做,我这里采用了一种更简单的方式,重载 onDestroy
在退出时就去调用 __gcov_flush
如果一切顺利,你可以在手机上找到这些 *.gcda 文件:
将 *.gcda 文件全部 pull 下来,存放在一个特殊位置的文件夹,在这个文件夹中,使得 gcno 纪录的源码相对位置是匹配的。在我的电脑上,这个路径为 /Users/user/Documents/develop/NativeCodeCoverageTest/app/.cxx/Debug/3g1m3d1m/gcov
,在这里存放 gcno 文件,可以使得 ../../../../src/main/cpp/a.cpp
刚好是匹配的。同时,将编译生成的 gcno 也 copy 到该目录下:
使用 gcov/lcov/gcorv 等工具生成 report。这里以 gcovr 为例:
gcovr -v -r /Users/user/Documents/develop/NativeCodeCoverageTest/app/src .
如果 gcovr 无法正确生成报告,可以加上 -v
选项看看 debug 信息;在或者先使用 gcov --dump
命令试试,毕竟 gcovr 是调用 gcov 的。
本文介绍了 gcov 生成代码覆盖率的基本流程和原理,重点说明了 gcno、gcda 和源码文件之间的关系;通过 strings
命令可以查询 gcno 中指定的源码位置,以及 gcda 的生成位置;通过对 GCOV_PREFIX
和 GCOV_PREFIX_STRIP
的设置,可以指定 gcda 生成的位置。最后,通过 AndroidNativeCodeCoverageExample 具体示例,详细说明了如何运行 android apk 并获取 c/c++ 的代码覆盖率。