【问题标题】:Find unexecuted lines of c++ code查找未执行的 C++ 代码行
【发布时间】:2015-07-06 08:35:02
【问题描述】:

作为单元测试的一部分,我想确保测试的代码覆盖率。目的是在代码中的某处放置类似 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() 之前执行,可以在函数体内完成吗?

【问题讨论】:

  • 研究编译器的警告和命令行选项。许多编译器具有识别“死代码”的能力。
  • TL;DR;为什么不使用像gcov 这样的覆盖分析代码注入并分析结果,例如使用lcov?如果不是从您的测试场景中执行,那可能不一定真正批准死代码。静态代码分析工具可能会更好地找到这些死的东西。
  • @πάνταῥεῖ gcov 非常详细 - 但在我并不关心的代码部分也是如此。放置像REQUIRE_TEST 这样的标记可以让我指定我关心的部分。对于我将库交付给的每个人来说,这也是另一个依赖项 - 感觉 C++几乎 能够做我想做的事......如果我的任何尝试成功,我将拥有我想要的一切.也许像gcov 这样的东西最终将是唯一的解决方案 - 但我想知道在“放弃”并添加另一个依赖项之前我正在尝试的事情是不可能的......

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


【解决方案1】:

一个丑陋但简单的方法是REQUIRE_TEST 使用__LINE____COUNTER__ 来构造它所引用的唯一文件范围静态对象的名称,如果它没有'这将导致编译器错误' t 被宣布。然后,您需要预先手动声明所有此类对象,每个 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
    

    【讨论】:

    • 根据标准,编译器可以在 main 之前初始化本地静态变量——如果它愿意的话。但是用 0 初始化它并在宏中添加另一个增加这个数字的代码行应该可以正常工作。感谢您迭代此自定义部分的想法。我还将添加当前文件名,但它应该就是我想要的 =)
    • COV 宏用于内联函数时,您知道为什么会出现节类型冲突吗?
    【解决方案3】:

    杰里米给了我一个正确的想法,让我仔细看看这些部分。他的回答有效——但只有没有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 之前被调用。

    【讨论】:

      猜你喜欢
      • 2010-10-28
      • 1970-01-01
      • 1970-01-01
      • 2012-12-09
      • 2014-06-29
      • 2011-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多