使用Google Mock测试后因gcov调用触发段错误的问题求助
问题原因与解决方案
问题原因
你用extern "C"包装的fcntl mock本质是全局拦截系统调用,所有代码路径对fcntl的调用都会被转发到MockClass的实例方法。
测试执行流程中,TearDown会销毁MockClass实例,但gcov的覆盖率写入逻辑是在所有测试完成、测试框架完成清理之后才触发的——这个过程中gcov会调用fcntl操作文件,此时mock对象已经被销毁,全局拦截的fcntl函数访问已释放的内存,直接引发段错误。
解决方案
方案1:在gcov运行前恢复原始系统函数(推荐)
核心思路是给mock的系统函数加兜底逻辑,同时在gcov执行前恢复原始调用链路:
- 启动时保存原始
fcntl函数指针,用dlsym(RTLD_NEXT, "fcntl")获取真实的系统函数地址; - 全局拦截的
fcntl函数先检查mock对象是否存活,存活则转发到mock,否则直接调用原始函数; - 在所有测试完成后(早于gcov执行的时机),清理mock对象并确保后续调用走原始函数。
示例代码片段:
#include <dlfcn.h> #include <cstdarg> // 保存原始fcntl函数指针 static int (*original_fcntl)(int, int, ...) = nullptr; // 全局mock实例指针(MockClass需提供静态成员或全局变量存储) MockClass* g_mock_instance = nullptr; extern "C" int fcntl(int fd, int cmd, ...) { va_list args; va_start(args, cmd); int ret; if (g_mock_instance != nullptr) { // 转发到mock对象的方法 ret = g_mock_instance->MockFcntl(fd, cmd, args); } else { // 兜底调用原始系统函数 ret = original_fcntl(fd, cmd, args); } va_end(args); return ret; } // 程序启动时初始化原始函数指针 __attribute__((constructor)) void init_mock_setup() { original_fcntl = reinterpret_cast<int(*)(int, int, ...)>(dlsym(RTLD_NEXT, "fcntl")); } // 全局清理函数:在所有测试完成后调用 void restore_system_calls() { g_mock_instance = nullptr; // 若有其他mock函数,同理重置 } // 利用gtest的测试套件销毁钩子,确保在gcov前执行 TEST_SUITE_TEAR_DOWN(GlobalTestSuite) { restore_system_calls(); }
方案2:让mock对象存活到进程退出
如果不想修改系统函数的拦截逻辑,可以调整mock对象的生命周期:
- 将
MockClass实例改为全局静态对象,或者用std::unique_ptr全局管理,只在进程退出时销毁; - 测试类的
SetUp仅初始化当前测试的mock预期,TearDown调用Mock::VerifyAndClearExpectations()清除预期,不销毁对象; - 注意:这种方式要严格隔离不同测试用例的mock状态,避免用例之间的预期互相干扰。
方案3:调整gcov执行时机(不推荐)
通过环境变量或编译选项让gcov提前完成数据写入,比如设置GCOV_PREFIX指定输出目录,或者编译时添加-fprofile-arcs -ftest-coverage的同时调整gcov的刷新策略。但这种方法可能导致覆盖率数据不完整,只适合临时排查问题,不建议长期使用。
内容的提问来源于stack exchange,提问作者Justplayit
相关产品推荐
相关产品推荐

