2015-04-27 95 views
6

作爲我單元測試的一部分,我希望確保代碼覆蓋測試。目的是在代碼的某處放置類似REQUIRE_TEST的宏,並檢查是否調用了所有這些宏。查找未執行的C++代碼行

void foo(bool b) { 
    if (b) { 
    REQUIRE_TEST 
    ... 
    } else { 
    REQUIRE_TEST 
    ... 
    } 
} 

void main() { 
    foo(true); 

    output_all_missed_REQUIRE_macros(); 
} 

理想情況下,輸出將包括宏的源文件和行。

我最初的想法是讓宏創建,將自己註冊在一些地圖,後來檢查是否所有這些被稱爲

#define REQUIRE_TEST \ 
     do { \ 
      static ___RequiredTest requiredTest(__func__, __FILE__, __LINE__);\ 
      (void)requiredTest;\ 
      ___RequiredTest::increaseCounter(__func__, __FILE__, __LINE__);\ 
     } while(false) 

但靜態對象只創建靜態對象時,代碼被稱爲第一次。所以地圖只包含在下一行中計算的函數 - 找不到REQUIRE_TEST宏。在這種情況下,__attribute__((used))被忽略。

gcc的有一個很好的屬性__attribute__((constructor)),但顯然在這裏放置時選擇忽略它(以下代碼,而不是靜態對象)

struct teststruct { \ 
    __attribute__((constructor)) static void bla() {\ 
    ___RequiredTest::register(__func__, __FILE__, __LINE__); \ 
    } \ 
};\ 

以及用於

[]() __attribute__((constructor)) { \ 
    ___RequiredTest::register(__func__, __FILE__, __LINE__); \ 
};\ 

唯一我現在可以想到的是a)手動(或通過腳本)分析常規編譯之外的代碼(uargh)或b)使用宏來計算宏 - 但之後我不知道哪些特定的REQUIRE_TEST宏是不是calle d ...(如果別人決定使用__COUNTER__宏以及一切休息...)

是否有解決這個問題的任何解決方案,得體?我錯過了什麼?它 會很高興有一個宏附加當前行和文件 所以一些預處理器變量,只要它被調用 - 但這不是 可能,對吧?有沒有其他的方法可以在main()之前執行的 之類的東西可以在函數體內完成?

+0

研究編譯器的警告和命令行選項。許多編譯器都有能力識別「死代碼」。 –

+8

TL; DR;爲什麼不使用像''gcov'](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html)這樣的覆蓋率分析代碼注入並分析結果,例如使用['lcov'](http://ltp.sourceforge.net/coverage/lcov.php)?如果_dead code_沒有從您的測試場景中執行,那麼這可能不一定真正批准。一個靜態代碼分析工具可能會更好地找到這種死的東西。 –

+0

@πάνταῥεῖ'gcov'非常詳細 - 但也代碼部分我不太在意。像「REQUIRE_TEST」這樣的標記可以讓我指定我關心的部分。它也將成爲我的圖書館所有人的另一個依賴 - 並且它感覺像C++幾乎可以做我想做的事情......如果我的任何嘗試工作,我會擁有我想要的一切。也許像'gcov'這樣的結果將是唯一的解決方案 - 但我想知道,我所嘗試的是不可能的,然後再「放棄」並添加另一個依賴項... – example

回答

1

醜陋,但簡單的方法是REQUIRE_TEST使用__LINE____COUNTER__構建它指的是一種獨特的文件範圍的靜態對象的名稱,這將導致一個編譯器錯誤,如果它尚未聲明。然後,您需要手動聲明所有這些對象,每個對象都有一個REQUIRE_TEST - 但是如果您還沒有這樣做,至少會出現編譯錯誤。

嘿,我說這很醜!

2

如何:

#include <iostream> 

static size_t cover() { return 1; } 

#define COV() do { static size_t cov[2] __attribute__((section("cov"))) = { __LINE__, cover() }; } while(0) 

static void dump_cov() 
{ 
     extern size_t __start_cov, __stop_cov; 

     for (size_t* p = &__start_cov; p < &__stop_cov; p += 2) 
     { 
       std::cout << p[0] << ": " << p[1] << "\n"; 
     } 
} 

int main(int argc, char* argv[]) 
{ 
     COV(); 

     if (argc > 1) 
       COV(); 

     if (argc > 2) 
       COV(); 

     dump_cov(); 
     return 0; 
} 

結果:

$ ./cov_test 
19: 1 
22: 0 
25: 0 

和:

$ ./cov_test x 
19: 1 
22: 1 
25: 0 

和:

$ ./cov_test x y 
19: 1 
22: 1 
25: 1 

基本上,我們在一個命名的內存部分(顯然我們使用了GCC特定的機制)建立了一個覆蓋數組,我們在執行後轉儲它。

我們依賴於在啓動時執行的本地靜態的持續初始化 - 將覆蓋標誌設置爲零的覆蓋數組中的行號 - 以及在首次執行語句時執行cover()的函數初始化,它爲被執行的行設置覆蓋標誌爲1。我不是100%確定所有這些都是由標準保證的,也不是由哪個版本的標準(我使用--std=c++11編譯)保證的。

最後, '-O3' 建設也產生正確的結果(儘管以不同的順序分配):

$ ./a 
25: 0 
22: 0 
19: 1 

和:

$ ./a x 
25: 0 
22: 1 
19: 1 

$ ./a x y 
25: 1 
22: 1 
19: 1 
+0

根據標準,編譯器是允許在主要之前初始化本地靜態 - 如果它希望這樣做的話。但用0初始化它,並添加另一個代碼行,在宏中增加這個數字應該工作得很好。感謝您的想法迭代這個自定義部分。我也將添加當前的文件名,但它應該幾乎是我想要的=) – example

+0

你有什麼想法,當'COV'宏用於內聯函數時,爲什麼這會產生節類型衝突? – example

1

傑里米給了我一個正確的想法,仔細看看這些部分。他的答案有效 - 但只有沒有inline函數。通過一些更多的研究,我能夠找到以下解決方案,它與gcc相關(由於部分的名稱),但更靈活(並且在內聯函數中工作)。宏現在如下:

#define REQUIRE_TEST \ 
    do { \ 
     struct ___a{ static void rt() {\ 
      ___RequiredTest::register_test(__FILE__, __LINE__);\ 
     } };\ 
     static auto ___rtp __attribute__((section(".init_array"))) = &___a::rt; \ 
     (void) ___rtp; \ 
     ___RequiredTest::increase_counter(__FILE__, __LINE__); \ 
    } while(false) 

配售函數指針的部分.init_array實際上其放置在正在前主叫的初始化函數列表。這樣就可以確定,本地定義的函數在main之前被調用。