Martel
Martel

Reputation: 2734

C++ ld linker --wrap option does not work for internal function calls

I am trying to implement some unit tests for a C++ library that does not use OO (all functions are declared at namespace level)

For that purpose, I am trying to create a test binary that mocks (simulate) some functions.

I have achieved the above for functions that I call directly, but I have been unable to replace the calls that the library's functions do. The example below explains this:

Production code

Lets suppose this is the production code, the one that uses the real functions instead of the simulated ones:

CameraHandler.H

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

CameraHandler.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;
}

Testing code

This is the code for the unit testing. As you can see in the Makefile, it generates a binary called testsMain.

CameraHandlerMock.h

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

CameraHandlerMock.cpp

#include "CameraHandlerMock.h"

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

UnitTestsMain.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;
}

The Makefile

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

The problem

If I execute the testsMain program, I obtain the following result:

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

Taking into account that Cam::myFunc2() calls to Cam::myFunc1(), and I have replaced it by __wrap__ZN3Cam6myFuncEv, what I expect is that t he result of calling Cam::myFunc2() is 999 + 11 = 1010. Nevertheless, Cam::myFunc2() is still calling the non-wrapped Cam::myFunc1(), so the result is 12.

Is there any way to wrap functions that are internally called by the library I want to test?

Upvotes: 3

Views: 2804

Answers (1)

Mike Kinghan
Mike Kinghan

Reputation: 61550

Let's lint a little bit of fluff first. In UnitTestsMain.cpp, the declaration:

extern "C" int _ZN3Cam6myFuncEv();

is redundant. It simply instructs the C++ compiler that references to the function of that prototype whose mangled name is _ZN3Cam6myFuncEv are references to an externally defined function of that name. This is exactly the same information, just expressed differently, that that the compiler has already got from:

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

when it #include-ed CameraHandler.h, because _ZN3Cam6myFuncEv() is the mangled form of Cam::myFunc. The extern "C" redeclaration of Cam::myFunc is harmless but contributes nothing either to compilation or linkage.

On to the main question: Why does your mock int __wrap__ZN3Cam6myFuncEv() get called instead of int Cam::myFunc in UnitTestsMain.cpp:

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

as you want; but your mock is not called for int Cam::myFunc in CameraHandler.cpp:

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

The answer lies in the documentation of the --wrap linker option:

--wrap=symbol

Use a wrapper function for symbol. Any undefined reference to symbol will be resolved to __wrap_symbol. Any undefined reference to __real_symbol will be resolved to symbol.

Maybe you read it and didn't grok the significance of undefined reference.

This means that when --wrap=symbol is in effect, and the linker applies it to an object file containing undefined references to symbol, it will replace them with references to __wrap_symbol, and undefined references to __real_symbol, in that object file, will be replaced with symbol.

Now in UnitTestsMain.o, compiled from UnitTestsMain.cpp, the references to both Cam::myFunc() and Cam::myFunc2() are undefined. These functions are both defined in CameraHandler.cpp, compiled in CameraHandler.o.

Therefore in the linkage of UnitTestsMain.o, --wrap ZN3Cam6myFuncEv will take effect and replace the call to Cam::myFunc ( = ZN3Cam6myFuncEv) with a call to __wrap_ZN3Cam6myFuncEv. The call to Cam::myFunc2() ( = ZN3Cam7myFunc2Ev) is not wrapped and is unaffected: it will be resolved to the definition to be found in CameraHandler.o

But in the linkage of CameraHandler.o, both functions are defined, so --wrap has no effect. When Cam::myFunc2() calls Cam::myFunc(), it calls ZN3Cam6myFuncEv, not __wrap_ZN3Cam6myFuncEv.

That explains why the program outputs:

999
12  

and not:

999
1010

Can you make your mocking work as expected?

Yes. You just have to ensure that every call to Cam::myFunc that you want to be mocked is compiled into an object file that does not contain the (real) definition of Cam::myFunc. The obvious way to do that is to define Cam::myFunc in its own source file. Here's your example fixed:

CameraHandler.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;
}

CameraHandlerMock.cpp

#include "CameraHandlerMock.h"

int __wrap__ZN3Cam6myFuncEv() {
    return 999;
}

UnitTestsMain.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;
}

Makefile

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

(Your production build is not considered at all in this example makefile)

With this, the test build runs like:

$ 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

and testsMain does what you expect:

$ ./testsMain 
999
1010

You can simplify both source files and the makefile somewhat if you rewrite CameraHandlerMock.cpp as just:

extern "C" {

int __wrap__ZN3Cam6myFuncEv() {
    return 999;
}

}

Then you have no need for the mock header file CameraHandlerMock.h at all.

If you have a lot of functions you need to mock in this low-level way, it may get tedious to define each one in its own source file. You may be aware that there are higher-level, framework-supported mocking options, e.g. googlemock, that have rich mocking capabilities and don't entail this tedium. It's fair to say, however, that they may replace it with more complicated kinds of tedium.

Upvotes: 3

Related Questions