使用 gcov/lcov/gcovr 在 Android APK 下获取代码覆盖率
Posted 芥末的无奈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 gcov/lcov/gcovr 在 Android APK 下获取代码覆盖率相关的知识,希望对你有一定的参考价值。
文章目录
前言
C/C++ 代码覆盖率常使用 gcov/lcov/gcovr 等工具生成,它们用起来非常方便,根据下面的参考文档你也能快速搭建起测试环境:
简单来说,你需要:
- 安装 lcov
- 在 c/c++ 编译选项中添加 -fprofile-arcs -ftest-coverage,编译可执行文件
- 运行可执行文件
- 使用 lcov 收集代码覆盖率信息
步骤 2 编译完成后,在 .o 目录下会生成 .gcno 文件;步骤 3 运行可执行文件后,会生成 .gcda 文件;最后配合源码文件,gcov 等工具就能生成报告了。
由于 lcov 和 gcovr 都是基于 gcov 二次开发的工具,因此最重要的是理解 gcov 工具是如何工作的。接下来将详细说明 gcov 的工作流程。
1. gcno 和 gcda 生成的位置
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 都存放在同一个目录下。
2. 三要素
想要使用 gcvo 获取代码覆盖率信息需要三要素:
- gcno
- gcda
- 源码
其中,gcno 和 gcda 应该一一对应,在同一个目录层级下,例如 a.cpp.gcno 和 a.cpp.gcda 要在同一目录下。源码的位置则在编译时就被写入到了 gcno 中,通过 strings
命令,你可以发现源码的位置:
很好,这样所有信息都能被串起来了,总结下:
- gcno 在编译时生成,生成的位置与 .o 文件同级;通过
strings
命令,可以在 gcno 找到源码的文件路径 - gcda 在执行完程序后生成,生成的位置被注入到了二进制文件中,通过
strings
命令,可以在二进制文件中找到 gcda 生成的路径;通过设置环境变量GCOV_PREFIX
和GCOV_PREFIX_STRIP
我们可以较为方便的修改 gcda 生成的位置 - gcno 和 gcda 需要一一匹配,gcov 命令通过这两个文件的信息,计算得到代码覆盖率,生成 reports
3. Android 下获取覆盖率
有了上面的铺垫,你已经知道了 gcov 的工作原理,想要在 android apk 下获取 gcov 的代码覆盖率,流程上大体相同:
- native c/c++ 编译中添加 coverage 相关的编译选项
- 本地编译 apk 或者库文件,在中间产物的目录中,得到 c/c++ 的 gcno 文件
- 指定
GCOV_PREFIX
和GCOV_PREFIX_STRIP
指定 gcda 在手机上生成的位置 - 从手机上将 gcda 文件 pull 下来到本机
- 使用 gcov/lcov/gcovr 等工具生成覆盖率报告
具体的 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 的。
4. 总结
本文介绍了 gcov 生成代码覆盖率的基本流程和原理,重点说明了 gcno、gcda 和源码文件之间的关系;通过 strings
命令可以查询 gcno 中指定的源码位置,以及 gcda 的生成位置;通过对 GCOV_PREFIX
和 GCOV_PREFIX_STRIP
的设置,可以指定 gcda 生成的位置。最后,通过 AndroidNativeCodeCoverageExample 具体示例,详细说明了如何运行 android apk 并获取 c/c++ 的代码覆盖率。
5. 参考
- 使用 Github Actions 和 Codecov 监控 C/C++ 仓库的代码覆盖率
- 关于代码覆盖lcov的使用
- LCOV Code Coverage
- gcov lcov进行 android apk项目的 code coverage 代码覆盖率检测
- AndroidNativeCodeCoverageExample
以上是关于使用 gcov/lcov/gcovr 在 Android APK 下获取代码覆盖率的主要内容,如果未能解决你的问题,请参考以下文章
ANDR-PERF tryGetService 失败; SELinux
java.lang.NoSuchMethodError public default void android.content.ServiceConnection.onBindingDied(andr
java [Actividad Dinamica] Crear una actividad dinamica en Android enviando la clase por String #Andr