CMake编译项目集成Gcov/Lcov代码覆盖率测试

来源:http://blog.csdn.net/lostaway/article/details/40948841

原文链接:http://blog.yeyuzhen.cn/?p=203

脑补链接:什么是CMake? 什么是代码覆盖率测试? 什么是 Gcov?

最近不怎么顺利的面试经历,让我觉得自己其实是一个2B程序员。应该是懒的原因,涉及到某技术的实现原理啥的,就觉得很烦人。能用就好,何必执着?所以,一个技术问题下来,都是“晚节不保”。自己更关心的是诸如“如何细粒度化任务,以提高开发效率?”、“如何实践代码覆盖率测试,提高程序的鲁棒性?”这类问题。唉~貌似逼格(项目经理)和现实(程序员)存在不小的差距。

日常开发中,我就很注重测试的环节——功能测试、性能测试。我常和组员说,“代码写的不好可以慢慢学,但是测试一定要用心,程序是一点点测出来的”,“能测出来的问题,不算Bug”。但是经过长期的“感性”测试,不免问自己,“测这些用例就OK了?”、“这块代码有没有问题?”、“测试要如何度量?”。后来了解到“代码覆盖率测试”这么个好东西,可以初步解决测试度量的问题。

完整演示项目Github地址:CMakeGcovSupport

[plain] view plain copy

  1. 初始项目目录结构:
  2. CMakeGcovSupport
  3. ├── CMakeLists.txt
  4. ├── bin
  5. ├── build
  6. ├── include
  7. │   └── name.h
  8. ├── libgreeting
  9. │   ├── CMakeLists.txt
  10. │   ├── include
  11. │   │   └── greeting.h
  12. │   └── src
  13. │       ├── CMakeLists.txt
  14. │       ├── greeting.cpp
  15. │       └── yelp.cpp
  16. └── src
  17.     ├── CMakeGcovSupport.cpp
  18.     ├── CMakeLists.txt
  19.     └── name.cpp

示例程序是个很简单的输出一行问候语的程序。为了演示复杂目录结构下CMake集成Gcov的方法,故意将输出问候语的函数单独放到了 libgreeting 静态库中。执行如下编译命令:

$ cd CMakeGcovSupport
$ mkdir build
$ cd build
$ cmake -DENABLE_COVERAGE=ON ..
$ gmake all

执行 CMake 外部编译之后,CMake 在 build 目录内为我们生成了 .gcno 文件:

[plain] view plain copy

  1. CMakeGcovSupport
  2. ├── CMakeLists.txt
  3. ├── bin
  4. │   └── CMakeGcovSupport
  5. ├── build
  6. │   ├── ……
  7. │   ├── libgreeting
  8. │   │   ├── ……
  9. │   │   └── src
  10. │   │       ├── CMakeFiles
  11. │   │       │   ├── ……
  12. │   │       │   ├── greeting.dir
  13. │   │       │   │   ├── ……
  14. │   │       │   │   ├── greeting.cpp.gcno
  15. │   │       │   │   ├── greeting.cpp.o
  16. │   │       │   │   ├── ……
  17. │   │       │   │   ├── yelp.cpp.gcno
  18. │   │       │   │   └── yelp.cpp.o
  19. │   │       │   └── ……
  20. │   │       └── ……
  21. │   └── src
  22. │       ├── CMakeFiles
  23. │       │   ├── ……
  24. │       │   ├── CMakeGcovSupport.dir
  25. │       │   │   ├── CMakeGcovSupport.cpp.gcno
  26. │       │   │   ├── CMakeGcovSupport.cpp.o
  27. │       │   │   ├── ……
  28. │       │   │   ├── name.cpp.gcno
  29. │       │   │   ├── name.cpp.o
  30. │       │   │   └── ……
  31. │       │   └── progress.marks
  32. │       └── ……
  33. ├── include
  34. ├── libgreeting
  35. └── src

为了避免接下来执行程序过程中,未覆盖的源码文件的覆盖率信息丢失,我们需要对覆盖率信息进行初始化操作:

$ cd CMakeGcovSupport
$ lcov -d build -z
$ lcov -d build -b . --no-external --initial -c -o CMakeGcovSupportInitialCoverage.info

然后我们执行 bin 中的 CMakeGcovSupport, main() 函数中将会调用 Greeting() 和 Name()  函数,而不会调用到 Yelp() 函数。

$ cd CMakeGcovSupport
$ cd bin
$ ./CMakeGcovSupport
$ Hello, gcov.

这时,我们去 .gcno 所在目录,会看到有同名的 .gcda 覆盖率数据文件生成了。执行以下命令,生成覆盖率测试报告:

$ cd CMakeGcovSupport
$ lcov -d build -b . --no-external -c -o CMakeGcovSupportCoverage.info
$ genhtml -o CMakeGcovSupportCoverageReport --prefix=`pwd` CMakeGcovSupportInitialCoverage.info CMakeGcovSupportCoverage.info

用浏览器打开 CMakeGcovSupportCoverageReport 目录中的 index.html 查看覆盖率报告(Mac + Lcov1.10):

Linux + Lcov1.11覆盖率结果(不会误包含外部头文件覆盖率信息):

以上就是CMake项目初步集成 Gcov/Lcov 的方式。但是,还未深度集成到 CMake 编译过程中,而且覆盖率报告还存在一些瑕疵。下一步计划解决应用“–no-external”选项之后依旧会包含外部头文件覆盖率信息的问题,以及不显示头文件覆盖率信息的问题。最终深度集成 CMake 的效果希望是自定义如下命令:

$ gmake InitialCoverage   # 初始化覆盖率信息命令
$ gmake ReportCoverage  # 生成覆盖率测试报告命令

 

参考

[1] 测试覆盖(率)到底有什么用?

[2] Linux 下 C/C++ 项目代码覆盖率的产生方法

[3] User Manuals – lcov

[4] gcov lcov 覆盖 c/c++ 项目入门

[5] 使用gcov,lcov,genhtml进行代码覆盖率测试

[6] 关于C++ code coverage tool 的研究(1)

[7] 关于C++ code coverage tool 的研究(2)—GCOV 实现原理

[8] 关于C++ code coverage tool 的研究(3)—gcov使用实例

[9] CMake添加gcov代码覆盖测试支持

[10] Linux平台代码覆盖率测试工具GCOV简介

编辑历史:

V1.1,增加 Linux + Lcov1.11 下覆盖率报告截图,@2014-11-09

V1.0,初稿,@2014-11-08

gcov、lcov与genhtml 使用心得

来源: http://blog.sina.com.cn/s/blog_7e4ac8b501018b27.html

gcc是linux平台下的C、C++ 编译器

gcov是配合gcc产生覆盖信息报告的工具;

lcov是将gcov产生的报告信息,以更直观的方式显示出来工具

基本的使用方法分为4个阶段:

(一)、gcc编译:产生插装后的目标文件test、gcov结点文件 test.gcno

  #gcc –fprofile-arcsftest-coverage -o test test.c

  # ls

  test   test.c   test.gcno

  说明:参数 fprofile-arcsftest-coverage 告诉gcc编译器:(1)在目标文件test 插装跟踪代码;(2)生成供gcov使用 test.gcno [gcov node 文件]。

        因此,这里的生成的目标文件比正常编译的文件大。

(二)、运行目标文件:收集运行覆盖信息 test.gcda

   # ./test

     Success  — 这里是运行结果。

   # ls

     test test.c test.gcno test.gcda

  这里test.gcda运行结果,

(三)、gcov产生报告信息: test.c.gcov

  #gcov  test.c

    File ‘test.c’

    Lines executed: 87.50% of 8

    test.c: creating ‘test.c.gcov’

 #ls

    test test.c test.c.gcov test.gcda test.gcno

(四)、lcov:格式化test.c.gcov ,输出到 test.info文件

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

  说明:

       -d  . :参数 d指路径, “.” 指当前路径

       -t  “name” :指目标文件,这里 是 test

       -o  “filename” :输出格式化后的信息文件名

(五)、genhtml:根据信息文件(.info)产生html 文档,输出到一个文件夹中

   #genhtml -o result test.info

  说明: -o  directory :参数o (output)后面跟路径名称,在当前目录下创建指定目录,本例中是result

 

至此: 可以在result目录中打开index.html 浏览覆盖信息

RPM Commands

Reference: http://www.cnblogs.com/xiaochaohuashengmi/archive/2011/10/08/2203153.html

RPM是RedHat Package Manager(RedHat软件包管理工具)类似Windows里面的“添加/删除程序”

rpm 执行安装包
二进制包(Binary)以及源代码包(Source)两种。二进制包可以直接安装在计算机中,而源代码包将会由RPM自动编译、安装。源代码包经常以src.rpm作为后缀名。

常用命令组合:

-ivh:安装显示安装进度–install–verbose–hash
-Uvh:升级软件包–Update;
-qpl:列出RPM软件包内的文件信息[Query Package list];
-qpi:列出RPM软件包的描述信息[Query Package install package(s)];
-qf:查找指定文件属于哪个RPM软件包[Query File];
-Va:校验所有的RPM软件包,查找丢失的文件[View Lost];
-e:删除包

rpm -q samba //查询程序是否安装

rpm -ivh /media/cdrom/RedHat/RPMS/samba-3.0.10-1.4E.i386.rpm //按路径安装并显示进度
rpm -ivh –relocate /=/opt/gaim gaim-1.3.0-1.fc4.i386.rpm //指定安装目录

rpm -ivh –test gaim-1.3.0-1.fc4.i386.rpm    //用来检查依赖关系;并不是真正的安装;
rpm -Uvh –oldpackage gaim-1.3.0-1.fc4.i386.rpm //新版本降级为旧版本

rpm -qa | grep httpd      #[搜索指定rpm包是否安装]–all搜索*httpd*
rpm -ql httpd         #[搜索rpm包]–list所有文件安装目录

rpm -qpi Linux-1.4-6.i368.rpm #[查看rpm包]–query–package–install package信息
rpm -qpf Linux-1.4-6.i368.rpm #[查看rpm包]–file
rpm -qpR file.rpm       #[查看包]依赖关系
rpm2cpio file.rpm |cpio -div #[抽出文件]

rpm -ivh file.rpm  #[安装新的rpm]–install–verbose–hash
rpm -ivh

rpm -Uvh file.rpm #[升级一个rpm]–upgrade
rpm -e file.rpm #[删除一个rpm包]–erase

常用参数:

Install/Upgrade/Erase options:

-i, –install install package(s)
-v, –verbose provide more detailed output
-h, –hash print hash marks as package installs (good with -v)
-e, –erase erase (uninstall) package
-U, –upgrade=<packagefile>+ upgrade package(s)
--replacepkge 无论软件包是否已被安装,都强行安装软件包
–test 安装测试,并不实际安装
–nodeps 忽略软件包的依赖关系强行安装
–force 忽略软件包及文件的冲突

Query options (with -q or –query):
-a, –all query/verify all packages
-p, –package query/verify a package file
-l, –list list files in package
-d, –docfiles list all documentation files
-f, –file query/verify package(s) owning file

How to use RPM Commands

Reference: http://www.tldp.org/LDP/solrhe/Securing-Optimizing-Linux-RH-Edition-v1.3/chap3sec20.html

This section contains an overview of principal modes using with RPM for installing, uninstalling, upgrading, querying, listing, and checking RPM packages on your Linux system. You must be familiar with these RPM commands now because we’ll use them often in the continuation of this book. To install a RPM package, use the command:

                 [root@deep] /#rpm -ivh foo-1.0-2.i386.rpm

Take a note that RPM packages have a file of names like foo-1.0-2.i386.rpm, which include the package name (foo), version (1.0), release (2), and architecture (i386).

To uninstall a RPM package, use the command:

                 [root@deep] /#rpm -e foo

Notice that we used the package name foo, not the name of the original package file foo-1.0-2.i386.rpm.

To upgrade a RPM package, use the command:

                 [root@deep] /#rpm -Uvh foo-1.0-2.i386.rpm

With this command, RPM automatically uninstall the old version of foo package and install the new one. Always use rpm -Uvh to install packages, since it works fine even when there are no previous versions of the package installed.

To query a RPM package, use the command:

                 [root@deep] /#rpm -q foo

This command will print the package name, version, and release number of installed package foo. Use this command to verify that a package is or is not installed on your system.

To display package information, use the command:

                 [root@deep] /#rpm -qi foo

This command display package information; includes name, version, and description of the installed program. Use this command to get information about the installed package.

To list files in package, use the command:

                 [root@deep] /#rpm -qlfoo

This command will list all files in a installed RPM package. It works only when the package is already installed on your system.

To check a RPM signature package, use the command:

                 [root@deep] /#rpm  --checksig foo

This command checks the PGP signature of specified package to ensure its integrity and origin. Always use this command first before installing new RPM package on your system. Also, GnuPG orPgp software must be already installed on your system before you can use this command.

Linux下的tar压缩解压缩命令详解

来自:http://www.cnblogs.com/qq78292959/archive/2011/07/06/2099427.html

tar

-c: 建立压缩档案
-x:解压
-t:查看内容
-r:向压缩归档文件末尾追加文件
-u:更新原压缩包中的文件

这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个。下面的参数是根据需要在压缩或解压档案时可选的。

-z:有gzip属性的
-j:有bz2属性的
-Z:有compress属性的
-v:显示所有过程
-O:将文件解开到标准输出

下面的参数-f是必须的

-f: 使用档案名字,切记,这个参数是最后一个参数,后面只能接档案名。

# tar -cf all.tar *.jpg
这条命令是将所有.jpg的文件打成一个名为all.tar的包。-c是表示产生新的包,-f指定包的文件名。

# tar -rf all.tar *.gif
这条命令是将所有.gif的文件增加到all.tar的包里面去。-r是表示增加文件的意思。

# tar -uf all.tar logo.gif
这条命令是更新原来tar包all.tar中logo.gif文件,-u是表示更新文件的意思。

# tar -tf all.tar
这条命令是列出all.tar包中所有文件,-t是列出文件的意思

# tar -xf all.tar
这条命令是解出all.tar包中所有文件,-t是解开的意思

压缩

tar -cvf jpg.tar *.jpg //将目录里所有jpg文件打包成tar.jpg

tar -czf jpg.tar.gz *.jpg   //将目录里所有jpg文件打包成jpg.tar后,并且将其用gzip压缩,生成一个gzip压缩过的包,命名为jpg.tar.gz

tar -cjf jpg.tar.bz2 *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用bzip2压缩,生成一个bzip2压缩过的包,命名为jpg.tar.bz2

tar -cZf jpg.tar.Z *.jpg   //将目录里所有jpg文件打包成jpg.tar后,并且将其用compress压缩,生成一个umcompress压缩过的包,命名为jpg.tar.Z

rar a jpg.rar *.jpg //rar格式的压缩,需要先下载rar for linux

zip jpg.zip *.jpg //zip格式的压缩,需要先下载zip for linux

解压

tar -xvf file.tar //解压 tar包

tar -xzvf file.tar.gz //解压tar.gz

tar -xjvf file.tar.bz2   //解压 tar.bz2

tar -xZvf file.tar.Z   //解压tar.Z

unrar e file.rar //解压rar

unzip file.zip //解压zip

总结

1、*.tar 用 tar -xvf 解压

2、*.gz 用 gzip -d或者gunzip 解压

3、*.tar.gz和*.tgz 用 tar -xzf 解压

4、*.bz2 用 bzip2 -d或者用bunzip2 解压

5、*.tar.bz2用tar -xjf 解压

6、*.Z 用 uncompress 解压

7、*.tar.Z 用tar -xZf 解压

8、*.rar 用 unrar e解压

9、*.zip 用 unzip 解压