代码覆盖工具gcov, lcov的一些使用经验

使用gcov和lcov做代码覆盖有一段时间了,期间走了一些弯路,算是一些经验教训。

  • gcov可以对shared object进行代码覆盖信息收集。

之前的一篇文章说过,gcov不能收集.so的代码覆盖率信息,其实这个是错的。

如果是C++程序,在CXXFLAGS中加入-fprofile-arcs -ftest-coverage 作为编译选项,并且在LDFLAGS中加入-lgcov作为链接选项。如果没有-lgcov选项编译出来的.so文件在动态加载的时候会提示类似 undefined reference to ‘__gcov_merge_add’ 或者 undefined reference to ‘__gcov_init’这样的错误。

如果是C程序,在CFLAGS中加入-fprofile-arcs -ftest-coverage 作为编译选项,也是在LDFLAGS中加入-lgcov作为链接选项,就OK了。跑测试的时候跟正常测试一样,最后把收集到的.gcda文件处理一下就能得到代码覆盖率报告

  • 用lcov处理.gcda文件的时候,在处理某些文件的时候hang了。

对于这个问题,不知道根本原因是什么,可能是那个文件的某行代码触发了bug,我对此是workaround了一下。lcov本身是一堆Perl脚本,打开它,通常在这里“/usr/bin/geninfo”,然后找到这行,注释掉

push(@gcov_options, “-a”) if ($gcov_caps->{‘all-blocks’});

  • genhtml的一个参数:-f, –frames

Use HTML frames for source code view。这样会在每个结果页的左边生成一个缩略图,review代码的时候很方便。但是,如果你的项目里面用到了一些第三方的库,而那些第三方的库没有提供完整的源代码,那么使用这个参数就会出现问题。

gd-png: fatal libpng error: Image width or height is zero in IHDR
gd-png error: setjmp returns error condition

解决方法可以是1. 尝试找一下源代码;2. 通常这些第三方库,我们都是放在一个单独的文件夹里面的,这样子用lcov处理.gcda文件的时候,可以指定目录,把这些第三方库的目录跳过去

  • genhtml的一个参数:–num-spaces NUM

默认值是8,出来的报告看着很费劲,建议换成4,如果代码有很多层,那设成2也是可以的

用lcov生成diff代码覆盖率报告

lcov是建立在gcov之上的一个可以生成html代码覆盖率报告的工具,最近公司开始尝试引入代码覆盖来提高产品质量,lcov很好地满足了我们的需求,虽然lcov本身支持生成代码覆盖率的diff报告,但是跟我们的需求不太符合。

首先说一下我们的情况,我们有一套自动化回归测试集,可以看做是我们测试的全集。现在已经完成了基于这个回归测试集的代码覆盖率报告,这其中肯定有某些行没有被覆盖到的,如何评估这些没有被测试过的行的风险呢?最开始是DEV跟QA一起review,由开发来评估这些没有被测试过的代码。最近提出了一个新的思路,就是用Production的代码覆盖和本地回归测试的代码覆盖做一个diff,着重看一下那些在Production里面实际被执行过的代码中,有哪些是我们本地回归测试所没有覆盖的,因为这些“裸奔”的代码才是最危险的代码。

查一下文档,lcov在生成html报告的时候可以做这个事情,在genhtml的时候使用参数“–baseline-file baseline-file”,指定了这个参数以后就会在用输入的tracefile里面的counter来减去baseline-file里面的counter,完成这个减法计算以后再生成报告。可以用Local测试的数据作为basefile,两者一减,在报告里面那些cover的行,就是Production上跑到的代码而回归测试集没有能覆盖的部分。但是如果直接用的话,结果可能不是我们想要的,因为我用了genhtml这样的一个特性:“Note that when a count for a particular line in baseline-file is greater than the count in the tracefile, the result is zero.”。

举个例子,如果我们的代码被执行了若干次:

Code A B C D
Local 0 40 50 60
Production 50 50 50 50
Actual result 50 (local uncover) 10 (local uncover) 0 (covered) 0 (covered)
Expected local uncover covered covered covered

主要看第三列,B这行代码。如果在Local测试中某行代码被执行了40次,而同一行代码在Production被执行了50次,那么diff出来的结果是这行代码没有被覆盖,而实际的结果是回归测试已经覆盖到了。解决思路很简单,我们可以让Local的counter增加N倍,这个N要足够大,就能避免这种情况的发生了。当我们按照正常操作生成一个tracefile的时候,接下来就用“–add-tracefile tracefile” 来把这个tracefile的counter加上去,

lcov -a mytest.info -a mytest.info -o mytest_2x.info

lcov -a mytest_2x.info -a mytest_2x.info -o mytest_4x.info

写个shell脚本帮你干这个事情,不到半小时就能把counter增加2的N次方倍。最后以这个放大了counter的tracefile作为基线,生成的diff report

genhtml -o diffresult –num-spaces 4 –legend -b mytest_1024x.info prod.info

最终结果:

Code A B C D
Local 0 40960 51200 61440
Production 50 50 50 50
Actual result 50 (local uncover) 0 (covered) 0 (covered) 0 (covered)
Expected local uncover covered covered covered

什么样的代码最危险?没有被测试过的代码是最危险的。Production和regression test的代码覆盖率diff报告可以给我们提供一些有针对性的信息。

分享两个PPT,关于gcov和lcov,还有代码覆盖

两个PPT的主题其实都是关于代码覆盖的,那为什么分开了2个PPT呢?因为准备在两个会上讲,一个讲代码覆盖,尽量不涉及工具细节,另一个只介绍工具。

PPT写的很粗,主要靠讲,不过PPT本身的话我个人感觉一般般,但是又没有修改的动力了,就先放上来吧,有问题意见请及时给我反馈,多谢先啦。

Using gcov and lcov
Knowledge share on code coverage

解决gcov不能生成.gcda文件,以及其他错误

上一篇博客简单介绍了如何用lcov获取代码覆盖信息,今天回到公司动手做,还是遇到了些问题。

跟开发沟通了一下,我们的程序不是在每台开发机上都能成功编译的,因为需要很多第三方库,等等……所以就登陆到他的开发机上把程序编译好,然后拷贝到我自己的机器上运行。问题就出在这里,编译的时候在机器A上,路径是/home/qli/debug,我在机器B上运行的目录是 /home/jchen/work/,程序运行完毕生成.gcda文件失败,出现若干个提示如下:

profiling:/home/qli:Cannot create directory
profiling:/home/qli/debug/server/Selection.pb.gcda:Skip
在我的运行环境中没有相应的目录,所以不能生成.gcda文件,Google了一下发现,只要设一下GCOV_PREFIX和GCOV_PREFIX_STRIP这两个环境变量就好了。GCOV_PREFIX就是制定生成数据文件的前缀,GCOV_PREFIX_STRIP就是需要在原来的路径上去掉多少层目录,这2个变量配合使用就能把数据文件生成到我们想要的地方。假如我编译时候的路径是“/home/qli/debug/server/”,我现在的运行路径是“/home/jchen/work/ads/ads_server/server/”,那么执行如下命令即可
export GCOV_PREFIX=”/home/jchen/work/ads/ads_server/server/”
export GCOV_PREFIX_STRIP=4
执行完这两行命令,就可以开始运行被测程序,然后正常退出,就会看到.gcda文件生成出来了。
.gcda文件生成了就可以用lcov直接处理它们,但是我在处理的过程中遇到以下错误:“stamp mismatch with graph file”
Processing Ads_Request.gcda
/home/jchen/work/ads/ads_server/server/xxx.gcda:stamp mismatch with graph file
geninfo: WARNING: gcov did not create any files for /home/jchen/work/ads/ads_server/server/xxx.gcda!
导致出现这个错误的原因是.gcda和.gcno文件并不是同一次build出来的,它们2个文件的时间戳就不一样了,很简单,更新所有的.gcno文件即可。

C/C++代码覆盖工具gcov与lcov入门

gcov是一个可用于C/C++的代码覆盖工具,是gcc的内建工具。下面介绍一下如何利用gcov来收集代码覆盖信息。
想要用gcov收集代码覆盖信息,需要在gcc编译代码的时候加上这2个选项 “-fprofile-arcs -ftest-coverage”,把这个简单的程序编译一下

gcc -fprofile-arcs -ftest-coverage hello.c -o hello

编译后会得到一个可执行文件hello和hello.gcno文件,当用gcc编译文件的时候,如果带有“-ftest-coverage”参数,就会生成这个.gcno文件,它包含了程序块和行号等信息
接下来可以运行这个hello的程序

./hello 5
./hello 12

运行结束以后会生成一个hello.gcda文件,如果一个可执行文件带有“-fprofile-arcs”参数编译出来,并且运行过至少一次,就会生成。这个文件包含了程序基本块跳转的信息。接下来可以用gcov生成代码覆盖信息:

gcov hello.c

运行结束以后会生成2个文件hello.c.gcov和myfunc.c.gcov。打开看里面的信息:

-: 0:Source:myfunc.c
-: 0:Graph:hello.gcno
-: 0:Data:hello.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include
-: 2:
-: 3:void test(int count)
1: 4:{
-: 5: int i;
10: 6: for (i = 1; i < count; i++)
-: 7: {
9: 8: if (i % 3 == 0)
3: 9: printf (“%d is divisible by 3 \n”, i);
9: 10: if (i % 11 == 0)
#####: 11: printf (“%d is divisible by 11 \n”, i);
9: 12: if (i % 13 == 0)
#####: 13: printf (“%d is divisible by 13 \n”, i);
-: 14: }
1: 15:}

被标记为#####的代码行就是没有被执行过的,代码覆盖的信息是正确的,但是让人去读这些文字,实在是一个杯具。不用担心,有另外一个工具叫lcov,可以用程序解析这些晦涩的字符,最终输出成html格式的报告,很好吧!

lcov -d . -t ‘Hello test’ -o ‘hello_test.info’ -b . -c

指定lcov在当前目录“.”去找代码覆盖的信息,输出为’hello_test.info’ ,这个hello_test.info是一个中间结果,需要把它用genhtml来处理一下,genhtml是lcov里面的一个工具。

genhtml -o result hello_test.info

指定输出目录是 result。一个完整的html报告就生成了,做一个连接,把这个目录连到随便一个web server的目录下,就可以看报告了。

Continue reading “C/C++代码覆盖工具gcov与lcov入门”