Google Mock a free system function on Linux always finishes with memory leak

Viewed 333

I'm trying to mock a simple function from the Linux standard library. strerror() returns the error message from an errno. This is my library with the function to mock:

~$ cat mylib.c
#include <string.h>
#include <stdio.h>

int myStrerror()
{
    int error_number = 0;

    char* buffer = strerror(error_number);
    fprintf(stdout, "Returned string =  '%s'\n", buffer);
    return 0;
}

#if defined (EXECUTABLE)
int main(int argc, char **argv)
{
    return myStrerror();
}
#endif

~$ g++ -pedantic-errors -Wall -c mylib.c

This is my google test:

~$ cat test_mylib.cpp
#include "gtest/gtest.h"
#include "gmock/gmock.h"

int myStrerror();

class strerrorMock {
public:
    MOCK_METHOD(char*, strerror, (int));
};

strerrorMock strerrorMockObj;

char *strerror(int error_number) {
    return strerrorMockObj.strerror(error_number);
}

TEST(MockTestSuite, strerror)
{
    using ::testing::Return;

    char response[] = "mocked strerror function";

    EXPECT_CALL(strerrorMockObj, strerror(0))
        .WillOnce(Return(response));
    EXPECT_EQ(myStrerror(), 0);
    ::testing::Mock::VerifyAndClearExpectations(&strerrorMockObj);
}


int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

~$ g++ -pedantic-errors -Wall \
        -o test_mylib.a \
        -I"$BUILD_DIR"/googletest-src/googletest/include \
        -I"$BUILD_DIR"/googletest-src/googlemock/include \
        test_mylib.cpp \
        "$BUILD_DIR"/lib/libgtestd.a \
        "$BUILD_DIR"/lib/libgmockd.a \
        ./mylib.o \
        -lpthread

This is what it returns normally:

~$ ./mylib.a
Returned string = 'Success'

and Running the test gives this:

~$ ./test_mylib.a
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from MockTestSuite
[ RUN      ] MockTestSuite.strerror
Returned string = 'mocked strerror function'
[       OK ] MockTestSuite.strerror (0 ms)
[----------] 1 test from MockTestSuite (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

test_mylib.cpp:32: ERROR: this mock object (used in test MockTestSuite.strerror) should be deleted but never is. Its address is @0x56114aa239e0.
ERROR: 1 leaked mock object found at program exit. Expectations on a mock object are verified when the object is destructed. Leaking a mock means that its expectations aren't verified, which is usually a test bug. If you really intend to leak a mock, you can suppress this error using testing::Mock::AllowLeak(mock_object), or you may use a fake or stub instead of a mock.

What have I to do to avoid the memory leak?

2 Answers

The problem is that we use a free global function strerror() from the system library. It isn't really mocked by an interface as usual with Googlemock. So we do not need an Interface. We have to cover the mocked function with also a free function that must be global to be on the same scope than the system function because it shall replace it. This is what we do with:

strerrorMock strerrorMockObj;

char* strerror(int error_number) {
    return strerrorMockObj.strerror(error_number);
}

Here the instance strerrorMockObj of the mock is also in the global scope to be callable within the function. But obviously Googletest cannot delete a global mock object as noted in the error message. One solution I have found is to instantiate the mock object within the test macro as usual and store a global pointer to it so the function can address it:

strerrorMock* ptrStrerrorMockObj;
char* strerror(int error_number) {
    return ptrStrerrorMockObj->strerror(error_number);
}

TEST(MockTestSuite, strerror)
{
    strerrorMock strerrorMockObj;
    ptrStrerrorMockObj = &strerrorMockObj;
...
}

Then the complete test program without complaining a memory leak looks like this:

~$ cat test_strerror.cpp
#include "gtest/gtest.h"
#include "gmock/gmock.h"

int myStrerror();

class strerrorMock {
public:
    MOCK_METHOD(char*, strerror, (int));
};

strerrorMock* ptrStrerrorMockObj;
char* strerror(int error_number) {
    return ptrStrerrorMockObj->strerror(error_number);
}

TEST(MockTestSuite, strerror)
{
    using ::testing::Return;

    strerrorMock strerrorMockObj;
    ptrStrerrorMockObj = &strerrorMockObj;

    char mockedstr[] = "mocked strerror function";
    EXPECT_CALL(strerrorMockObj, strerror(0))
        .WillOnce(Return(mockedstr));
    EXPECT_EQ(myStrerror(), 0);
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

After some month using the solution of my first answer on several platforms I found that it is not very stable. In particular on MS Windows I had trouble that GoogleMock does not always find the mocking function. So I decided to accept minimal modification of the production code and use a wrapper class for the free system functions as recommended by googletest.

With the following I only have to add a header file to the production code and change its system calls for example

# from
fd = fopen("openclose.txt", "a");
# to
fd = stdioif->fopen("openclose.txt", "a");

On Microsoft Windows I have cloned googletest from github built it using powershell with settings cmake -S . -B build then cmake --build build --config MinSizeRel and stay in its root directory using this structure:

├── build
│   └── lib
│       └── MinSizeRel
│           ├── gmock.lib
│           ├── gmock_main.lib
│           ├── gtest.lib
│           └── gtest_main.lib
├── include
│   └── stdioif.h
├── src
│   ├── main.cpp
│   ├── openclose.cpp
│   └── test_openclose.cpp
├── main.exe
├── main.obj
├── openclose.txt
├── test_openclose.exe
└── test_openclose.obj

Here is the header file:

#ifndef INCLUDE_STDIOIF_H
#define INCLUDE_STDIOIF_H

#include <stdio.h>

class Istdio {
// Interface to stdio system calls
  public:
    virtual ~Istdio() {}
    virtual FILE* fopen(const char* pathname, const char* mode) = 0;
    virtual int fprintf(FILE* stream, const char* format) = 0;
    virtual int fclose(FILE* stream) = 0;
};


// Global pointer to the  current object (real or mocked), will be set by the
// constructor of the respective object.
Istdio* stdioif;


class Cstdio : public Istdio {
// Real class to call the system functions.
  public:
    virtual ~Cstdio() {}

    // With the constructor initialize the pointer to the interface that may be
    // overwritten to point to a mock object instead.
    Cstdio() { stdioif = this; }

    FILE* fopen(const char* pathname, const char* mode) override {
        return ::fopen(pathname, mode);
    }

    int fprintf(FILE* stream, const char* format) override {
        return ::fprintf(stream, format);
    }

    int fclose(FILE* stream) override {
    }
};

// This is the instance to call the system functions. This object is called
// with its pointer stdioif (see above) that is initialzed with the
// constructor. That pointer can be overwritten to point to a mock object
// instead.
Cstdio stdioObj;

/*
 * In the production code you must call it with, e.g.:

    stdioif->fopen(...)

 * The following class should be coppied to the test source. It is not a good
 * idea to move it here to the header. It uses googletest macros and you always
 * hove to compile the code with googletest even for production and not used.

class Mock_stdio : public Istdio {
// Class to mock the free system functions.
  public:
    virtual ~Mock_stdio() {}
    Mock_stdio() { stdioif = this; }
    MOCK_METHOD(FILE*, fopen, (const char* pathname, const char* mode), (override));
    MOCK_METHOD(int, fprintf, (FILE* stream, const char* format), (override));
    MOCK_METHOD(int, fclose, (FILE* stream), (override));
};

 * In a gtest you will instantiate the Mock class, prefered as protected member
 * variable for the whole testsuite:

    Mock_stdio mocked_stdio;

 *  and call it with: mocked_stdio.fopen(...) (prefered)
 *  or                    stdioif->fopen(...)
*/

#endif // INCLUDE_STDIOIF_H

This is the simple example program:

#include "stdioif.h"

#include <iostream>

int openclose() {
    FILE* fd = nullptr;
    int rc = 0;

    fd = stdioif->fopen("openclose.txt", "a");
    if(fd == NULL) {
        std::cerr << "Error opening file\n";
        return 1;
    }

    rc = stdioif->fprintf(fd, "hello world :-)\n");
    if(rc < 0) {
        std::cerr << "Error appending to file with return code: " << rc << "\n";
        stdioif->fclose(fd);
        return rc;
    }

    rc = stdioif->fclose(fd);
    if(rc) {
        std::cerr << "Error closing file with return code: " << rc << "\n";
        return rc;
    }

    std::cout << "done.\n";
    return 0;
}

I execute it with:

#include "src/openclose.cpp"

int main() {
    return openclose();

The test program looks like this:

#include "gtest/gtest.h"
#include "gmock/gmock.h"

#include "stdioif.h"

#include "src/openclose.cpp"

using ::testing::_;
using ::testing::Return;


class Mock_stdio : public Istdio {
// Class to mock the free system functions.
  public:
    virtual ~Mock_stdio() {}
    Mock_stdio() { stdioif = this; }
    MOCK_METHOD(FILE*, fopen, (const char* pathname, const char* mode), (override));
    MOCK_METHOD(int, fprintf, (FILE* stream, const char* format), (override));
    MOCK_METHOD(int, fclose, (FILE* stream), (override));
};


class OpenCloseTestSuite: public ::testing::Test {
  protected:
    // Member variables of the whole testsuite: instantiate the mock objects.
    Mock_stdio mocked_stdio;
};


TEST_F(OpenCloseTestSuite, open_close) {

    EXPECT_CALL(mocked_stdio, fopen(_, _))
        .WillOnce(Return((FILE*)0x123456abcdef));

    EXPECT_CALL(mocked_stdio, fprintf(_,_))
        .WillOnce(Return(-1));

    EXPECT_CALL(mocked_stdio, fclose(_)).Times(1);

    // process unit
    EXPECT_EQ(openclose(), 0);
}

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

To compile it on Microsoft Windows I use:

cl -nologo  /EHsc -I. -I.\include -I.\googletest\include -I.\googlemock\include .\build\lib\MinSizeRel\gtest.lib .\build\lib\MinSizeRel\gmock.lib .\src\[main.cpp | test_openclose.cpp]
Related