使用Intel编译器(5)PGO(5)PGO工具之代码覆盖工具(code coverage)2

参考手册:

http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011Update/compiler_c/index.htm


说明:本系列文章为个人笔记,如有不正确之处,请参考官方相关文档,如果错误发现,我会尽量更新修改。另外,以下内容不保证对于所有版本的编译器都正确,编译器的实现也可能有一些变化之处,具体参考官方文档。


更多说明请参考http://blog.csdn.net/gengshenghong/article/details/7034748中补充说明部分。


Summary:上一部分介绍了Intel编译器相关的代码覆盖工具的使用方法,对于codecov的选项只是稍微提示了一下,这里以几个关键功能来分析其它选项。

(1) “可视化”代码覆盖报告

这里的可视化报告,就是前面一部分的默认输出的html类型的报告了,一个CODE_COVERAGE.html为summary信息,链接到其它的文件中(CodeCoverage文件夹)。这里先继续讨论这种可视化报告的基本内容和一些关键选项,下一部分分析几个用于输出其它格式报告的一些选项。

1. 代码覆盖基础

这里只是提示一下,codecov的覆盖报告中,有文件覆盖报告、函数覆盖报告和基本块覆盖报告。(PS:关于测试中的代码覆盖,这里不详细介绍,相关的还有行覆盖等概念)。

2. 关于可视化报告结构

可视化报告,其中CODE_COVERAGE.html为summary信息,即top level coverage,显示的是所有的模块的报告,显示文件覆盖率、函数覆盖率和基本块覆盖率。通过其中的链接就可以进入到Individual Module Source View查看每一个模块(文件)的源代码的视图,其中会显示哪些代码或函数覆盖了,哪些代码或函数没有覆盖(用不同的颜色表示)。分别如下图所示:

使用Intel编译器(5)PGO(5)PGO工具之代码覆盖工具(code coverage)2_第1张图片使用Intel编译器(5)PGO(5)PGO工具之代码覆盖工具(code coverage)2_第2张图片

3. 关于颜色

上一节已经介绍了一系列设置颜色的选项,具体每个选项设置什么颜色参考手册即可,这个比较容易理解。对于上图(默认配色),其中没有颜色的表示覆盖的代码,粉红颜色的表示是没有覆盖的代码。需要提示的一点是,要理解codecov大概是如何完成颜色显示的功能的:扫描源代码字符串,直到到达某一个位置,其被profile信息标记为基本块的开始,如果这个基本块的profile信息表明其coverage category(覆盖类别)改变了(比如前一个基本块是覆盖的,这一个没有覆盖,或者反之),那么codecov工具改变对应的HTML报告中这一部分的代码的显示颜色。所以,需要注意的是理解一些特殊部分的颜色,比如:基本块紧随后的注释行和括号}等(注释肯定不会被执行了)的颜色和其没有覆盖的块的颜色是一样的,当然,对于一个基本块内部的颜色,那么肯定是和基本块的颜色一样。大概理解一下下面这个截图的效果就行了(其中黄色是没有覆盖的基本块):

使用Intel编译器(5)PGO(5)PGO工具之代码覆盖工具(code coverage)2_第3张图片


4. 动态计数器Dynamic Counters

codecov除了上面的代码覆盖的功能,另一个功能是能显示每一个基本块的执行次数。只需要在codecov中加入-counts选项即可,在报告中,^符号表示基本块的开始的地方,后面的数字表示执行的次数,可选的括号内有一些其他数据信息,参考下面的例子:

使用Intel编译器(5)PGO(5)PGO工具之代码覆盖工具(code coverage)2_第4张图片

对于for循环,其中,for(ii=0;ii<10;ii++),其中有两个基本块的开始之处,ii=0开始的基本块执行力1次,ii<10开始的执行块执行了11此(10次循环要比较11次),然后第一个if对于10次循环都需要执行的,满足条件的只有4次,后面的理解也是一样。另外,以ii<10开始的基本块后面标识了一个(2),表示此处生成了两个基本块,当然两个基本块都执行了10次。对于switch,其它的都和for循环一样的理解,但是switch(ii)这一句,其中括号里面为2/3,表示这里生成了三个基本块,但是只执行了2个基本块(这里的颜色使用的就是部分覆盖partial coverage的颜色,并不是白色和黄色),最前面的2表示的是执行了两次。

当然,这里只是大概的分析了一下,要完全理解这些数据,估计需要对这些代码对应的基本块的结构有清晰的理解了。

那么,这里已经提到了部分覆盖,就有另一个选项了:-nopartial。此选项告诉codecov,将一个源码出生成的多个基本块中,只要一个以上的基本块被覆盖(执行)了,那么就认为该处被覆盖了,对于上面的例子,加上了此选项后,switch部分结果如下(只是颜色发生了变化):

使用Intel编译器(5)PGO(5)PGO工具之代码覆盖工具(code coverage)2_第5张图片


5. 排除代码覆盖分析

对于很多情况,可能希望排除一些文件/函数/部分代码,不对其进行代码覆盖分析(不在报告上显示其不考虑计算其覆盖率),这些场合很多,比如一些调试的函数、比如测试的时候部分测试案例不进行覆盖测试等等,总之,这个功能是常见的。

A、文件级别设置包含和排除

codecov提供了-comp选项,其参数为一个txt文件,在文件内,列举出要参与覆盖的"文件名"和要排除的“文件名"即可。在"文件名"前面加上~表示排除,否则就是包含了。

注意:如果使用了-comp指定了一个txt文件,而文件为空,就会报错了,因为不对任何文件进行覆盖分析,是没有意义的!

说明:这里"文件名"使用了引号,因为这里的文件名不是真正的文件名,只需要真正的文件名包含此字符串即可,包括相对路径的子文件夹包含此字符串的情况,具体而言如下:

source

~skip

那么,类似于source.cpp,source1.cpp, asource.cpp, asource1.cpp, source/1.cpp, test/source/2.cpp,sourcexxx/1.cpp等等的文件都会参与到代码覆盖中。

同样,类似于skip.cpp, skip1.cpp, askip.cpp, askip1.cpp, skip/1.cpp, test/skip/3.cpp,skipxxx/2.cpp等等的文件都会从代码覆盖中排除。

选项的使用就是:codecov -comp myComp.txt,比较容易理解。

B、行和函数级别的排除

可以对代码中的某行或某函数进行排除,方法是利用注释,但是对注释有一定的要求,先分析对行的排除,选项是-onelinedsbl

codecov -onelinedsbl "NO COVER" -onelinedsbl "NO_COVER"
这个选项可以使用多个,表示对于具备这些”特点“(下面会分析什么特点)的行都会排除。其参数为一个字符串,表示,对于某一行,如果其注释中 包含此字符串,那么该行

就会被排除掉,注意,这里是"仅"包含,那么注释中不能有其它任意字符,除非是在注释中此字符串前面或后面有一些空格或tab是可以的,但是空格或tab不能在单词中间,另外,这也是区分大小写的,注释可以使用//或者/**/,但是不能跨行。总之,是严格匹配的。另外,如果字符串只是一个单词,那么可以省略掉上面的-onelinedsbl中的分号(即上面的命令等同于codecov -onelinedsbl "NO COVER" -onelinedsbl NO_COVER)。看下面的例子(其中浅绿色表示被排除的代码):

使用Intel编译器(5)PGO(5)PGO工具之代码覆盖工具(code coverage)2_第6张图片

PS:另外还有一点,内联的注释(/**/),比如在行尾。比如printf(/*NO COVER*/"%d",ii);也是不会被排除的。

对于整个函数的排除,函数的排除和行排除的方法是一样的,选项也是一样的,可以一起使用。如果要排除一个函数,那么注释需要在函数名所在行。比如,下面的写法:

void foo() { // NO_COVER
}

void foo() // NO_COVER
{
}

void foo()
{	// NO_COVER
}
其中,第一和第二种方法都是排除整个函数,第三种方法只会排除一行。
C、定义任意边界进行排除

理解了上面的行排除的方法,任意边界就很容易理解了。利用-beginblkdsbl和-endblkdsbl两个选项分别定义开始和结束的注释要求,那么其间的代码就会从覆盖中排除。这两个选项对注释的要求和上面的行注释一样。


6. 差异覆盖Differential Coverage

用下面的例子来说明差异覆盖:

// File: differential_coverage.cpp
#include <stdio.h>

void foo()
{
	printf("This is foo\n");
}

void bar()
{
	printf("This is bar\n");
}

void foobar()
{
	printf("This is foobar\n");
}

int main(int argc, char* argv[])
{
	int count = argc;
	switch(count)
	{
		case 0:
			printf("Case0\n");
			break;
		case 1:
			printf("Case1\n");
			foo();
			break;
		case 2:
			printf("Case2\n");
			bar();
			break;
		case 3:
			printf("Case3\n");
			foo();
			bar();
			break;
		case 4:
			printf("Case4\n");
			foobar();
			break;
	}
}

@echo OFF
rem #File: build.bat
set exeargs= arg1 arg2
rem #can set env vars in command line or in this file before run this bat file.

if exist *.obj del *.obj
if exist *.exe del *.exe
if exist *.dyn del *.dyn
if exist *.lock del *.lock
if exist *.dpi del *.dpi
if exist *.spi del *.spi
if exist *.spl del *.spl

icl differential_coverage.cpp -o differential_coverage.exe /Od /Qprof-gen:srcpos /nologo
differential_coverage.exe %exeargs%
profmerge

if exist *.html (
	del *.html
	rmdir /S /Q CodeCoverage 
)
使用build.bat编译、运行、合并得到dpi文件等。分别修改build.bat使用"set exeargs="、"set exeargs= arg1"、"set exeargs= arg1 arg2"、"set exeargs= arg1 arg2 arg3"对应代码会执行case1234的内容,case0是不可能执行到的,得到对应的spi和dpi文件,最后得到的文件如下(删除了无关文件):

differential_coverage.cpp
pgopti1.dpi
PGOPTI1.SPI
pgopti2.dpi
PGOPTI2.SPI
pgopti3.dpi
PGOPTI3.SPI
pgopti4.dpi
PGOPTI4.SPI
PS:事实上,spi文件是一个简单的文本文件,对于这里的四个spi文件内容是相同的,spi文件在编译的第一步就得到了,既然这样,就是用其中任意一个,还是命名为PGOPTI.SPI这样就不用在命令行指定-spi选项了。

为了理解差异覆盖,下面是执行case1(即"set exeargs="时)得到的dpi和spi文件对应的报告截图(codecov -dpi pgopti1.dpi):

使用Intel编译器(5)PGO(5)PGO工具之代码覆盖工具(code coverage)2_第7张图片

这个效果很容易理解了,main中只有case1的内容被覆盖了,其余的都没有覆盖,其它函数只有foo()覆盖了,对于执行case234的时候的报告也是类似的。下面来看什么是差异覆盖和其报告是怎样的。

差异覆盖用于比较一个程序两次运行的覆盖的不同,一次参考运行(reference run)和一次新的运行(new run),用于标识新的运行覆盖了但是参考运行没有覆盖的部分

选项:

很显然,既然是比较两次运行的差异,那么dpi文件就需要两份了(spi文件不需要两份),使用-ref选项指定参考运行(即旧的运行)得到的dpi文件,然后和当前新的运行的dpi进行比较。

实例:将执行case1的运行作为参考,执行case2的运行作为新的运行,使用codecov的选项为:

codecov -dpi pgopti2.dpi -ref pgopti1.dpi [-spi PGOPTI.SPI](-spi可以省略,当然,前提是存在pgopti.spi这个默认的文件)

其得到的报告的differential_coverage.cpp部分的结果部分截图如下(理解了这部分,报告的其它部分也就理解了):

使用Intel编译器(5)PGO(5)PGO工具之代码覆盖工具(code coverage)2_第8张图片

其中case2标记为“未覆盖”的颜色,bar()被标记为"未覆盖"的函数,如果不使用-ref,那么“新的运行”对应的应该是执行了case2,报告的颜色应该是刚好相反。那么,这里的颜色是如何标记的,如何理解差异覆盖中的“cover”和“uncover”呢?上面的红色描述已经大概表述了(用于标识新的运行覆盖了但是参考运行没有覆盖的部分),其更具体一点的描述是:两次运行的代码有同样的属性(covered或not covered),被认为是covered的代码。否则,如果新的运行执行了的代码(即covered),但是参考运行中这段代码没有被执行(即not covered),那么这段代码作为uncovered处理。同时,如果代码在参考运行中覆盖了,但是在新的运行没有被覆盖,差异覆盖会将其显示为覆盖(covered)了。这段话很纠结,但是很清晰,对比这段话,就很容易判断上面的覆盖和未覆盖是如何判断的了。比如bar函数,在参考运行中没有覆盖,新的运行覆盖了,所以显示为未覆盖。main函数中case2的部分也是一样。另外,main函数中中case1的部分和foo函数的部分,由于在参考运行中覆盖了,新的运行没有覆盖,显示为覆盖。至于main中的其他部分和foobar等函数,由于两次运行都没有覆盖(属性一样),所以显示为覆盖了。

可以修改-dpi和-ref的dpi文件,分别进行两两对比来理解差异覆盖是如何处理的。


(2) 导出代码覆盖报告数据

前面都是默认的输出,为HTML的报告,还有一些选项用于导出一些覆盖报告数据的。有两种方式:

1. 快速导出数据(Quick export):可以导出xml或txt格式的数据,不输出默认的html报告。

2. 混合导出数据(Combined export):导出xml或txt格式的数据的同时,仍然输出默认的html报告。

Quck export选项:

-xmlfcvrg和-txtfcvrg:分别指定导出的xml文件和txt文件的文件名,两者可以同时使用,这两个选项是函数级别的数据。

-xmlbcvrg和-txtbcvrg:同上,这两个选项是block基本块级别的数据,所以数据更详细。

Combined export选项:

-xmlbcvrgfull和-txtbcvrgfull:指定导出xml文件和txt文件的文件名,默认的html报告仍然会输出。这两个选项都是基本块级别的数据,数据可能更详细。

Dynamic Call Graphs选项:

还有一个选项用于输出动态调用图表的信息。-txtdcg,指定输出的call graph的文件名,是一个txt文件,其信息包含了代码中程序、模块、函数级别的静态调用和动态调用(direct、indirect、virtual)比例的相关数据。

PS:这些导出数据的选项,可能更多的是用于利用这些数据去自己构建一些其它的工具,输出的都是xml或txt格式的数据。


关于codecov工具,这里需要补充一点:

使用prof-use选项生成dpi文件的时候,要注意,它是将目录下所有的dyn文件都合并为dpi,所以如何多次运行辅助可执行文件,那么就会导致产生多份dyn文件,如果每次运行没有删除dyn文件,得到的dpi文件就包含了多次运行的dyn文件,然后利用dpi生成的代码覆盖报告可能和预料的有些地方是不一样的(比如统计基本块的执行次数,就会翻倍)。总之,注意这一点就可以了。


说明:上面用到的大部分截图的测试程序可以到http://dl.dbank.com/c00wsrujbq下载查看。当然,不需要程序也足以理解这些内容了,而且程序也是我根据要说明的功能不断修改过的。

你可能感兴趣的:(html,xml,测试,工具,reference,编译器)