gcov lcov进行 android apk项目的 code coverage 代码覆盖率检测
最近在做code coverage,看了很多博客,大都说到了最基本的一些要求,
比如在用gcc编译的时候加入
CFLAGS=-fprofile-arcs -ftest-coverage
和LDFLAGS=-lgcov -lgcc
参数,就可生成*.gcno文件,
然后运行生成的可执行文件,就会在*.gcno文件对应目录下输出*.gcda文件,
然后运行gcov *.cpp
会生成*.cpp.gcov
文件,
查看可看到该cpp里的语句执行了几次,然后运行
lcov --capture --directory . --output-file testHtml.info --test-name testHtml; genhtml -o result testHtml.info
可生成result文件夹,打开里面的index.html即可看到结果。
但是这是对于一般的c/c++项目,而且是在编译的机器上直接运行可执行文件,如果我是把可执行文件放到别的电脑上运行,又或者我不是运行一个简单的类似于“hello world!”
的可直接在终端运行的程序,而是一个需要在android
系统里运行的apk
,那这样的话,运行可执行文件就无法输出*.gcda
文件了,很多博客里并没有提到这一特殊情况,或者说没有讲的很明白,让初学者无从下手,这时该怎么办呢?
一、如果在别的电脑上运行,有两种方法:
①设置环境变量,gcov
可设置两个环境变量来指定*.gcda
文件的输出路径:GCOV_PREFIX, GCOV_PREFIX_STRIP
。在终端输入export GCOV_PREFIX=/home/$user/a/
运行程序后将在/home/$user/a/
下输出*.gcda
文件,并且目录和*.gcno
文件保持一致,如果同时在终端输入export GCOV_PREFIX_STRIP=$n
,那么*.gcda
的目录就会减少$n层,一般不用设置这个环境变量。
②在编译源码的时候加入另一个参数-fprofile-dir
(和-fprofile-arcs -ftest-coverage
放在相同位置),我几乎没在别的博客里看到别人有说到这个参数,但这个参数却非常有用,可参见https://gcc.gnu.org/onlinedocs/gcc/Gcov-Data-Files.html#Gcov-Data-Files。
通过指定-fprofile-dir=/your/path/
,编译运行后(不管是在编译的电脑还是其他电脑上),可在/your/path/
(要确保运行的电脑上存在这个目录,不然程序没有权限创建这个目录,需要以root模式运行,否则无法生成*.gcda
文件)看到输出的*.gcda
文件。
二、如果编译的是android apk
:
这时需要把apk
安装到手机上运行,手机目录跟编译电脑目录不同,所以可用上面②
的方法指定输出目录,但还有一个问题,生成*.gcda
文件的前提是程序运行结束正常退出,即return 0;
,而有的apk
没有退出功能,或者不允许退出,比如launcher
,这时就无法生成*.gcda
文件,那该怎么办呢?
没事,我们可以在源码里加入信号处理函数,通过捕获到(SIGTERM)kill
等信号来手动调用__gcov_flush()
函数生成*.gcda
文件(这个函数很多博客有提到,具体实现我也不清楚)。代码如下(可能需要放在android_main
函数入口,即MainActivity
):
//add by hfq
#include <signal.h>
extern "C" void __gcov_flush();
void sigfunc(int signo)
{
__gcov_flush();
}
extern void android_main(struct android_app* state)
{
signal(SIGTERM,sigfunc); //set siginal
......
}
编译运行后,用adb kill $pid
将进程杀死(可能需要有root权限),即可在指定目录下找到输出的*.gcda
文件,然后将这些文件拷贝到编译的电脑上对应的*.gcno
目录下,运行lcov
命令,即可。
不过有可能*.gcda
的目录和*.gcno
目录不再对应了(我的出现了这种情况,不知道为什么),我写了一个脚本,可把*.gcda
拷贝到对应的*.gcno
目录下:
#!/bin/bash
source /etc/profile
set -x
gcno_path="/disk_build/kodi"
gcda_path="/disk_build/Movies"
find $gcno_path -name "*.gcno" > gcno_path.txt
find $gcda_path -name "*.gcda" > gcda_path.txt
cp gcda_path.txt gcda.txt
sed -i 's/\//\n/g' gcda_path.txt
sed -n '/gcda/w out.txt' gcda_path.txt
mv out.txt gcda_path.txt
sed -i 's/.gcno/.gcda/g' gcno_path.txt
while read da_path
do
grep $da_path gcno_path.txt > no_path.txt
if [ $? = 0 ]
then
read no_path < no_path.txt
grep $da_path gcda.txt > da_path.txt
read da_path < da_path.txt
cp $da_path $no_path
fi
done < gcda_path.txt
rm gcda_path.txt gcno_path.txt no_path.txt da_path.txt gcda.txt
这样就算结束了。