使用gcov完成代码覆盖率的测试

本文源自:《量子数科院》http://www.linezing.com/blog/?p=234


Gcov作为gnu/gcc工作组件之一,是一款的免费的代码覆盖率测试工具,而且可以结合lcov生成美观的html的测试报表。本文介绍一些gcov的使用方法,基本原理,一些实际中可能会遇到的问题以及解决思路。

  1. Gcov的用法

1.1       编译

Gcov的使用方法很简单,首先需要给gcc编译的时候打开覆盖测试的开关

例如要对srcfile.c单个文件生成的程序进行代码覆盖测试,在gcc编译的时候:

1
2
                   
gcc -fprofile-arcs -ftest-coverage srcfile.c -o srcfile

或者简化成:

1
2
                   
gcc --coverage srcfile.c -o srcfile

如果源文件很多,需要编译,链接的时候,在gcc编译的时候:

编译:

1
2
                            
gcc -fprofile-arcs -ftest-coverage -c srcfile.c

链接:

1
2
                            
gcc srcfile.o -o srcfile -lgcov

或者

1
2
                           
gcc srcfile.o –o srcfile -fprofile-arcs

看出来了没有,gcov可以只针对大项目中的某几个单独的文件进行代码覆盖测试,只要在这几个文件编译的时候,加上-ftest-coverage,其他的文件不变就行了,爽吧。

1.2       生成报表

编译完成后会同时生成 *.gcno 文件(gcov notes),gcov生成覆盖率报告时需要参考该文件。

运行生成的可执行文件,给予正常的工作负载,待其正常退出后会生成覆盖率统计数据 *.gcda 文件(gcov data)

通过如下命令行之一查看覆盖率报告:

gcov 生成文本统计结果和带 line-count 标注的源代码:gcov srcfile

lcov 生成较正式的 HTML 报告:

1
2
lcov -c -d srcfile_dir -o srcfile.info
genhtml -o report_dir srcfile.info

注意:另外,编译选项中最好加入 -g3 -O0,前者是为了增加调试信息,后者是为了禁用优化,免得覆盖率测试不准确。

1.3       一个单文件的例子

一个例子程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>
int main( int argc, char * argv[]) {
     int i = 0;
     printf ( "Begin Test...\n" );
     if (1 == argc) {
         printf ( "argc == 1\n" );
     } else {
         printf ( "argc != 1\n" );
         for (i = 0; i < argc; i++)
         printf ( "%d\tof\t%d\n" , i+1, argc);
     }
     printf ( "End Test!\n" );
}

编译:

1
gcc test .c -fprofile-arcs -ftest-coverage -o test

生成文件如下:

1
test  test .c  test .gcno

运行:

1
. /test  1  2  3  4

生成文件如下:

1
test  test .c  test .gcda  test .gcno

生成覆盖测试报告:

1
gcov  test

生成的test.gcov如下:

第一列是覆盖情况,第二列是行号加源程序,其中第一列中数字开头的是执行的次数,####开头的是没有执行到的语句。

2. Gcov的实现原理简介

Gcc中指定-ftest-coverage 等覆盖率测试选项后,gcc 会:

  • 在输出目标文件中留出一段存储区保存统计数据
  • 在源代码中每行可执行语句生成的代码之后附加一段更新覆盖率统计结果的代码
  • 在最终可执行文件中进入用户代码 main 函数之前调用 gcov_init 内部函数初始化统计数据区,并将gcov_exit 内部函数注册为 exit handlers
  • 用户代码调用 exit 正常结束时,gcov_exit 函数得到调用,其继续调用 __gcov_flush 函数输出统计数据到 *.gcda 文件中

2 对后台服务程序进行覆盖率测

从 gcc coverage test 实现原理可知,若用户进程并非调用 exit 正常退出,覆盖率统计数据就无法输出,也就无从生成报告了。后台服务程序若非专门设计,一旦启动就很少主动退出,用 kill 杀死进程强制退出时就不会调用 exit,因此没有覆盖率统计结果产生。

为了解决这个问题,我们可以给待测程序增加一个 signal handler,拦截 SIGHUP、SIGINT、SIGQUIT、SIGTERM 等常见强制退出信号,并在 signal handler 中主动调用 exit 或 __gcov_flush 函数输出统计结果即可。如何使用gcov完成对后台驻守程序的测试

该方案仍然需要修改待测程序代码,不过借用动态库预加载技术和 gcc 扩展的 constructor 属性,我们可以将 signalhandler 和其注册过程都封装到一个独立的动态库中,并在预加载动态库时实现信号拦截注册。这样,就可以简单地通过如下命令行来实现异常退出时的统计结果输出了:

1
LD_PRELOAD=. /gcov_out .so . /daemon

测试完毕后可直接 kill 掉 daemon 进程,并获得正常的统计结果文件 *.gcda。

用来预加载的动态库gcov_out.so的代码如下,其中__attribute__ ((constructor))

是gcc的符号,它修饰的函数会在main函数执行之前调用,我们利用它把异常信号拦截到我们自己的函数中,然后调用__gcov_flush()输出错误信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#define SIMPLE_WAY
void sighandler( int signo)
{
#ifdef SIMPLE_WAY
     exit (signo);
#else
     extern void __gcov_flush();
     // flush out gcov stats data
     __gcov_flush();
     // raise the signal again to crash process
     raise (signo);
#endif
}
__attribute__ ((constructor))
void ctor()
{
     int sigs[] = {
         SIGILL, SIGFPE, SIGABRT, SIGBUS,
         SIGSEGV, SIGHUP, SIGINT, SIGQUIT,
         SIGTERM
     };
     int i;
     struct sigaction sa;
     sa.sa_handler = sighandler;
     sigemptyset(&sa.sa_mask);
     sa.sa_flags = SA_RESETHAND;
     for (i = 0; i < sizeof (sigs)/ sizeof (sigs[0]); ++i) {
         if (sigaction(sigs[i], &sa, NULL) == -1) {
             perror ( "Could not set signal handler" );
         }
     }
}

编译:

1
gcc -shared -fPIC gcov_out.c -o gcov_out.so

4. 参考资料

man gcc

man gcov

lcov – http://ltp.sourceforge.net/coverage/lcov.php

注意,lcov 最好使用 1.9 及以上版本,否则可能遇到如下错误:

geninfo: ERROR: …: reached unexpected end of file

来自淘宝清无分享:

转载文章请注明,转载自:量子数科院[http://www.linezing.com/blog]。文章均为原创,版权归量子统计所有


你可能感兴趣的:(使用gcov完成代码覆盖率的测试)