查找未执行的c ++代码行

时间:2015-04-27 18:31:57

标签: c++ c++11 macros g++ code-coverage

作为我的单元测试的一部分,我想确保测试的代码覆盖率。目的是在代码中的某处放置REQUIRE_TEST宏,并检查是否所有这些都被调用。

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)使用__COUNTER__宏来计算宏 - 但是我不知道哪些特定的REQUIRE_TEST宏没有被调用...(如果其他人决定使用__COUNTER__宏,一切都会中断...)

  

这个问题有没有合适的解决方案?我错过了什么?它   有一个附加当前行和文件的宏会很高兴   所以每当调用它时都会有一些预处理器变量 - 但事实并非如此   可能,对吗?是否还有其他方式可以注册   在main()之前执行,可以在函数体内完成吗?

3 个答案:

答案 0 :(得分:2)

这个怎么样:

@Override
public boolean onTouch(View v, MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_UP) {
        retrieveMQTTConnAssets();
    }
    return true;
}

结果:

#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

基本上,我们在命名的内存部分设置了一个coverage数组(显然我们已经使用了GCC特定的机制),我们在执行后转储它。

我们依赖于在启动时执行的局部静态的不断初始化 - 它将行号放入覆盖数组中,覆盖标志设置为零 - 并且通过在第一次执行时执行的函数调用$ ./cov_test x y 19: 1 22: 1 25: 1 进行初始化语句的设置,将执行行的coverage标志设置为1。我不是100%确定所有这些都是由标准保证,也不是由标准的哪个版本保证(我使用cover()编译)。

最后,使用'-O3'构建也会产生正确的结果(尽管以不同的顺序分配):

--std=c++11

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

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

答案 1 :(得分:1)

一种丑陋但简单的方法是REQUIRE_TEST使用__LINE____COUNTER__来构造它所引用的唯一文件范围静态对象的名称,这将导致如果没有声明,编译器错误。然后,您需要预先手动声明所有此类对象,每个REQUIRE_TEST一个 - 但至少如果您没有这样做,则会出现编译错误。

嘿,我说这很难看!

答案 2 :(得分: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之前调用的初始化函数列表中。这样可以确保在main之前调用本地定义的函数。