Linux下代碼覆蓋率工具:gcov
對於C/C++軟件開發,常常需要通過代碼覆蓋率報告來了解測試用例的場景覆蓋情況,對於測試用例未覆蓋的代碼分支流程,需要補充用例,以保證測試用例的全面性與完整性,不漏測任何一個分支BUG。Linux下可用gcov工具生成覆蓋率統計信息,然後藉助gcov的圖形化工具lcov,可生成html格式的代碼覆蓋率報告,進一步提高覆蓋率測試結果的可讀性。
1、Gcov實現原理
Gcc中添加-ftest-coverage編譯選項後,則:
1)輸出目標文件中留出一段存儲區用於保存統計數據;
2)源代碼中每一行可執行語句生成的代碼之後注入一段更新覆蓋率統計結果的代碼;
3)在可執行進程文件中,進入main函數之前調用 gcov_init初始化統計數據區,並將gcov_exit註冊爲 exit handlers;
4)用戶側代碼調用exit 時,將調用gcov_exit,其繼續調用__gcov_flush函數輸出統計數據到*.gcda文件中。
從gcov實現原理可知,若用戶進程並未調用 exit退出,就得不到覆蓋率統計數據,也就無從生成報告了。而後臺服務程序一旦啓動很少主動退出。爲了解決這個問題,我們可以給待測程序註冊一個信號處理函數(signal handler),處理SIGINT、SIGQUIT、SIGTERM等常見強制退出信號,並在信號處理函數中主動調用exit或 __gcov_flush函數,以便輸出統計結果。
上述方案需要修改待測程序代碼,可以借用動態庫預加載技術和gcc擴展的constructor屬性,將signal handler和註冊過程都封裝到一個獨立的動態庫中,並在預加載動態庫時實現信號攔截註冊。這樣,就可以通過如下命令行來實現異常退出時的統計結果輸出:
|
LD_PRELOAD=./gcov_preload.so ./test |
測試完成後,直接kill掉test進程,即可獲得正常的統計結果文件*.gcda。其中,gcov_preload.so是你自己寫的一個程序,接受SIG,然後exit,以觸發gcov進行輸出。
接下來,我們詳細說明gcov的使用步驟。
Gcov屬於gcc工具集之一,是隨gcc一起發佈的,並不需要獨立安裝。lcov需要自己下載開源軟件安裝。Lcov開源軟件的下載地址:
https://sourceforge.net/projects/ltp/files/Coverage%20Analysis/
以lcov-1.13.tar.gz爲例,安裝步驟比較簡單:
tar –zxvf lcov-1.13.tar.gz
cd lcov-1.13/
make install
lcov可執行二進制默認安裝在/usr/local/bin目錄下,若該路徑不在環境變量$PATH中,可先用export PATH=$PATH: /usr/local/bin添加進來,以便後續使用。
2、編譯鏈接
爲了生成生成覆蓋率統計信息,需要在編譯時添加編譯選項:
編譯選項CFLAGS += -fprofile-arcs -ftest-coverage,或CFLAGS += --coverage,其中-fprofile-arcs用於生成.gcno文件,-ftest-coverage用於生成.gcda文件。
鏈接選項LDFLAGS += -lgcov,注:此選項僅在使用ld鏈接時需要,使用gcc時不需要。
舉例說明,編輯一個test.c文件,內容如下:
#include <stdio.h>
#include <stdlib.h>
int func_1(char *ptr)
{
if (NULL == ptr)
{
printf("func_1:ptr==NULL!\n");
return 1;
}
printf("func_1: ptr: %p\n", ptr);
return 0;
}
void func_2(char *ptr)
{
printf("func_2: ptr: %p\n", ptr);
}
void func_3(void)
{
printf("func_3...\n");
}
void main(int argc, char *argv[])
{
char *ptr = NULL;
func_1(ptr);
ptr = (char *)malloc(8);
func_2(ptr);
free(ptr);
return;
}
執行gcc編譯命令:
[root@HLZ gcov]# gcc -fprofile-arcs -ftest-coverage test.c -o test
[root@HLZ gcov]# ls
lcov-1.13 test test.c test.gcno
編譯生成test可執行程序的同時,也生成了同名的.gcno文件。
3、運行
執行test程序,生成同名的.gcda文件:
[root@HLZ gcov]# ./test
func_1:ptr==NULL!
func_2:ptr: 0x99b5008
[root@HLZ gcov]# ls
lcov-1.13 test test.c test.gcda test.gcno
執行gcov命令,生成覆蓋率信息文件,即.gcov文件:
[root@HLZ gcov]#gcov test.c
File'test.c'
Linesexecuted:73.68% of 19
test.c:creating'test.c.gcov'
[root@HLZ gcov]# ls
lcov-1.13 test test.c test.c.gcov test.gcda test.gcno
test.c.gcov就是代碼覆蓋信息文件,但看起來並不直觀,下面我們進一步分析。
4、覆蓋率分析
覆蓋率分析過程分兩步。
首先,藉助lcov對覆蓋率文件test.c.gcov進行改造,生成了test.info文件:
[root@HLZ gcov]#lcov -c -d . -o 'test.info' -b .
Capturing coverage data from .
Found gcov version: 4.4.7
Scanning. for .gcda files ...
Found 1 data files in .
Processing test.gcda
Finished .info-file creation
[root@HLZ gcov]# ls
lcov-1.13 test test.c test.gcda test.gcno test.info
其次,通過genhtml工具(lcov工具集中帶有)生成result文件夾,其中就包含index.html,可以直接雙擊打開查看:
[root@HLZ gcov]# genhtml -o result test.info
Reading data file test.info
Found 1 entries.
Found common filename prefix "/home/hanlzh/test"
Writing .css and .png files.
Generating output.
Processing file gcov/test.c
Writing directory view page.
Overall coverage rate:
lines......: 73.7% (14 of 19 lines)
functions..: 75.0% (3 of 4 functions)
[root@HLZ gcov]# ls
lcov-1.13 result test test.c test.gcda test.gcno test.info
[root@HLZ gcov]# ls result/
amber.png emerald.png gcov gcov.css glass.png index.html index-sort-f.html index-sort-l.html ruby.png snow.png updown.png
最後,查看結果:
1)在windows界面下,雙擊index.html,顯示目錄級別下的覆蓋率統計信息;
2)進一步點擊目錄gcov,顯示文件級別的覆蓋率統計信息;
3)進一步點擊文件test.c,顯示文件內分支語句的覆蓋率詳細信息;
顯然地,標紅行是執行./test時沒有執行到的語句,可以爲我們進一步優化代碼或補充測試用例提供了參考。
5、總結
Lcov和genhtml的功能比較豐富,選項參數比較多,感興趣的讀者可以通過man lcov和man genhtml查閱。