使用gcov进行C语言代码覆盖率统计

介绍:

gcov是一个测试代码覆盖率的工具。它必须与GCC一起使用来分析程序,以帮助并发现程序的未测试部分,还可以结合lcov工具生成html格式的统计报告,可以方便的查看代码覆盖率的情况,甚至可以查看每一行代码的执行次数。

基本原理:

基本块BB:如果一段程序的第一条语句被执行过一次,这段程序中的每一条语句都要执行一次,那么这段程序构成一个基本块。一个BB中的所有语句的执行次数一定相同。一般情况下BB的最后一条语句一定是一个跳转语句,跳转的目的地是另外一个BB的第一条语句,如果跳转是有条件的,就产生了分支,该BB就有两个BB作为目的地。

跳转ARC:从一个BB到另外一个BB的跳转叫做一个跳转arc,要想知道程序中的每条语句和分支的执行次数,就必须知道每个BB和ARC的执行次数。

如果把BB作为节点,ARC作为边,那么一个函数中的所有BB就构成了一个有向图(流程图)。

为了统计BB和ARC的执行次数,GCC会在产生汇编文件时进行插桩。在程序执行的过程中,这些桩代码负责收集程序的执行信息。根据节点的入度和出度相等这个基本原理,只要知道了部分ARC的执行次数,就可以推断所有BB和ARC的执行次数。所以不会对所有的ARC插桩,只需要最低限度的插桩。

编译时为每个.c文件生成对应的.gcno文件,存储流程图信息。运行程序时,为每个.c文件生成.gcda文件,收集代码覆盖率数据。

gcov使用基础:

Linux下的工具gcov(gcc自带)可以统计函数覆盖、行覆盖、条件覆盖、分支覆盖。Gcov只能和gcc一起使用。

gcc使用手册中有gcov的介绍和使用方法。Using the GNU Compiler Collection For gcc version 4.7.1 newest until 20120825.pdf

使用gcc编译时使用相关选项。针对CMake编译项目可以通过修改Makefile集成gcov。

多次执行一个进程,gcda文件里的数据会累加。

多个实例同时运行,gcda文件中的数据也可以正确更新。

使用举例:

#编译代码

[root@localhost aaa]# gcc -fprofile-arcs -ftest-coverage -o eight_queen eight_queen.c

编译时,每个.c源文件都会生成.gcno文件。执行可执行文件时,每一个源文件都会生成.gcda文件。

.gcno文件包含重构流程图的信息,并且记录BB的源代码行号。.gcda文件包含arc转换的计数,还有一些汇总信息。

[root@localhost aaa]# ./eight_queen

gcov creates a logfile called ‘sourcefile.gcov’ which indicates how many times each line of a source file ‘sourcefile.c’。

gcov should be run with the current directory the same as that when you invoked the compiler. Otherwise it will not be able to locate the source files. gcov produces files called ’mangledname.gcov’ in the current directory. These contain the coverage information of the source file they correspond to.

If you invoke gcov with multiple input files, the contributions from each input file are summed.

#统计分支覆盖率,可以用来评估软件测试的代码覆盖率。

[root@localhost aaa]# gcov -b eight_queen.c

File 'eight_queen.c'

Lines executed:96.88% of 32 //注释行、宏的定义不算,只有‘{’或者‘}’的行不算,空行不算。

Branches executed:100.00% of 22 //这里的branches其实是指条件,而不是分支。循环的判定和if的判定同等对待。

Taken at least once:100.00% of 22

Calls executed:100.00% of 5 五处函数调用都被执行了

eight_queen.c:creating 'eight_queen.c.gcov'

每个条件计为两个branch,为真为假各为一个branch。条件表达式只要经过计算,不管结果为真还是假,真假两个branch都计入Branches executed。但是只有真的执行某条分支了,taken at least once才会计数。注意逻辑或和逻辑与表达式的屏蔽效果。逻辑或表达是有一个条件为真了,后续条件不再计算。逻辑与表达式有一个条件为假了,后续条件不再计算。

下面是更详细覆盖信息:显示每一行的执行次数,每一个分支的执行次数,每一个函数被调用的次数。

branch x never executed表示该条件表达式没有被计算过。call x never executed表示该函数没有被调用过。

#####:   x: 表示该行没有被执行过。-:   x:表示该行不是有效的行,即该行只有注释、宏的定义、‘{’或者‘}’、或者是空行。

[root@localhost aaa]# cat eight_queen.c.gcov

        -:    0:Source:eight_queen.c

        -:    0:Graph:eight_queen.gcno

        -:    0:Data:eight_queen.gcda

        -:    0:Runs:1

        -:    0:Programs:1

        -:    1:/* */

        -:    2:/* */

        -:    3:/* */

        -:    4:

        -:    5:#include<stdio.h>

        -:    6:

function place called 15720 returned 100% blocks executed 100%

    15720:    7:int place(int k,int X[]){

    15720:    8:  int i=1;

    15720:    9:  if (k==1) {return 1;}

branch  0 taken 8 (fallthrough)

branch  1 taken 15712

    48800:   10:  for(i=1;i<k;i++){

branch  0 taken 46752

branch  1 taken 2048 (fallthrough)

        -:   11:       

    46752:   12:        if (X[i]==X[k] || i-X[i]==k-X[k] || i+X[i]==k+X[k]) {

branch  0 taken 39556 (fallthrough)

branch  1 taken 7196

branch  2 taken 36322 (fallthrough)

branch  3 taken 3234

branch  4 taken 3234 (fallthrough)

branch  5 taken 33088

    13664:   13:                return 0;

        -:   14:        }

        -:   15:  }

     2048:   16:  return 1;

        -:   17:}

        -:   18:

function nqueens called 1 returned 100% blocks executed 100%

        1:   19:int nqueens(){

        -:   20:  int k,n,X[9];

        1:   21:  n=8;

        1:   22:  k=1;

        1:   23:  int i=1;

        1:   24:  for(i=1;i<=8;i++){X[i]=0;}

branch  0 taken 8

branch  1 taken 1 (fallthrough)

        1:   25:  int count=0;

     4023:   26:  while (k>0){

branch  0 taken 4021

branch  1 taken 1 (fallthrough)

     4021:   27:        X[k]++;

    21706:   28:        while (X[k]<=n && !place(k,X)){

branch  0 taken 15720 (fallthrough)

branch  1 taken 1965

call    2 returned 15720

branch  3 taken 13664

branch  4 taken 2056 (fallthrough)

    13664:   29:          X[k]++;

        -:   30:        }

        -:   31:       

     4021:   32:        if (X[k]<=n){

branch  0 taken 2056 (fallthrough)

branch  1 taken 1965

        -:   33:         

     2056:   34:          if (k==n){

branch  0 taken 92 (fallthrough)

branch  1 taken 1964

       92:   35:                count++;

       92:   36:                printf("%d: ",count);

call    0 returned 92

      828:   37:                for(i=1;i<=n;i++){

branch  0 taken 736

branch  1 taken 92 (fallthrough)

      736:   38:                  printf("%d ",X[i]);

call    0 returned 736

        -:   39:                }

       92:   40:                printf("\n");

call    0 returned 92

        -:   41:          } else {

     1964:   42:                k++;

        -:   43:          }

        -:   44:        } else {

        -:   45:         

     1965:   46:          X[k]=0;

     1965:   47:          k--;

        -:   48:        }

        -:   49:  }

        1:   50:}

        -:   51:

function main called 1 returned 100% blocks executed 100%

        1:   52:void main(){

        1:   53:  nqueens();

call    0 returned 1

        1:   54:}

.so动态库的代码覆盖率不进行统计。

lcov可以将.gcda文件的内容汇总成info文件,然后将info文件中的数据已html网页的形式显示出来。

统计ospf模块代码覆盖率

操作要点:

1、开发人员编译ospf。ospf代码中已经处理了sigterm信号,在其中加上__gcov_flush()。

2、将可执行文件ospfd和所有.c文件对应的.gcno文件发送给测试人员。

3、测试人员执行ospf自动化用例(这里只执行了高优先级的测试用例)。

4、所有.c文件都生成了对应的.gcda文件。

5、测试人员使用lcov生成html统计报告。有整体和每个文件的统计数据,包括行、分支和函数覆盖率。如果要看每行、每个分支、每个函数的执行次数,需要源代码。

需要说明:

1、没有执行CSPF、TE、FRR、SNMP、VRF等相关案例。所以相应文件没有计入统计。但是这些功能可能有一些支持性的代码在其它文件中。这会降低覆盖率。

2、没有覆盖P2MP和NBMA网络。这会降低覆盖率。

3、内部功能逻辑覆盖率比较低。

4、ospf_routemap.c覆盖率低,看quagga代码,文件中的函数是ospf对route-map的match和set条目变化的响应。这些要加在用例中。说明每个模块都要对route-map做等价类覆盖测试。

5、CLI代码覆盖率低,因为有不少命令是和TE相关的,这些功能是在TE案例中覆盖的。还有一些命令因为比较生僻,自动化用例中没有覆盖。其实有一个专门覆盖CLI的测试脚本,用来覆盖所有cli命令的所有常见cli行为,但是这次没有执行。

6、手工执行测试用例、探索测试、协议一致性测试的覆盖这里没有体现。

 

补充说明:

1.gcc自带gcov,所以不需要单独安装。

2.使用gcov需要在gcc编译时加上参数gcc -fprofile-arcs -ftest-coverage,编译后每一个.c文件都会产生一个.gcno的文件。

3.运行使用gcov选项编译出的可执行文件,程序正常退出后,每一个.c文件都会产生.gcda格式的文件,该文件记录了代码执行的覆盖率情况。如果想分析代码或者想查看某个.c文件里每一行的执行情况,在有源码情况下,可以使用gcov -b 文件名生成.c.gcov文件,里面可以查看每一行代码的执行情况。如果运行的是一个守护进程,则需要开发人员在代码中主动调用 exit 或 __gcov_flush函数输出统计结果。还可以拦截杀死进程的信号,在杀死进程前调用__gcov_flush()输出数据,这样手动杀死守护进程即可得到.gcda数据文件。

4.程序运行测试完成后,每一个.c文件都有一个对应的.gcda文件,路径与编译程序的机器上项目代码的绝对路径一致,去同样的路径下找即可。如果想指定目录,可使用下面两个环境变量重定位数据文件:

    >export GCOV PREFIX - 指定加入到目标文件中的绝对路径前缀,默认没有前缀

    >export GCOV PREFIX STRIP - 指示要跳过的目录层次

 比如编译后目标文件在‘/user/build/foo.o’,编译后的程序执行时会创建‘/user/build/foo.gcda’文件。但若把程序拷贝到另一个系统中运行时,可能并没有这个目录,此时设置环境变量

    ‘GCOV_PREFIX=/target/run’

    ‘GCOV_PREFIX_STRIP=1’

这样,运行时将输出到‘/target/run/build/foo.gcda’文件。然后可把生成的所有.gcda文件拷贝到本机编译时的代码目录中使用gcov工具即可。

5.测试人员自己在没有源码时,借助lcov也可以生成代码覆盖率统计情况。lcov工具需要安装,下载后直接make install即可。首先拿到开发编译后生成的.o文件和.gcno文件,再把程序运行后产生的gcda文件放到同一个目录下,再使用命令lcov -c -d . -o output.info  ;-c表示要捕获覆盖率数据,-d表示 使用从gcda文件中捕获数据,后面接路径,-o 表示生成的结果保存的文件。可使用lcov -l output.info查看代码覆盖率统计信息。注意:lcov默认不统计branch的统计信息,需要加上参数--rc lcov_branch_coverage=1。才能统计到branch的信息。得到info文件后就可以生成html的结果统计了,命令genhtml -o result output.info --branch-coverage --rc lcov_branch_coverage=1 --no-source,-o表示生成的html结果保存到那个目录下,-branch-coverage --rc lcov_branch_coverage=1 --no-source表示要生成的html要包含branch的信息,--no-source表示没有源码时生成报告,如果不加该参数,目录下没有.c文件源码则不能生成html结果报告。

 

实际操作情况:

1.开发人员在ospf代码里处理sigterm信号时,调用__gcov_flush()输出数据文件,这样再杀死进程后就可以得到数据文件,开发人员使用gcc编译时加上参数-fprofile-arcs -ftest-coverage。

2.把带gcov选项编译好的文件,放到相应版本的路由器上。杀掉原来的ospfd进程,启用新放进来这个进程。注意确保新启动进程是带gcov选项编译的。

3.新的进程启动后,在该路由器上自动化测试ospf模块。测试完成后killall ospfd或者kill 进程号。编译ospf模块机器的路径,本次是/root/trunk/platform/linux/fr-xxx/obj/的目录,进入该目录即可找到生成的gcno文件。

4.路由器的linux中没有安装gcc工具和lcov工具,需要到安装了这些工具的linux PC上生成报告。上面生成的gcda文件和开发编译后的.o以及.gcno文件copy到安装了gcc和lcov的linux PC中,每个.gcda,.o,.gcno文件都是一一对应的,目录结构也一致。

5.使用lcov -c -d . -o output.info --rc lcov_branch_coverage=1将.gcno文件合成一个.info文件。可使用lcov -l output.info查看该info文件的概况,包含每个.c文件的覆盖率百分比信息。再使用genhtml output.info -o result --branch-coverage --rc lcov_branch_coverage=1 --no-source即可生成html,在result目录下即可看到结果,里面记录了ospfd进程本次运行过程中代码的覆盖情况。

6.多次测试、多台设备或者多个人的测试覆盖数据可以合并。参考第3,4,5步骤将多次或多人执行的gcda文件分别生成info文件。然后使用下面的命令合并info文件。

lcov -a output1.info -a output2.info -o sum.info -rc lcov_branch_coverage=1,-a表示要合并的info文件,-rc lcov_branch_coverage=1表示统计branch,注意该参数必须加上,否则会导致统计branch的覆盖率的数据有遗漏和错误。然后参考步骤5使用sum.info生成合并后的报告。

7.如果想要忽略某些目录,或者.c文件的覆盖,比如想忽略ospf_xxx.c的覆盖率,可使用lcov -r sum.info '*/ospf_xxx.c'-o newdata.info,-r参数后可以匹配正则,新生成的newdata.info中将不再包含ospf_xxx的信息,再使用步骤5生成html报告,里面将不再含有ospf_xxx.c的覆盖率信息。

ospf模块部分文件代码覆盖率如下,点击文件名,可以打开每个文件,查看每一行代码的执行次数、每个函数调用的次数、每个分支执行的次数。

  

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值