Search code examples
linuxmemory-leaksmockingdebian-based

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


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?


Solution

  • 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]