• 使用 gcov/lcov/gcovr 在 Android APK 下获取代码覆盖率



    前言

    C/C++ 代码覆盖率常使用 gcov/lcov/gcovr 等工具生成,它们用起来非常方便,根据下面的参考文档你也能快速搭建起测试环境:

    简单来说,你需要:

    1. 安装 lcov
    2. 在 c/c++ 编译选项中添加 -fprofile-arcs -ftest-coverage,编译可执行文件
    3. 运行可执行文件
    4. 使用 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
    
    • 1
    • 2

    运行结束后,在 /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_PREFIXGCOV_PREFIX_STRIP 我们可以较为方便的修改 gcda 生成的位置
    • gcno 和 gcda 需要一一匹配,gcov 命令通过这两个文件的信息,计算得到代码覆盖率,生成 reports

    3. Android 下获取覆盖率

    有了上面的铺垫,你已经知道了 gcov 的工作原理,想要在 android apk 下获取 gcov 的代码覆盖率,流程上大体相同:

    1. native c/c++ 编译中添加 coverage 相关的编译选项
    2. 本地编译 apk 或者库文件,在中间产物的目录中,得到 c/c++ 的 gcno 文件
    3. 指定 GCOV_PREFIXGCOV_PREFIX_STRIP 指定 gcda 在手机上生成的位置
    4. 从手机上将 gcda 文件 pull 下来到本机
    5. 使用 gcov/lcov/gcovr 等工具生成覆盖率报告

    具体的 demo 你可以在 AndroidNativeCodeCoverageExample 找到。在 Android apk 上拿到代码覆盖率的具体步骤如下:

    1. app/CMakeLists.txtset(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") 设置编译选项

    2. 在 Android Studio 编译 apk 后,可以在编译目录找到 *.gcno 文件
      在这里插入图片描述
      通过 strings 命令查看源码位置,发现是一个相对路径,请注意这个相对路径,如果你执行 gcov 命令却提示找不到源码时,请确保 gcno/gcda 文件与源码的是配对的。
      在这里插入图片描述
      此外,通过 strings 命令查看生成的 .o 文件,可以查询到 *.gcda 生成的目录:
      在这里插入图片描述
      在这里插入图片描述

    3. 在 android 代码中指定 GCOV_PREFIXGCOV_PREFIX_STRIP,确保 GCOV_PREFIX 是有权限写入的。在示例中 GCOV_PREFIX 被指定为应用缓存目录;GCOV_PREFIX_STRIP=100 裁剪掉掉前 100 个目录,我们让所有 gcda 文件都在同一级目录下,方便处理。
      在这里插入图片描述

    4. 在 apk 的退出时,调用需要调用 __gcov_flush 让 gcov 生成 gcda 文件。这篇文章 解释了为什么要这么做,我这里采用了一种更简单的方式,重载 onDestroy 在退出时就去调用 __gcov_flush
      在这里插入图片描述
      如果一切顺利,你可以在手机上找到这些 *.gcda 文件:
      在这里插入图片描述

    5. 将 *.gcda 文件全部 pull 下来,存放在一个特殊位置的文件夹,在这个文件夹中,使得 gcno 纪录的源码相对位置是匹配的。在我的电脑上,这个路径为 /Users/user/Documents/develop/NativeCodeCoverageTest/app/.cxx/Debug/3g1m3d1m/gcov,在这里存放 gcno 文件,可以使得 ../../../../src/main/cpp/a.cpp 刚好是匹配的。同时,将编译生成的 gcno 也 copy 到该目录下:
      在这里插入图片描述

    6. 使用 gcov/lcov/gcorv 等工具生成 report。这里以 gcovr 为例:

    gcovr -v -r /Users/user/Documents/develop/NativeCodeCoverageTest/app/src .
    
    • 1

    在这里插入图片描述
    如果 gcovr 无法正确生成报告,可以加上 -v 选项看看 debug 信息;在或者先使用 gcov --dump 命令试试,毕竟 gcovr 是调用 gcov 的。


    4. 总结

    本文介绍了 gcov 生成代码覆盖率的基本流程和原理,重点说明了 gcno、gcda 和源码文件之间的关系;通过 strings 命令可以查询 gcno 中指定的源码位置,以及 gcda 的生成位置;通过对 GCOV_PREFIXGCOV_PREFIX_STRIP的设置,可以指定 gcda 生成的位置。最后,通过 AndroidNativeCodeCoverageExample 具体示例,详细说明了如何运行 android apk 并获取 c/c++ 的代码覆盖率。


    5. 参考

  • 相关阅读:
    CAXA 3D实体设计2024:塑造未来的创新引擎
    ESP32入门:1、VSCode+PlatformIO环境搭建(离线快速安装)
    java synchronized
    iObjects C++许可模块划分
    浅谈电动汽车智能充电桩及运营管理云解决方案
    WMS仓储管理系统的工作流程是什么
    java多线程进阶(十)线程池
    3. Spring Boot starter入门
    【C++项目实现】俄罗斯方块
    Android 12.0 内置MTK平台音乐播放器
  • 原文地址:https://blog.csdn.net/weiwei9363/article/details/126002907