使用gcov/lcov测试用例的代码覆盖率
gcov(GNU Coverage)是GCC的一部分,用于代码覆盖率分析。它用于确定源代码中的哪些部分已经执行,并生成相应的覆盖率报告。
基本流程
- 编译时插桩:在使用
gcov
时,需要在编译代码时启用覆盖率分析选项。通过在编译命令中包含-fprofile-arcs
和-ftest-coverage
选项,编译器会在生成的可执行文件中插入代码,以便在执行时记录代码的执行路径。-
-fprofile-arcs
:表示启用代码覆盖率分析,它告诉编译器在生成的可执行文件中插入代码来记录程序的执行路径。这种记录通常以一种称为 “arc” 的形式存在,代表程序中的分支或决策点。每当程序执行经过一个分支时,-fprofile-arcs
会记录该信息,从而构建执行路径的统计数据。 -
-ftest-coverage
:用于生成代码覆盖率信息,它告诉编译器生成额外的代码,以在程序执行时收集覆盖率信息,并将此信息存储在相应的数据文件中(通常是以.gcda
后缀结尾的文件)。这些数据文件包含有关程序中每个源代码文件的执行路径的信息。
gcc -fprofile-arcs -ftest-coverage -o your_program your_program.c
使用这两个编译选项编译代码后,这将生成一个可执行文件
your_program
,以及与每个源文件关联的.gcno
文件(.gcno
以及.gcda
这两种文件会在相关文件类型中会有详细介绍)。 -
-
运行可执行文件: 运行生成的可执行文件,执行测试用例或运行应用程序。
gcov
会在执行期间收集覆盖率信息。./your_program
在运行成功后,会生成相应的
your_program.gcda
文件,其中包含有关代码执行路径的数据。 -
生成报告: 运行
gcov
工具,它会分析gcda
文件(由执行时生成的数据文件)和源代码,生成一个详细的覆盖率报告。gcov your_program.gcda
通过运行
gcov
工具可以从.gcda
文件生成.gcov
文件(此处为your_program.c.gcov
),提供关于每个源代码文件的详细覆盖率信息。通过使用
gcov gcda 2.gcda xx.gcda
指定多个gcda文件也可以一次性生成多个源文件的覆盖率报告。
最简单的gcov指令gcov fib.gcda
会生成如下报告:
File 'fib.c'
Lines executed:82.35% of 17
Creating 'fib.c.gcov'
gcov
常用的参数有:
-
-b
输出分支语句频率信息到输出文件, 以及概要信息到标准输出, 但不显示无条件分支gcov -b fib.gcda
生成如下报告:File 'fib.c' Lines executed:82.35% of 17 Branches executed:100.00% of 6 Taken at least once:66.67% of 6 No calls Creating 'fib.c.gcov'
-
-l
在报告.gcov中显示代码文件中的每一行。 -
-c
以数字而不是百分比显示分支频率 -
-f
在报告中显示函数级别的覆盖率信息。 -
-p
在 .gocv 文件名中加入完整的路径信息, 此时路径中的 ‘/’ 用 ‘#’ 表示, ‘..’ 用 ‘^’ 表示
相关文件类型
1. .gcno
.gcno
(Coverage Notes)文件是由GCC生成的一个文件类型,用于代码覆盖率分析。这个文件包含有关源代码中各个语句、分支和函数的信息,以便在程序执行时收集覆盖率数据。
- 基本块信息: 源代码中的基本块(basic block)的信息。
- 分支信息: 程序中的分支(例如
if
语句)的信息,以便跟踪哪些分支已经执行。 - 函数信息: 程序中的函数的信息,以便了解哪些函数已经执行。
这些信息是通过在源代码中插入额外的代码而生成的,这些代码负责跟踪程序的执行路径。.gcno
文件则包含有关如何生成 .gcda
文件所需的信息。
2. .gcda
.gcda
(Coverage Data)文件是由GCC生成的一种文件类型,用于存储程序的代码覆盖率数据。这个文件包含有关源代码中的哪些部分已经执行的信息,以便进行详细的代码覆盖率分析。
具体来说,.gcda
文件记录了程序在执行时经过的代码路径,包括已执行的基本块(basic block)、分支和函数。这些数据是通过在程序中插入代码来收集的,插入的代码负责跟踪执行的路径,然后将信息写入 .gcda
文件。
3. gcov
.gcov
文件是由 gcov
工具生成的代码覆盖率报告文件。它提供了关于源代码文件中哪些行已经执行的详细信息,包括每行代码的执行次数、覆盖率百分比等。
.gcov
文件是一个文本文件,其内容类似于以下示例:
-: 0:Source:your_program.c
-: 0:Graph:your_program.gcno
-: 0:Data:your_program.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:/* Hello, World! program */
-: 2:#include <stdio.h>
-: 3:
1: 4:int main() {
1: 5: // Displaying "Hello, World!" on the console
1: 6: printf("Hello, World!\n");
1: 7:
-: 8: // Return 0 to indicate successful execution
1: 9: return 0;
-: 10:}
在这个示例中,每一行表示源代码文件中的一行代码,以及相关的执行信息。例如,第 4 行表示 main
函数执行了 1 次,第 6 行表示 printf("Hello, World!\n");
语句也执行了 1 次。
总的来说,gcno是编译后就生成的,gcda是运行程序后生成的,gcov是运行gcov gcda后生成的。
lcov
由于.gcov
的报告可读性差,通过lcov
和gcovr
等前端可视化工具可以将.gcda
中的数据整理出来,便于阅读。
lcov
是一个用于生成代码覆盖率报告的工具,解析 gcov
生成的 .gcda
和 .gcno
文件来收集覆盖率数据。
lcov
主要的优点有:
- 支持过滤和排除:
lcov
可以进行过滤和排除,以便在报告中仅显示感兴趣的文件或排除某些文件。 - 生成总体覆盖率报告:
lcov
能够生成总体的覆盖率报告,展示整个项目的代码覆盖率情况。 - 生成 HTML 报告: 除了文本报告,
lcov
还能够生成更可视化的 HTML 格式的报告。这些报告可以通过网页浏览器查看,提供了更直观的代码覆盖率信息。
lcov生成覆盖率常用命令如下(转https://blog.csdn.net/qq_32534441/article/details/90645316):
- 生成全量覆盖率
lcov -b <测试代码路径> -d <gcda目录位置> -c -o result.info --rc lcov_branch_coverage=1
-
-b
b for base directory(Use DIR as base directory for relative paths)为指定原代码路径,即生成gcno数据时编译的代码路径(make时的路径) -
-d
d for directory,为gcda所在目录,可以将所有的gcda放置在一个目录中 -
-c
代表生成覆盖率数据(Capture coverage data),后面不用给其他参数 -
-o
指定生成的文件名,这里指定文件为当前目录下的result.info -
–rc lcov_branch_coverage=1
表示包含分支数据, rc for Override configuration file setting在当前最新版(1.14)的lcov中,指定lcov_branch_coverage=1可能没有效果,解决办法如下:
- 复制一份
/etc/lcovrc
到~/.lcovrc
- 将
~/.lcovrc
中的lcov_branch_coverage设置为1,即lcov_branch_coverage = 1
- 复制一份
-
Merg多份覆盖率数据
lcov -a phase1.info -a phase2.info -o out.info
其中phase1.info以及phase2.info为独立了的两份覆盖率数据,他们整合为一份out.info
-
展示result.info
lcov --list result.info
展示result.info中的code coverage信息
-
在.info文件中正向提取需要统计覆盖率的文件,生成报告
// 比如希望把source相关的路径提取出来 lcov --extract Debug/coverage.info 'source/' -o Debug/finalresult.info
-
在.info文件中反向删除不需要统计覆盖率的文件,生成报告
// 比如希望去除UnitTest 和/usr/相关文件 lcov --remove Debug/coverage.info 'UnitTest/' '/usr/*' -o Debug/finalresult.info
其他工具:
实战:使用gcov+lcov测试QEMU Code Coverage
-
下载QEMU源码
# 不同版本的qemu都可以从这个网址上下载https://download.qemu.org/ wget https://download.qemu.org/qemu-8.0.5.tar.xz tar xvJf qemu-8.0.5.tar.xz cd qemu-8.0.5
-
根据自己的需要配置configure文件生成make
mkdir build cd build ../configure --help # 查看可以配置的编译选项 ./configure --disable-kvm --disable-xen --enable-gcov --extra-cflags="-fprofile-arcs -ftest-coverage" --target-list="aarch64-softmmu arm-softmmu arm-linux-user aarch64-linux-user"
- 根据需要可以disable一些特性,比如此处通过
--disable-kvm --disable-xen
关闭硬件虚拟化 -
--enable-gcov --extra-cflags="-fprofile-arcs -ftest-coverage"
启动code coverage插桩,在一些QEMU版本中,可能还没有--enable-gcov
,所以需要加上--extra-cflags="-fprofile-arcs -ftest-coverage"
-
--target-list="aarch64-softmmu arm-softmmu arm-linux-user aarch64-linux-user"
选择要编译生成的target
- 根据需要可以disable一些特性,比如此处通过
-
编译qemu
make
-
编译成功后在build文件夹下可以看到很多.gcno文件
ls -R | grep .gcno
-
启动运行qemu程序, 此处以qemu-arm为例
- 创建需要在qemu上模拟运行的程序
// hello.c #include <stdio.h> int main(void) { return printf("Hello, I am an ARM32 binary!\n"); }
- 使用交叉编译链编译hello.c
arm-linux-gnueabi-gcc -static -o hello32 hello.c
- 运行qemu-arm
# under build directory ./arm-linux-user/qemu-arm -L /usr/arm-linux-gnueabi ./hello32
-
成功运行后,可以看到build文件夹会生成.gcda文件
ls -R | grep .gcno
-
通过
lcov
生成code coverage信息lcov --rc lcov_branch_coverage=1 -c -d . -o coverage.info # 通过-d可以选中指定目录下的.gcda文件,生成报告 lcov --rc lcov_branch_coverage=1 -c -d ./libqemu-arm-linux-user.fa.p -o coverage2.info
-
查看code coverage
lcov --list coverage.info
如果多次运行qemu程序,相应的code coverage信息会叠加起来存在.gcda文件中,只需要查看最终的.gcda就可以获得所有测试样例综合起来的coverage信息
Reference
- gcov代码覆盖率测试-原理和实践总结
- Code Coverage Testing for C++
- Code coverage testing of C/C++ projects using Gcov and LCOV
- gcov-example
- gcov—a Test Coverage Program
- https://stackoverflow.com/questions/32249416/gcovr-generate-a-0-coverage-report-if-cpp-gcno-and-gcda-files-are-not-present
- https://stackoverflow.com/questions/12360167/generating-branch-coverage-data-for-lcov
- RUNNING ARM BINARIES ON X86 WITH QEMU-USER
Enjoy Reading This Article?
Here are some more articles you might like to read next: