【问题标题】:Detecting specific function calls in unit tests在单元测试中检测特定的函数调用
【发布时间】:2018-06-11 04:44:05
【问题描述】:

我希望能够检测我的函数(或它调用的任何其他函数)是否最终会在我的单元测试中调用某些特定函数(例如,mallocfree):我的软件有硬实时要求,我想确保没有人在这些函数中添加会意外触发分配的东西(并让我的 CI 管道自动检查它)。

我知道我可以在 gdb 上放一个断点,但理想情况下我想做这样的事情:

void my_unit_test() {
    my_object obj; // perform some initialization that will allocate

    START_CHECKING_FUNCTION(malloc); // also, operator new or std::allocate would be nice
    obj.perform_realtime_stuff();
    STOP_CHECKING_FUNCTION(malloc);
}

理想情况下,如果在两次检查之间调用了 malloc,测试将以不太脏的方式失败(例如,不是std::abort)。

理想情况下,这可以在任何系统上运行,但我可以接受目前只能在 linux 上运行的东西。这在某种程度上可能吗?也许通过 LD_PRELOAD hack 来替换 malloc,但我宁愿不必为我感兴趣的所有功能都这样做。

【问题讨论】:

  • 一些malloc 实现会跟踪函数被调用的次数,以帮助调试。你能利用它吗?
  • 我只是看不出硬实时要求和动态内存分配之间有任何直接关系。
  • @seleciii44 要求是不做any动态内存分配。
  • seleciii44:基本上,在常见的“桌面”内核上,如果您调用 malloc(或任何其他类型的系统函数),您的线程很有可能会被操作系统抢占;即使不是,如果另一个线程同时调用 malloc 或 free,它也有可能被锁定。
  • 1201ProgramAlarm: 看起来有点像panthema.net/2013/malloc_count 这样的东西确实可以工作。

标签: c++ c linux unit-testing dynamic-analysis


【解决方案1】:

如果您使用的是 GNU C 库,则可以使用 _malloc_hook () 和类似函数来调用用户定义的函数,只要使用 malloc 系列的函数之一。

这样的钩子函数可以分析调用跟踪(使用backtrace()),以确定此调用链中是否允许malloc,如果不是,则打印有关罪魁祸首的消息。

【讨论】:

    【解决方案2】:

    这不是一个完整的答案,但您可以尝试使用 Valgrind 来计算分配和释放。默认的 Valgrind 工具 memcheck 默认计算分配和释放的数量,并在HEAP SUMMARY 中打印结果报告,这是一个示例输出:

    $ valgrind ./a.out
    ==2653== Memcheck, a memory error detector
    ==2653== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==2653== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
    ==2653== Command: ./a.out
    ==2653== 
    ==2653== 
    ==2653== HEAP SUMMARY:
    ==2653==     in use at exit: 0 bytes in 0 blocks
    ==2653==   total heap usage: 2 allocs, 2 frees, 72,716 bytes allocated
    ==2653== 
    ==2653== All heap blocks were freed -- no leaks are possible
    ==2653== 
    ==2653== For counts of detected and suppressed errors, rerun with: -v
    ==2653== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

    您可以再添加一个测试来计算基线分配数:

    void my_unit_test_baseline() {
        my_object obj; // perform some initialization that will allocate
    }
    

    现在您可以运行实际测试并将分配数量与基线测试进行比较。如果它们不等于您测试的代码中发生的某些分配。您可以记录这一事实或以其他任何您想要的方式向它发出信号。

    【讨论】:

      【解决方案3】:

      单元测试调用他们测试的函数。您想知道单元测试调用的函数 F 是否最终可以调用 malloc(或 new 或 ...)。似乎您真正想做的是为整个系统构建一个调用图,然后在调用图中询问关键函数 F 是否可以到达 malloc 等。一旦你有了调用图,这很容易计算。

      获取调用图并不是那么容易。如果您有一个真正的语言前端来进行名称解析,那么发现模块 A 直接调用模块 B 是“技术上很容易的”。找出 A 间接调用的内容并不容易。您需要一个(函数指针)指向分析,而这些很难。而且,当然,您已经决定是否要深入研究库(例如 std::) 例程。

      在面对函数指针和方法调用时,您的调用图需要保守(这样您就不会错过潜在的调用)和相当精确(这样您就不会被误报淹没)。

      这个 Doxygen 支持声称可以构建调用图:http://clang.llvm.org/doxygen/CallGraph_8cpp.html 我不知道它是否处理间接/方法调用或者它有多精确;我对它不是很熟悉,文档似乎很薄。 Doxygen 过去在处理间接或精确方面并不享有盛誉,但过去的版本并非基于 Clang。在http://stackoverflow.com/questions/5373714/generate-calling-graph-for-c-code

      您的问题已标记为 c/c++,但似乎与 C++ 有关。对于 C,我们的 DMS Software Reengineering Toolkit 及其通用流分析和调用图生成支持,再加上 DMS 的 C Front End,已用于 analyze C systems of some 16 million lines/50,000 functions with indirect calls 以生成保守正确的调用图。

      我们并没有专门尝试为大型系统构建 C++ 调用图,但是与 DMS 的C++ Front End 一起使用的 DMS 通用流分析和调用图生成将是“技术上简单的”。在构建正确且大规模运行的静态分析时,没有什么是微不足道的。

      【讨论】:

        【解决方案4】:

        如果您正在使用调用 malloc 的库,那么您可能需要查看 Joint Strike Fighter C++ Coding Standards。这是一种针对关键任务软件的编码风格。一个建议是编写自己的分配器。另一个建议是使用像 jemalloc 这样的东西,它有统计数据,但更不可预测,因为它是针对性能的。


        您想要的是一个具有间谍功能的模拟库。每个框架的工作方式会有所不同,但这里有一个使用 Google 的示例:

        static std::function<void*(size_t)> malloc_bridge;
        
        struct malloc_mock {
            malloc_mock() { malloc_bridge = std::bind(&malloc_mock::mock_, this, _1); }
            MOCK_METHOD1(mock_, void*(size_t));
        }
        
        void* malloc_cheat(size_t size) {
            return malloc_bridge(size);
        }
        
        #define malloc malloc_cheat
        
        struct fixture {
            void f() { malloc(...); }
        };
        
        struct CustomTest : ::testing::test {
            malloc_mock mock_;
        };
        
        TEST_F(CustomTest, ShouldMallocXBytes) {
            EXPECT_CALL(mock_, mock_(X))
              .WillOnce(::testing::Return(static_cast<void*>(0)));
            Fixture fix;
            fix.f();
        }
        
        #undef malloc
        

        警告:编译器未触及代码。但你明白了。

        【讨论】:

        • 问题是:我没有直接调用malloc。但我正在使用 C++ 结构,例如 std::vector、boost::flat_map / flat_set 等,它们在某些时候可能会调用 libc malloc(有时甚至没有 malloc 出现在源代码中的任何位置;例如 new int[100] 将调用 malloc至少在 linux 上的幕后,但你不能用宏来捕捉它)
        • 你可以使用jemalloc之类的东西,我相信它确实有统计数据。
        猜你喜欢
        • 2021-08-21
        • 2020-10-14
        • 2021-07-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-22
        • 1970-01-01
        相关资源
        最近更新 更多