Linux下代码覆盖率工具:gcov
对于C/C++软件开发,常常需要通过代码覆盖率报告来了解测试用例的场景覆盖情况,对于测试用例未覆盖的代码分支流程,需要补充用例,以保证测试用例的全面性与完整性,不漏测任何一个分支BUG。Linux下可用gcov工具生成覆盖率统计信息,然后借助gcov的图形化工具lcov,可生成html格式的代码覆盖率报告,进一步提高覆盖率测试结果的可读性。
1、Gcov实现原理
Gcc中添加-ftest-coverage编译选项后,则:
1)输出目标文件中留出一段存储区用于保存统计数据;
2)源代码中每一行可执行语句生成的代码之后注入一段更新覆盖率统计结果的代码;
3)在可执行进程文件中,进入main函数之前调用 gcov_init初始化统计数据区,并将gcov_exit注册为 exit handlers;
4)用户侧代码调用exit 时,将调用gcov_exit,其继续调用__gcov_flush函数输出统计数据到*.gcda文件中。
从gcov实现原理可知,若用户进程并未调用 exit退出,就得不到覆盖率统计数据,也就无从生成报告了。而后台服务程序一旦启动很少主动退出。为了解决这个问题,我们可以给待测程序注册一个信号处理函数(signal handler),处理SIGINT、SIGQUIT、SIGTERM等常见强制退出信号,并在信号处理函数中主动调用exit或 __gcov_flush函数,以便输出统计结果。
上述方案需要修改待测程序代码,可以借用动态库预加载技术和gcc扩展的constructor属性,将signal handler和注册过程都封装到一个独立的动态库中,并在预加载动态库时实现信号拦截注册。这样,就可以通过如下命令行来实现异常退出时的统计结果输出:
|
LD_PRELOAD=./gcov_preload.so ./test |
测试完成后,直接kill掉test进程,即可获得正常的统计结果文件*.gcda。其中,gcov_preload.so是你自己写的一个程序,接受SIG,然后exit,以触发gcov进行输出。
接下来,我们详细说明gcov的使用步骤。
Gcov属于gcc工具集之一,是随gcc一起发布的,并不需要独立安装。lcov需要自己下载开源软件安装。Lcov开源软件的下载地址:
https://sourceforge.net/projects/ltp/files/Coverage%20Analysis/
以lcov-1.13.tar.gz为例,安装步骤比较简单:
tar –zxvf lcov-1.13.tar.gz
cd lcov-1.13/
make install
lcov可执行二进制默认安装在/usr/local/bin目录下,若该路径不在环境变量$PATH中,可先用export PATH=$PATH: /usr/local/bin添加进来,以便后续使用。
2、编译链接
为了生成生成覆盖率统计信息,需要在编译时添加编译选项:
编译选项CFLAGS += -fprofile-arcs -ftest-coverage,或CFLAGS += --coverage,其中-fprofile-arcs用于生成.gcno文件,-ftest-coverage用于生成.gcda文件。
链接选项LDFLAGS += -lgcov,注:此选项仅在使用ld链接时需要,使用gcc时不需要。
举例说明,编辑一个test.c文件,内容如下:
#include <stdio.h>
#include <stdlib.h>
int func_1(char *ptr)
{
if (NULL == ptr)
{
printf("func_1:ptr==NULL!\n");
return 1;
}
printf("func_1: ptr: %p\n", ptr);
return 0;
}
void func_2(char *ptr)
{
printf("func_2: ptr: %p\n", ptr);
}
void func_3(void)
{
printf("func_3...\n");
}
void main(int argc, char *argv[])
{
char *ptr = NULL;
func_1(ptr);
ptr = (char *)malloc(8);
func_2(ptr);
free(ptr);
return;
}
执行gcc编译命令:
[root@HLZ gcov]# gcc -fprofile-arcs -ftest-coverage test.c -o test
[root@HLZ gcov]# ls
lcov-1.13 test test.c test.gcno
编译生成test可执行程序的同时,也生成了同名的.gcno文件。
3、运行
执行test程序,生成同名的.gcda文件:
[root@HLZ gcov]# ./test
func_1:ptr==NULL!
func_2:ptr: 0x99b5008
[root@HLZ gcov]# ls
lcov-1.13 test test.c test.gcda test.gcno
执行gcov命令,生成覆盖率信息文件,即.gcov文件:
[root@HLZ gcov]#gcov test.c
File'test.c'
Linesexecuted:73.68% of 19
test.c:creating'test.c.gcov'
[root@HLZ gcov]# ls
lcov-1.13 test test.c test.c.gcov test.gcda test.gcno
test.c.gcov就是代码覆盖信息文件,但看起来并不直观,下面我们进一步分析。
4、覆盖率分析
覆盖率分析过程分两步。
首先,借助lcov对覆盖率文件test.c.gcov进行改造,生成了test.info文件:
[root@HLZ gcov]#lcov -c -d . -o 'test.info' -b .
Capturing coverage data from .
Found gcov version: 4.4.7
Scanning. for .gcda files ...
Found 1 data files in .
Processing test.gcda
Finished .info-file creation
[root@HLZ gcov]# ls
lcov-1.13 test test.c test.gcda test.gcno test.info
其次,通过genhtml工具(lcov工具集中带有)生成result文件夹,其中就包含index.html,可以直接双击打开查看:
[root@HLZ gcov]# genhtml -o result test.info
Reading data file test.info
Found 1 entries.
Found common filename prefix "/home/hanlzh/test"
Writing .css and .png files.
Generating output.
Processing file gcov/test.c
Writing directory view page.
Overall coverage rate:
lines......: 73.7% (14 of 19 lines)
functions..: 75.0% (3 of 4 functions)
[root@HLZ gcov]# ls
lcov-1.13 result test test.c test.gcda test.gcno test.info
[root@HLZ gcov]# ls result/
amber.png emerald.png gcov gcov.css glass.png index.html index-sort-f.html index-sort-l.html ruby.png snow.png updown.png
最后,查看结果:
1)在windows界面下,双击index.html,显示目录级别下的覆盖率统计信息;
2)进一步点击目录gcov,显示文件级别的覆盖率统计信息;
3)进一步点击文件test.c,显示文件内分支语句的覆盖率详细信息;
显然地,标红行是执行./test时没有执行到的语句,可以为我们进一步优化代码或补充测试用例提供了参考。
5、总结
Lcov和genhtml的功能比较丰富,选项参数比较多,感兴趣的读者可以通过man lcov和man genhtml查阅。