2015-04-27 97 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之前被调用。