C++ ld 链接器 --wrap 选项不适用于内部函数调用



我正在尝试为不使用 OO 的C++库实现一些单元测试(所有函数都在命名空间级别声明)

为此,我正在尝试创建一个模拟(模拟)某些函数的测试二进制文件。

我已经为我直接调用的函数实现了上述目标,但是我无法替换库函数所做的调用。下面的示例对此进行了解释:

生产代码

假设这是生产代码,使用真实函数而不是模拟函数的代码:

相机处理程序.H

namespace Cam {
int myFunc();
int myFunc2();
}

相机处理程序.cpp

#include "CameraHandler.h"
using namespace Cam;
int Cam::myFunc() {
// Imagine this is the function I want to simulate with a mock
// Its mangled name is _ZN3Cam6myFuncEv
return 1;
}
int Cam::myFunc2(){
return Cam::myFunc() + 11;
}

测试代码

这是单元测试的代码。正如你在 Makefile 中看到的,它生成了一个名为testsMain的二进制文件。

CameraHandlerMock.h

extern "C" {
int __wrap__ZN3Cam6myFuncEv(); // mangled name of Cam::myFunc(), with the __wrap_ prefix.
}

相机处理程序模拟.cpp

#include "CameraHandlerMock.h"
int __wrap__ZN3Cam6myFuncEv(){
// As you can see, the mocked function returns 999 instead of 1.
return 999;
}

单元测试主.cpp

#include <iostream>
#include <typeinfo>
#include "CameraHandler.h"
#include "CameraHandlerMock.h"
extern "C" int _ZN3Cam6myFuncEv();
int main(){
std::cout << Cam::myFunc() << std::endl;
std::cout << Cam::myFunc2() << std::endl;
return 0;
}

生成文件

WRAP=-Wl,--wrap,_ZN3Cam6myFuncEv
all: production unitTests
production: // does not matter for this example
g++ main.cpp CameraHandler.cpp -o main 
unitTests:
g++ ${WRAP} UnitTestsMain.cpp CameraHandlerMock.cpp CameraHandler.cpp -o testsMain

问题所在

如果我执行testsMain程序,我会得到以下结果:

999 // call to Cam::myFunc()
12 // Cam::myFunc2(), which is Cam::myFunc() + 11.

考虑到Cam::myFunc2()调用Cam::myFunc1(),我用__wrap__ZN3Cam6myFuncEv代替了它,我期望的是调用Cam::myFunc2()的结果是999 + 11 = 1010。尽管如此,Cam::myFunc2()仍然调用非包装Cam::myFunc1(),所以结果是12

有什么方法可以包装我要测试的库在内部调用的函数

让我们先皮毛一点绒毛。在UnitTestsMain.cpp, 宣言:

extern "C" int _ZN3Cam6myFuncEv();

是多余的。它只是指示引用函数的C++编译器 该原型的名称为_ZN3Cam6myFuncEv引用 该名称的外部定义的函数。这是完全相同的信息, 只是表达方式不同,编译器已经从

namespace Cam {
int myFunc();
...
}

当它#include-edCameraHandler.h时,因为_ZN3Cam6myFuncEv()是被破坏的Cam::myFunc形式 .extern "C"重新声明Cam::myFunc是无害的 但对编译或链接没有任何贡献。

关于主要问题:为什么你的模拟int __wrap__ZN3Cam6myFuncEv()被调用而不是int Cam::myFuncUnitTestsMain.cpp

int main(){
std::cout << Cam::myFunc() << std::endl;
std::cout << Cam::myFunc2() << std::endl;
return 0;
} 

如你所愿; 但是你的模拟不需要int Cam::myFuncCameraHandler.cpp

int Cam::myFunc2(){
return Cam::myFunc() + 11;
}

答案就在--wrap链接器选项的文档中:

-

-换行=符号

对符号使用包装函数。任何未定义的符号引用都将是 决心__wrap_symbol。对__real_symbol的任何未定义的引用都将是 解析为符号。

也许你读了它,并没有意识到未定义引用的意义。

这意味着当--wrap=symbol生效时,链接器会应用它 到一个包含对symbol未定义引用的对象文件,它将替换它们 引用__wrap_symbol,未定义引用__real_symbol, 在该对象文件中,将替换为symbol

现在在UnitTestsMain.o,从UnitTestsMain.cpp编译,对这两个Cam::myFunc()的引用Cam::myFunc2()未定义。这些函数都在CameraHandler.cpp中定义,CameraHandler.o编译。

因此在UnitTestsMain.o的联动中,--wrap ZN3Cam6myFuncEv将生效和 将对Cam::myFunc( =ZN3Cam6myFuncEv) 的调用替换为对__wrap_ZN3Cam6myFuncEv的调用。 对Cam::myFunc2()( =ZN3Cam7myFunc2Ev) 的调用未包装且不受影响:它将 已解析为CameraHandler.o中的定义

但是在CameraHandler.o的联动中,两个函数都被定义了,所以--wrap有 没有效果。当Cam::myFunc2()调用 Cam::myFunc() 时,它调用的是ZN3Cam6myFuncEv,而不是__wrap_ZN3Cam6myFuncEv.

这就解释了为什么程序输出:

999
12  

而不是:

999
1010

你能让你的嘲笑工作如预期的那样吗?

是的。你只需要确保每一个电话Cam::myFunc你想成为 模拟被编译为不包含(真实)定义的对象文件 的Cam::myFunc.显而易见的方法是定义Cam::myFunc自己的 源文件。这是您的示例已修复:

相机处理程序.h

#ifndef CAMERAHANDLER_H
#define CAMERAHANDLER_H
namespace Cam {
int myFunc();
int myFunc2();
}
#endif

CameraHandlerMock.h

#ifndef CAMERAHANDLERMOCK_H
#define CAMERAHANDLERMOCK_H
extern "C" {
int __wrap__ZN3Cam6myFuncEv();
}
#endif

CameraHandler_myFunc.cpp

#include "CameraHandler.h"
using namespace Cam;
int Cam::myFunc() {
return 1;
}

CameraHandler_myFunc2.cpp

#include "CameraHandler.h"
using namespace Cam;
int Cam::myFunc2(){
return Cam::myFunc() + 11;
}

相机处理程序模拟.cpp

#include "CameraHandlerMock.h"
int __wrap__ZN3Cam6myFuncEv() {
return 999;
}

单元测试主.cpp

#include <iostream>
#include "CameraHandler.h"
#include "CameraHandlerMock.h"
int main(){
std::cout << Cam::myFunc() << std::endl;
std::cout << Cam::myFunc2() << std::endl;
return 0;
}

生成文件

SRCS := UnitTestsMain.cpp CameraHandler_myFunc.cpp 
CameraHandler_myFunc2.cpp CameraHandlerMock.cpp
OBJS := $(SRCS:.cpp=.o)
LDFLAGS := -Wl,--wrap,_ZN3Cam6myFuncEv
.PHONY: unitTests clean
unitTests: testsMain
testsMain: $(OBJS)
$(CXX) $(LDFLAGS) -o $@ $^
UnitTestsMain: CameraHandler.h CameraHandlerMock.h
CameraHandler_Func.o CameraHandler_Func2.o: CameraHandler.h
CameraHandlerMock.o: CameraHandlerMock.h
clean:
rm -f $(OBJS) testsMain

(在此示例生成文件中根本不考虑您的生产版本)

这样,测试版本将像以下方式运行:

$ make
g++    -c -o UnitTestsMain.o UnitTestsMain.cpp
g++    -c -o CameraHandler_myFunc.o CameraHandler_myFunc.cpp
g++    -c -o CameraHandler_myFunc2.o CameraHandler_myFunc2.cpp
g++    -c -o CameraHandlerMock.o CameraHandlerMock.cpp
g++ -Wl,--wrap,_ZN3Cam6myFuncEv -o testsMain UnitTestsMain.o 
CameraHandler_myFunc.o CameraHandler_myFunc2.o CameraHandlerMock.o

testsMain会执行您的期望:

$ ./testsMain 
999
1010

如果重写,则可以稍微简化源文件和生成文件CameraHandlerMock.cpp只是:

extern "C" {
int __wrap__ZN3Cam6myFuncEv() {
return 999;
}
}

那么你根本不需要模拟头文件CameraHandlerMock.h

如果你有很多函数需要以这种低级的方式进行模拟,它 在自己的源文件中定义每个可能会变得乏味。你可能知道 有更高级别的、框架支持的模拟选项,例如 GoogleMock, 具有丰富的嘲笑能力,不需要这种乏味。然而,公平地说,他们可能会 用更复杂的乏味取而代之。

最新更新