Mimicc User Guide

© 2021 Eurba Labs Development Tools

By using this software, you agree to the Terms And Conditions described in the Mimicc License Agreement, which can be found either on our website, or in your Mimicc installation at <mimicc-root>/usr/local/doc/mimicc/License.pdf.

TL;DR: if you’re in a hurry, skip ahead to the Quick Start section. You may also need to skim the setup section to help with installation.

If you’re working with the tool and confused about how it works, have a look at the Common Problems section.

Introduction

Mimicc is a mock generator and compiler for C and C++. It was built with professional engineers in mind, but will certainly be useful to anyone striving to build highly reliable, hardened software in C or C++.

Mimicc is first and foremost a tool for writing unit tests, and aims to remove the barriers commonly encountered by software developers writing tests for systems written in low-level power-tool programming languages.

Mimicc was written with two guiding philosophies:

  1. All time spent writing and maintaining mocks is wasted time.
  2. The software architecture of a mocking and testing framework should have minimal or no influence on the architecture of production software.

The motivations for both of these objectives are likely immediately felt by anyone who has written or maintained mocks in large-scale software projects. Engineers commonly build in “hooks” and “backdoors” to their software for testing purposes. This can take many forms depending on the language and organizational guidelines, but the end result is the same: additional complexity for the trade-off of better testability.

We would like to have a tool which allows an engineer to use their language of choice in the most natural, and (most importantly) straightforward way, without compromising their ability to test the code.

Overview

The Mimicc tool:

A mock generated with Mimicc:

Implementation Status

The current version of Mimicc (0.11.1) has very broad support for the C and C++ language specifications. In general if a function has “linkage”, a mock can be generated.

The following items are under active development, and planned for the 1.0 release, but still incomplete:

Setup

Mimicc is distributed as a complete, self-contained Clang-based toolchain including the mimicc command itself, the clang compiler, lld linker, compiler-rt, and libc++1. For best results we recommend simply using this toolchain as your test development toolchain, though it is not required.

Build Environment

Windows Installation And Configuration

Mimicc for Windows is distributed as a standalone installer executable and follows the standard Windows application installation flow. If you are re-installing or updating Mimicc, we strongly recommend you first uninstall the existing version.

Installer Options

We recommend keeping the default installation destination (e.g. C:\Program Files).

If you plan to use the Clang binary distributed with Mimicc as the only Clang instance on your machine, we also recommend adding Mimicc to at least the current user’s %PATH%. If you plan to use multiple simultaneous installations of Clang, you should skip modifying the %PATH% and instead rely on batch files to set up your command line build environment.

Libc Headers

In order to use the Mimicc toolchain natively, you will need to have a valid Windows SDK installed which can provide the libc headers and runtime. The standard way to get the Windows SDK is by installing Microsoft Visual Studio and selecting the appropriate components. Installation and configuration of Visual Studio is beyond the scope of this document. Please refer to Microsoft’s documentation for more information.

Environment Variables

Both Clang and Mimicc also use common Windows development environment variables to locate the default system include headers (e.g. %INCLUDE% and %LIBPATH%). The standard way to set these variables is using the vcvarsall.bat batch script that ships with Visual Studio. For example:

  
C:\> C:\Program Files (x86)\Visual Studio...\Build\vcvarsall.bat amd64
  

This will initialize your cmd session with all of the variables required to build on an amd64 machine.

Alternatively, you can make use of the -isysroot Clang option to locate libc headers.

Linux And MacOS Installation And Configuration

In general setting up your toolchain should simply require adding <mimicc-root>/usr/local/bin/ to your regular binary lookup path. This can be done globally, or in an encapsulated shell. The Mimicc distribution provides several convenience shell scripts under <mimicc-root>/usr/local/share/mimicc/ to simplify this. These scripts are intended to give you a “virtual environment” within your shell for using the mimicc command, and the Clang distribution it ships with.

If you already have a Clang distribution installed on your system (for example with Xcode), using the env scripts will also help you avoid conflicts and issues between toolchains.

To use the scripts, identify your shell type (e.g. bash, zsh, ksh, etc.), and pick an appropriate implementation from <mimicc-root>/usr/local/share/mimicc/. To activate the mimicc dev environment, simply source this script. Note that these are not executable scripts to be run stand-alone, they must be explicitly “included” in your environment via the shell source command. To deactivate, simply run the end_mimicc command.

Example for bash:

  
bash@home user-prompt# source <mimicc-root>/usr/local/share/mimicc/mimicc-env
(mimicc) bash@home user-prompt# mimicc -c -o mock.o hdr-to-mock.h
(mimicc) bash@home user-prompt# clang -o test mock.o uut.c test.c
(mimicc) bash@home user-prompt# end_mimicc
bash@home user-prompt#
  

The full list of shell scripts available per shell-type is as follows:

If your favorite shell is not covered by one of these scripts, please let us know by filing a support request on our website mimic.dev

Linux Additional Build Requirements

In order for Mimicc (and Mimicc Clang) to work out of the box, you will need a functioning C/C++ development environment including libc, libstdc++, and their headers. Most modern desktop or server Linux distributions have a package manager which can partially automate this for you, for example apt-get or yum. These package managers usually offer “group” installations for development tool chains. We recommend you use one of these macro sets for initial distribution setup.

MacOS Additional Build Requirements

In order for Mimicc and Clang to function properly on a Darwin (MacOS) system, you must have system libc headers installed. System libc headers are installed either as part of the full Xcode distribution, or the CommandLineTools package distributed to Developers.

If you are using the env scripts as described in the Tool Installation section above, this is the only prerequisite.

If you are not using the env scripts, or attempting to use Mimicc as a global installation, you will need to make sure that mimicc and its corresponding clang binary are able to find the libc headers for your system.

Most standard Unix-like platforms use /usr/include for libc headers. On newer versions of MacOS though, libc headers are part of the Development SDK, which could be part of your Xcode installation, or the Command Line tools package, and the path depends on the version of the SDK. The conventional way of finding this path is to use xcrun -show-sdk-path.

Mimicc and Mimicc’s Clang will need to be explicitly told where to look for libc headers. There are two ways to do this. The simplest method is simply to add the following line to your .bashrc or equivalent shell profile file:

  
export SDKROOT=`xcrun -show-sdk-path`
  

Alternatively, you can add the following flag on all executions of Clang or Mimicc:

  
-isysroot `xcrun -show-sdk-path`
  

Integration With Build Scripts

The “mimicc” and “mimicc++” commands should be included in your project’s build scripts as the tools used to auto-generate a mock test dependency. In other words, an ideal build setup should be able to detect when a header file has changed, and trigger a rebuild of the mock accordingly. The specific steps for this will vary depending on your build environment (i.e. GNU Make, CMake, etc.).

As a trivial example, consider the following GNU Make-based project, which has a source file containing test code (test.cpp), and a header containing declarations which need to be mocked (drv.h). We want to build an executable to run our tests called test-exe. The Makefile for this would be as follows:

Makefile:

  
test-exe: mock-drv.o test.cpp
    clang -o $@ $^

mock-drv.o: drv.h
    mimicc -c -o $@ $< -Wall --hout=mock-drv.h
  

Runtime Environment

The only runtime requirement for Mimicc is that each test executable must define two functions for handling failures:

The implementation depends primarily on which test framework you choose to work with, but could be as simple as an fprintf() or cout() call followed by non-zero exit. The declarations are as follows:

  
    void mock_fatal(const char *pLocation, unsigned count, const char *pMsg);
    void mock_failure(const char *pLocation, unsigned count, const char *pMsg);
  

The mock implementations assume that execution stops immediately when mock_fatal() is called. In other words if mock_fatal() returns and resumes execution of the mock function where the fatal failure occurred, the behavior is undefined. This is particularly important for test frameworks like GoogleTest, which by default may treat failures as non-fatal and continue execution. For these frameworks you may need to explicitly call exit() with a non-zero code in your implementation of the mock_fatal() function.

The mock_failure() function can be implemented however you see fit, up to and including simply ignoring the inputs entirely (not recommended). The mock_failure() function is the primary point of error recording when expected value assertion checks fail.

The following sections provide some example implementations for mock_fatal() and mock_failure() for different unit test frameworks.

CppUTest Example

If you’re using CppUTest as your test framework, implementation of the failure functions is straightforward. We recommend including it in your main.cpp as follows:

  
    #include <cstring>
    #include <memory>

    #include "CppUTest/CommandLineTestRunner.h"
    #include "CppUTest/TestHarness.h"


    std::unique_ptr<char []> mockErrStr(const char *pLocation, unsigned count, const char *pMsg)
    {
        const char *pFmtStr = "mock assertion failure! location: '%s',"
                              " iteration: %d, message: %s";
        size_t n = snprintf(NULL, 0, pFmtStr, pLocation, count, pMsg);
        std::unique_ptr<char []> outStrBuf(new char[n+1]);
        snprintf(outStrBuf.get(), n+1, pFmtStr, pLocation, count, pMsg);
        return outStrBuf;
    }

    void mock_fatal(const char *pLocation, unsigned count, const char *pMsg) {
        FAIL_TEST(mockErrStr(pLocation, count, pMsg).get());
    }

    void mock_failure(const char *pLocation, unsigned count, const char *pMsg) {
         mock_fatal(pLocation, count, pMsg);
    }

    int main(int argc, char **argv)
    {
        return RUN_ALL_TESTS(argc, argv);
    }
  

GoogleTest Example

If you’re using GoogleTest as your test framework, implementation of the failure functions is simple, but some care should be taken when running tests. In general if a fatal test failure occurs in the context of a mock (i.e. a call to mock_fatal() is made), the mock will be in an unknown state. This could have unexpected side effects if it is allowed to continue executing, thus we recommend terminating tests immediately when fatal errors are encountered. This can be done via the --gtest_break_on_failure test runner argument, or you can simply call exit(1) on fatal errors as shown below.

The implementation is then as follows:

  
    #include <gtest/gtest.h>
    #include <memory>

    std::unique_ptr<char []> mockErrStr(const char *pLocation, unsigned count, const char *pMsg)
    {
        const char *pFmtStr = "mock assertion failure! location: '%s',"
                              " iteration: %d, message: %s";
        size_t n = snprintf(NULL, 0, pFmtStr, pLocation, count, pMsg);
        std::unique_ptr<char []> outStrBuf(new char[n+1]);
        snprintf(outStrBuf.get(), n+1, pFmtStr, pLocation, count, pMsg);
        return outStrBuf;
    }

    void mock_failure(const char *pLocation, unsigned count, const char *pMsg)
    {
        ADD_FAILURE() << mockErrStr(pLocation, count, pMsg).get();
    }

    void mock_fatal(const char *pLocation, unsigned count, const char *pMsg)
    {
        FAIL() << mockErrStr(pLocation, count, pMsg).get();
        exit(1);
    }
  

Use In Continuous Integration

Mimicc is designed to be run as a regular step in any normal build and test pipeline. In an ideal configuration, Mimicc should be installed on build and test servers and ready to generate platform-specific mocks as part of the standard build and test procedure. Mimicc mocks are not designed to be hand-edited, and are therefore ideally used in an automated build pipeline.

The more automated the dependency generation step is, the better.

Running The Smoke Tests

Mimicc provides a set of platform-independent integration “smoke tests” to make sure the tools are working correctly. The tests can be downloaded from mimicc.dev as a supplement to the main binary installation package.

To run the tests, make sure you have Mimicc included in your binary search path (either globally or in your local shell session), and execute the appropriate alltests script for your platform, which will either be Powershell on Windows or Bash on Linux and MacOS. For example:

Windows:

powershell -File ./smoketests/bin/alltests.ps1

Linux And Mac OS:

./smoketests/bin/alltests.sh

Regardless of what standard or specification your project code base uses, we strongly recommend that you use C++ when writing your unit tests, and at least C++11, ideally C++14 or newer. In general the test code should not be bound by the same restrictions as the target code under test, which may be quite restricted.

The reasons for recommending a post-11 C++ standard are many, but these are the most significant:

  1. The post-11 auto keyword can save a tremendous amount of typing when accessing mock API nodes.
  2. The use of lambdas, and in particular automatic lambda-to-function-pointer conversion can be extremely powerful when setting test-specific object comparison or assignment operations on mock API nodes.

The Clang compiler distributed with Mimicc supports all available C++ standards up to C++20. To select a specific standard, use the ordinary Clang -std= argument.

Quick Start

To get started, activate the Mimicc toolchain environment for your shell of choice using one of the scripts provided (showing Bourne Shell variants here):

  
user-prompt$ source <mimic-root>/usr/local/share/mimicc/mimicc-env
(mimicc) user-prompt$ 
  

For C-Shell variants, use mimicc-env.csh, or for the fish shell, use mimicc-env.fish.

We can now use mimicc as the compiler that it is. Given a C header file drv.h containing declarations you want mocked in your unit tests, you can generate a mock object file as if you were invoking an instance of gcc on the header. We’ll focus on “pure” C for now because it’s simpler and better suited to a truly quick “Quick Start”, but the same principles would apply for C++:

  
(mimicc) user-prompt$ mimicc -c -o mock-drv.o drv.h
  

For C++ use the mimicc++ command, analogous to how you would use g++ or clang++ as your regular compiler.

This single command will find all “mockable” functions and methods declared in drv.h, emit code for them, and compile that code into your target architecture, producing the output object file mock-drv.o.

Note the familiar usage of -c, and -o, which are both standard gcc arguments, used in the ordinary way. The Mimicc command-line interface is built on the Clang GCC emulator, which means you can pass any regular compilation parameters you normally would (e.g. -I, -D, -g, -Wall, etc.).

Of course in order for a mock to be useful to us, it will need some kind of interface to interact with. We can emit the mock API for any Mimicc-built mock with the --hout option.

  
(mimicc) user-prompt$ mimicc -c -o mock-drv.o drv.h --hout=mock-drv.h
  

Of course the mock itself isn’t all that useful unless we’re actually trying to test something. Let’s look at a concrete example. Consider the following hypothetical contrived production code in uut.c which we want to test.

uut.c:

  
    #include <string.h>

    #include "drv.h"
    #include "uut.h" /* declares connect(), etc. not shown here */

    state_t connect(driver_t *drv)
    {
        char ret_buf[32];
        error_t res;

        res = driver_init(drv);
        if (res != SUCCESS) {
            return STATE_ERROR;
        }
        res = driver_read(drv, ret_buf, sizeof(ret_buf));
        if (res != SUCCESS) {
            return STATE_ERROR;
        }
        if (strncmp(ret_buf, "HELLO", sizeof(ret_buf)) == 0) {
            return STATE_CONNECTED;
        }

        return STATE_IDLE;
    }
  

The functions driver_init(), and driver_read() are external dependencies declared in drv.h, which we need to mock in order to construct a “true” unit test of the function connect() in uut.c. We already built this by running Mimicc on drv.h above, now we just need to write the test code. Let’s take a look at what’s in the production header file drv.h so we know how best to control the autogenerated mock’s behavior:

drv.h:

  
    #ifndef __DRV_H__
    #define __DRV_H__

    #include <stdint.h>

    typedef int error_t;
    #define SUCCESS 0
    #define ERROR   1

    typedef struct driver {
        uint32_t handle;
        uint8_t buf[1024];
        uint16_t readIdx;
    } driver_t;

    /*!
     * @brief Initialize the driver
     * @param[in] drv Pointer to the driver instance
     */
    error_t driver_init(driver_t *drv);

    /*!
     * @brief Buffered read from the device
     * @param[in] drv Pointer to the driver instance
     * @param[out] ret_buf Pointer to where the consumed data should be stored
     * @param[in] amt The amount of data to read
     */
    error_t driver_read(driver_t *drv, char *ret_buf, unsigned amt);

    #endif /* __DRV_H__ */
  

Note the usage of doxygen comments to specify I/O direction of the parameters. This will be important later. In particular ret_buf is an output parameter in driver_read().

test.c:

  
    #include <stdio.h>
    #include <stdlib.h>

    #include "mock-drv.h"
    #include "uut.h"

    void mock_fatal(const char *pLocation, unsigned count, const char *pMsg)
    {
        const char *pFmt = "assertion failure in '%s': iter %d, msg: %s";
        fprintf(stderr, pFmt, pLocation, count, pMsg);
        exit(1);
    }
    void mock_failure(const char *pLocation, unsigned count, const char *pMsg)
    {
        mock_fatal(pLocation, count, pMsg);
    }

    int main(int argc, char **argv) {
        driver_t mock_drv;
        char mock_ret_data[] = "HELLO";

        MOCK_FUNCTIONS(drv).driver_init.expect(&mock_drv, 0);
        MOCK_FUNCTIONS(drv).driver_init.andReturn(SUCCESS);

        MOCK_FUNCTIONS(drv).driver_read.expect(&mock_drv, 0, 32);
        MOCK_FUNCTIONS(drv).driver_read.andReturn(
            SUCCESS, mock_ret_data, sizeof(mock_ret_data));

        char actual_ret[8];
        state_t res = connect(&mock_drv);
        if (res != STATE_CONNECTED) {
            fprintf(stderr, "Test failed, expected %d, got: %d\n",
                    STATE_CONNECTED, res);
            return 1;
        }
        printf("SUCCESS\n");
        return 0;
    }
      

Here we’ve also introduced the mock_fatal() and mock_failure() functions, which are the two and only symbols that must be defined in our test binary for Mimicc-generated mocks to work correctly. They are the functions that are called when the mock code encounters either a fatal or non-fatal error condition respectively. Most users will simply treat them as a wrapper for an assertion failure in the test framework of their chosen environment. Their prototypes are as follows:

  
    void mock_fatal(const char *pFuncName, unsigned iter, const char *pMsg);
    void mock_failure(const char *pFuncName, unsigned iter, const char *pMsg);
  

We now have all the ingredients, we finish building the test with our regular C compiler and run the executable, deactivating the environment when we’re done.

  
(mimicc) user-prompt$ clang -o test-exe mock-drv.o uut.c test.c
(mimicc) user-prompt$ ./test-exe 
SUCCESS
(mimicc) user-prompt$ end_mimicc
user-prompt$
  

This is our first introduction to the mock API and how we use it to build tests. A full description of how this API is structured is well beyond the scope of a quick start guide, so we encourage you to read the API reference below. For the purposes of getting up and running, you can always directly read the mock API header file mock-drv.h that was emitted by Mimicc (not recommended).

In general the API is emitted with a logical structure that mirrors the declarations in your own code, thus once a few rules are understood and remembered, you should never need to read the auto-generated header, you can use your own code as reference.

Common Examples

Function Pointer Mocks

It is extremely common, particularly in traditional “systems” C development to use vtable-like structures to create polymorphic interfaces. To support this design pattern, Mimicc automatically generates mock functions for function pointer variables and struct fields that it finds. They are accessed via the mockFunctionPointers() call, which is either a member of a STRUCT/CLASS/UNION API node, or a member of a VARIABLES node in file and namespace scopes.

Consider for example the following abstract file operations structure, similar to the “character device” file operations interface used in the Linux kernel:

fops.h:

  
    struct fops {
        int (*open)(void);
        int (*read)(char *buf, const unsigned amt);
        int (*write)(const char *buf, const unsigned amt);
        int (*seek)(const int offs);
        void (*close)(void);
    };
    

Now consider the following function print(), in driver.c, which uses this abstract interface in order to be able to operate on any device which implements the interface:

driver.c:

  
    #include "fops.h"

    void print(const struct fops *drv, char *data, unsigned amt)
    {
        int res = drv->open();
        if (res >= 0) {
            unsigned remain = amt;
            while (remain > 0) {
                res = drv->write(data, remain);
                if (res < 0) {
                    break;
                }
                data += res;
                remain -= res;
            }
            drv->close();
        }
    }
  

We would like to write a unit test for the print() function, so we will need an instance of the fops struct with valid function pointers. We do this by running Mimicc on fops.h, and using the mockFunctionPointers() call on the fops struct node. The test setup code would be as follows:

  
    struct fops drv;
    char buf[32];

    MOCK_STRUCT(fops.fops).mockFunctionPointers(&drv);
    MOCK_VARIABLES(fops.fops).open.expect();
    MOCK_VARIABLES(fops.fops).open.andReturn(0);

    MOCK_VARIABLES(fops.fops).write.expect(buf, 0, sizeof(buf));
    MOCK_VARIABLES(fops.fops).write.andReturn(sizeof(buf));

    print(&drv, buf, sizeof(buf));
  

It is important to note that function pointer mocks are global in scope, which means each time a call to mockFunctionPointers() is made, the pointers initialized are given the same address every time. This is particularly relevant in test cases where more than one initialized “vtable” object might be needed. When the code under test executes the functions pointed to by these pointers, they will branch to the same mock function implementation.

Test authors must take care to note the expected order of calls and returns.

Arrays And Pointer Types

In both C and C++, pointers and built-in array types are commonly used both as inputs and outputs to functions. In order to simplify tests that treat pointers as arrays, Mimicc’s API and mock behavior automatically treat all pointer types (except for implicit this) as arrays and allows users to perform iterated comparison or assignments of array members.

For example consider the following plain C header foo.h which declares an I/O interface:

foo.h:

  
    #include <stddef.h>
    /*!
     * @param[out] out A place to store data we read
     * @param[in] len The amount of data to read
     */
    int read(char *out, size_t len);
    /*!
     * @param[in] in Where to get data to write
     * @param[in] len The amount of data to read
     */
    int write(const char *in, size_t len);
  

When the mimicc command generates mocks for these functions, out and in will both be assigned an accompanying depth parameter to allow specifying how many values should be checked (for read) or copied (for write). A depth of 0 will simply perform a check on the pointer itself in the case of read, or skip copying in the case of write.

Or more explicitly, consider the following example usage of the mock:

  
    int ret = 0;
    char dataVectWrite[4] = {1, 2, 3, 4};
    char dataVectRead[4] = {1, 2, 3, 4};
    char retCheck[4] = {0, 0, 0, 0};
    MOCK_FUNCTIONS(foo).write.expect(dataVectWrite, 4, 4);
    MOCK_FUNCTIONS(foo).write.andReturn(4);

    MOCK_FUNCTIONS(foo).read.expect(4);
    MOCK_FUNCTIONS(foo).read.andReturn(4, dataVectRead, 4);

    ret = write(dataVectWrite, 4);
    if (ret != 4) mock_failure(__FILE__, 0, "invalid write result");

    ret = read(retCheck, 4);
    if (ret != 4) mock_failure(__FILE__, 0, "invalid read result");

    for (int i = 0; i < 4; i++) {
        if (retCheck[i] != dataVectWrite[i]) {
            mock_failure(__FILE__, i, "invalid read result");
        }
    }
  

Note a couple of important features of this setup. The first is that write.expect takes 3 parameters. The first is a pointer to an array that will be used to compare what the function eventually gets called with. The second parameter is the “depth” for that pointer, which tells the mock how many values to compare. If instead of 4, we specified 0 here, it would mean we want the write() mock call to compare the pointer value instead of its contents. The last is actually the expected value of the len parameter that will be compared to the len parameter given to the write() call later.

Also note that read.expect() only takes one parameter, and it’s the expected len parameter. The first parameter in the read() function declaration was declared an output, and is therefore excluded from expect() calls and input checking in general. Instead we see it being used in the read.andReturn() call, with an accompanying “depth” parameter to specify how many values should be copied.

In general if you’re using Mimicc with any regularity, it’s good to get comfortable with the idea that function parameters which are pointers will always be expanded in this way, as it’s more common for a software engineer to be interested in the contents of a memory location than its address. If the address is of interest, simply use a depth of 0.

Overloaded Functions And Methods

Mimicc’s mock API is designed assuming engineers need to be able to do two very important things:

  1. Use the API in ordinary C and C++ test files
  2. Use the API to directly refer to unique symbols in an executable

The first of these goals is easy enough to satisfy. The second may have many possible solutions. Mimicc’s approach aims to use the compiler itself to help resolve ambiguities which arise when referring to mock symbols.

Consider the following simple C++ header file foo.h:

  
    int compute(void);
    int compute(unsigned char *data);
  

Here compute() is an “overloaded” function, which will have different linkage depending on how it is called. The compiler implicitly generates a “mangled” symbol for each of these functions in order to have a globally unique, disambiguated symbol to use during compilation and linking. This mangled symbol is invisible to the language user.

When these functions are mocked, the Mimicc mock API similarly needs to know exactly which version of the compute() function is being referenced when expect() or andReturn() is called. This is accomplished using the API’s resolve() call. Whenever Mimicc encounters an overloaded function within a namespace or record context, it will emit a templated resolve() function with specializations for each explicit function type.

In the above example, we would access the mock API for the first compute() function as follows:

  
    MOCK_FUNCTIONS(foo).compute.resolve<int (*)(void)>();
  

We would access the second compute() function as follows:

  
    MOCK_FUNCTIONS(foo).compute.resolve<int (*)(unsigned char *)>();
  

Note the use of a function pointer type declaration to explicitly describe which function signature we want. If you’re not familiar with C/C++ function pointer syntax, you will quickly become familiar with it if you’re frequently mocking overloaded functions.

The same principle extends to overloaded class methods, including overloaded operators and constructors. Consider the example file foo.cpp, which declares class bar as follows:

  
    class bar {
    public:
        int compute(void);
        int compute(void) const;
    };
  

The const qualifier on the second compute() declaration only makes sense in the context of a class instance method. We access the first compute() method as follows:

  
    MOCK_FUNCTIONS(foo.bar).compute.resolve<int (bar::*)(void)>();
  

We access the second compute() method as follows:

  
    MOCK_FUNCTIONS(foo.bar).compute.resolve<int (bar::*)(void) const>();
  

Note the use of a member pointer type declaration. This logical syntax may be unfamiliar to some C++ developers, but should quickly become familiar through regular usage of the API.

Since mock API access expressions can become particularly long with overloaded functions, we recommend using the post-C++11 auto keyword in your test code to access the specific API node and limit the amount of required typing:

  
    auto ol       = MOCK_FUNCTIONS(foo.bar).compute.resolve<int (bar::*)(void)>();
    auto const_ol = MOCK_FUNCTIONS(foo.bar).compute.resolve<int (bar::*)(void) const>();
    const_ol.expect(...);
    const_ol.andReturn(...);
  

Operators

Consider the following C++ header file foo.h, which declares a simple class bar:

foo.h:

  
    class bar {
    public:
       bar &operator<<(bar &arg);
       bar &operator<<(const bar &arg);
    };
  

This class might be used in a function uut() as follows:

uut.cpp:

  
    void uut(bar &left, const bar &right) {
        left << right;
    }
  

We would like to write a test for uut(). In our test, we want to control and validate the behavior of the class’s << operator, which will have a different overload resolution depending on the argument type (i.e. const or non-const). The right argument to the uut() function is const-qualified, so we know that the const argument version of << will be selected. We can set expectations accordingly.

Our example test code might look like this:

  
    bar testLeft, testRight;

    auto cApi = MOCK_OPERATORS(foo.bar).LessLess.resolve<bar &(bar::*)(const bar &)>();
    cApi.expect(&testLeft, testRight);
    cApi.andReturn(testLeft);
    cApi.setCompare(
        NULL,
        [](const bar &lhs, const bar &rhs) -> bool { return (&lhs == &rhs); }
    );

    uut(testLeft, testRight);
  

Note the use of the resolve() method required to select the specific API node for the overloaded << call that we’re interested in, in this case the one taking a const argument.

Note also the use of the setCompare() API endpoint, which is described in more detail in the Mock API Reference section below. In general the setCompare() function takes a list of function pointers to act as callbacks when the mock implementation needs to compare a type as it appears in the argument list. Passing a “NULL” value to any of the function pointer parameters simply tells the mock to use the default comparison for that argument. If a type does not have a built-in comparison, and also has no user-defined comparison operation, the mock implementation will call mock_failure() when asked to make a comparison.

In this specific example, we’ve made use of the modern C++ “lambda” feature, specifically that the compiler is able to automatically convert a lambda expression like this into a conventional C function pointer.

Nontrivial Constructor Mocks

It is extremely common in C++ to declare classes which have nontrivial construction requirements. Examples include:

If any of these cases are encountered, Mimicc may refuse to serialize a mock constructor implementation, and will emit a warning. In these cases, Mimicc will still create API endpoints, including a shadowCall which actually implements the body of the mock constructor. The only thing it will exclude is the constructor’s definition (which will provide the symbol needed by the linker) including its initializer list.

If a unit test will require that this mock constructor to be present in the executable, the user will be required to include a unique definition in their test code, and call the shadowCall API endpoint to run the “machinery” for the mock.

Here’s a simple example of a class bar, declared in foo.h, for which Mimicc is unable to serialize a constructor mock implementation.

foo.h:

  
    // default constructor implicitly deleted:
    struct imp_del1 {
        int &ref;
    };

    struct bar {
        bar();
        imp_del1 m_member1; // no default construction
    };
  

When the mimicc command runs, all requisite mock API endpoints will be created for the bar class, but the actual constructor implementation bar::bar() will not. To use this constructor in a test, the user will need to include the wrapped definition somewhere in the test executable.

test.cpp:

  
    int val = 0;
    imp_del1 dummy = { val };
    bar::bar() : m_member1(dummy)
    {
        MOCK_CONSTRUCTOR(foo.bar).shadowCall(this); 
    }
  

Ideally, all such implementations should be a simple passthrough to the shadowCall() endpoint with nothing else in the definition. The goal is simply to require the class author to explicitly define the initialization list as they see fit.

See also the Unmocked Constructors troubleshooting example below.

Singletons

Mocking of singletons is enabled by the makeInstance() call for user-declared constructor nodes. The makeInstance() call is always public, regardless of the class’s access specifier declarations. Consider the following standard singleton declaration class ‘bar’ from file foo.h:

  
    class bar
    {
    public:
        static bar &instance(void);
        void init(const char *argv0);

    private:
        ~bar();
        bar();
        bar(SingletonTest const&);
        bar& operator=(SingletonTest const&);
    };
  

You can mock the singleton accessor function instance() as in the following example test. Note that there are multiple overloaded constructor nodes due to the presence of both the default and copy constructor declarations, thus a resolve() call is needed in the API

  
    bar *inst = MOCK_CONSTRUCTOR(foo.bar).
        resolve<void (bar::*)(void)>().makeInstance();

    MOCK_FUNCTIONS(foo.bar).instance.expect();
    MOCK_FUNCTIONS(foo.bar).instance.andReturn(*inst);

    bar &inst_check = bar::instance();
  

Types Requiring Move

One of the most common non-copy-constructible types used in everyday development is std::unique_ptr, including containers of std::unique_ptr. Consider the following non-trivial method getNames() from class bar, declared in foo.h.

  
    #include <array>
    #include <memory>
    #include <string>

    struct bar {
        std::array<std::unique_ptr<std::string>, 4> getNames(void);
    };
  

Assume we are testing a method which calls getNames() and expects to get an array of strings in return: “Paul”, “John”, “George”, “Ringo”. We can manufacture this scenario in our test code as follows:

  
    std::array<std::unique_ptr<std::string>, 4> namesVect;
    namesVect[0] = std::make_unique<std::string>("Paul");
    namesVect[1] = std::make_unique<std::string>("John");
    namesVect[2] = std::make_unique<std::string>("George");
    namesVect[3] = std::make_unique<std::string>("Ringo");

    bar uut;
    MOCK_FUNCTIONS(foo.bar).getNames.expect(&uut);
    MOCK_FUNCTIONS(foo.bar).getNames.andReturn(std::move(namesVect));
  

Command Line Reference

For a brief description of the mimicc-specific command line options, run mimicc with --help.

  
user-prompt$ mimicc --help
OVERVIEW: Mimicc C/C++ mock generation tool
...
  

Note however that mimicc (and mimicc++) are also “front-ends” for Clang, and thus support the full set of Clang input options. For details on clang usage, please refer to Clang documentation.

Mock API Reference

Rules

The Mock API follows a set of rules and patterns designed to make the API structure logical and consistent, though it is not always intuitive. This section provides the complete specification of the API structure as declared in the emitted source files. This does not necessarily reflect how it should ideally be used in test code. Please refer to the examples section for hints on ideal usage.

In the following set of rules, an “endpoint” always refers to a function that can be called in ordinary C/C++ code.

  1. The API is declared as a tree of nested structs following the declaration scope of the input file, with the “sanitized” file name as the outermost node.

  2. Namespace scopes (i.e. files, namespaces, and records) are dot-dereferenced hierarchically with no intermediate nodes, e.g. file.namespace1.struct1.subStruct1. It is important to note that C only has one single namespace scope, and it is the global translation unit scope. Thus even if you declare a struct as nested within another struct, if you’re using Mimicc and Clang in “pure-C” mode, all mock API nodes will fall under the root “file” node.

  3. All nodes with namespace scope have a CTL node. The CTL node’s direct children will vary depending on the specific type of the parent node.

  4. The functions child node of a CTL node has no functional endpoints, and contains a set of children, which are function nodes. The names of these function nodes are the names of the functions declared in the parent scope. For example:

    file.namespace1.struct1.substruct1.CTL.functions.foo

  5. The variables child node of a CTL node has no functional endpoints, and contains a set of children, which are variable nodes. The names of these variable nodes are the names of the variables declared in the parent scope. For example:

    file.namespace1.struct1.substruct1.CTL.variables.foo

  6. The operators child node of a CTL node has no functional endpoints, and contains a set of children, which are function nodes. The names of these function nodes are the “spelled” names of the operators declared in the parent scope. For example:

    file.namespace1.struct1.substruct1.CTL.operators.PlusPlus

    For a list of operator spellings, refer to the section on operators below.

  7. The structors child node of a CTL node has no functional endpoints, and contains either or both of two possible children: a constructor node, and a destructor node, both of which are function nodes. For example:

    file.namespace1.struct1.substruct1.CTL.structors.destructor

  8. A function node can either itself be a function implementation node, or an overloaded function node.

  9. A function implementation node may have any of the following API endpoints:

Convenience Macros

Since the specific API structure for all node types may in some cases be difficult to remember, we recommend using the provided convenience macros in accessing them. Each call to an API endpoint should use exactly one of these macros as the root expression (i.e. they are not nestable). These macros are as follows:

Refer to the examples section for hints on how to use these macros.

Nodes With Namespace Scope (File, Namespace, Record)

reset()

The reset() endpoint is used to clear mock state for all children of the current node. Mock state includes recorded expectations, returns, and user override callbacks. Almost all “controllable” nodes have a reset() endpoint, which allows for fine-grain control over the state of the mocks.

A reset() call on the root file node will clear all mock state globally.

mockFunctionPointers()

The mockFunctionPointers() endpoint is used to initialize a set of function pointers to point to mock function implementations.

If the parent node is a namespace or file scope, the function takes no arguments, and initializes any function pointer variables (note: not types) with external linkage found and mocked by Mimicc within the given scope.

If the parent node is a record, the function takes a pointer to the record type that this node refers to, and the function will recursively initialize all function pointers declared within the record.

It’s important to note that all calls to mockFunctionPointers() will initialize all dependent pointers to the same mock function address every time. This is particularly relevant when used to initialize C-style vtable or operations structures. For example, consider a struct file_operations declared in foo.h, which contains mockable function pointers. This might be used in a test as follows:

test.c:

  
    struct file_operations ops1;
    struct file_operations ops2;
    MOCK_RECORDS(foo.file_operations).mockFunctionPointers(&ops1);
    MOCK_RECORDS(foo.file_operations).mockFunctionPointers(&ops1);
  

After the two calls to mockFunctionPointers(), all function pointers in ops1 and ops2 will have been initialized to the same addresses. This will be important to keep in mind when writing expectations and returns on the mock function API node.

Special Endpoints For Record Nodes

setCompare()

The setCompare() endpoint sets a user-provided function callback to be used whenever a comparison of the given mocked type is required within the mock function runtime implementation. For details on how comparisons work within the mock implementation, refer to the section on mock runtime behavior below.

The setCompare() function takes a pointer to a comparison function as its argument. The comparison function takes two pointers of the type that this record node refers to and returns a bool.

During mock function execution, this comparison operation will take precedence over the default comparison operation, but not over the comparison operation provided by the function API node.

setCompareEnable()

The setCompareEnable() endpoint enables or disables all comparisons of this type in mock runtime function execution. For details on how comparisons work within the mock implementation, refer to the section on mock runtime behavior below.

The setCompareEnable() function takes a true/false value to enable or disable comparison respectively.

Note that comparison of a mocked type can be disabled either on the record API node, or on the function API node that uses it as an argument. In order for comparison to proceed, both comparisons must be enabled, which is the default state.

setVolatileCompare()

This is the same as setCompare(), except that the function provided will be used when a comparison is required on the mock type, which is also volatile qualified. This is required to handle cases where a mock function argument is of mocked type with a volatile qualifier. In these cases the only way to use the non-qualified comparison operation for the mocked type would be to discard the volatile qualifier, which could have unintended side effects.

setVolatileCompareEnable()

This is the same as setCompareEnable(), except the enable/disable only refers to the volatile qualified comparisons of the mocked type.

setAssign()

The setAssign() endpoint sets a user-provided function callback to be used whenever an output assignment of the given mocked type is required within the mock function runtime implementation. For details on how output assignments work within the mock implementation, refer to the section on mock runtime behavior below.

The setAssign() function takes a pointer to an assignment function as its argument. The assignment function takes two pointers of the type that this record node refers to, where the first argument is the “left hand side” of an ‘=’ operation, and the second argument is the “right hand side”.

During mock function execution, this assignment operation will take precedence over the default assignment operation, but not over the assignment operation provided by the function API node.

setAssignEnable()

The setAssignEnable() endpoint enables or disables all output assignments of this type in mock runtime function execution. For details on how assignments work within the mock implementation, refer to the section on mock runtime behavior below.

The setAssignEnable() function takes a true/false value to enable or disable assignment respectively.

Note that output assignment of a mocked type can be disabled either on the record API node, or on the function API node that uses it as an argument. In order for output assignment to proceed, both assignments must be enabled, which is the default state.

setVolatileAssign()

This is the same as setAssign(), except that the function provided will be used when an output assignment is required on the mock type, which is also volatile qualified. This is required to handle cases where a mock function argument is of mocked type with a volatile qualifier. In these cases the only way to use the non-qualified assignment operation for the mocked type would be to discard the volatile qualifier, which could have unintended side effects.

setVolatileAssignEnable()

This is the same as setAssignEnable(), except the enable/disable only refers to the volatile qualified assignment of the mocked type.

Function Nodes

A function node may either be a function implementation, or if the function is overloaded it will be a container for a set of function implementations, one for each overloaded version.

For overloaded function nodes, the names of each implementation will be considered opaque to users. In this case, the node will declare exactly one endpoint: resolve(). Users are expected to rely on resolve() to gain access to the endpoints for the desired function implementation.

resolve()

The syntax for the resolve() endpoint is as follows:

  
    nodetype resolve<specific decl>(void);
  

Where nodetype is an opaque Mimicc-generated struct type which declares the Mock API for the function of interest, and specific decl is a complete function or member function declaration which you want to address.

For an example on how to use this, refer to the example section above.

Function Implementation Nodes

shadowCall()

The shadowCall() endpoint implements the body of the mock implementation. The specific behavior is described in detail in the mock behavior section below. Calling this function will represent a “tick” of the mock’s machinery, in which case recorded expectations will be checked, and recorded returns will be copied out.

The mock function implementation is the primary user of this function. As an API endpoint, it can be used to write side-effect hook functions, or non-default constructors that properly check input arguments and return outputs using the default mock behavior.

expect()

The argument list for an expect() call always mirrors an “expanded”, and “filtered” version of the argument list of the function being mocked. Here “expanded” means exactly three things:

  1. If the function being mocked is a class member function, the implicit this pointer is included explicitly in the declaration, including CV qualifiers.
  2. If an argument is a pointer type, a “depth” parameter will immediately follow it, which tells the mock API whether a comparison should be performed on the pointer itself (i.e. a depth of 0), or iterate and perform a dereferenced check on “depth” number of items.
  3. Variadic ellipses become a va_list.

Here “filtered” means exactly one thing:

  1. Arguments which are declared to be “output only” in the original source are excluded.

andReturn()

The argument list for an andReturn() call always starts with a declaration that is the ordinary return type of the function being mocked. Arguments following the return type mirror an “expanded”, and “filtered” version of the argument list of the function being mocked. Here “expanded” means exactly two things:

  1. If an argument is a pointer type, a “depth” parameter will immediately follow it, which tells the mock API whether a copy should be skipped (i.e. a depth of 0), or iterate and perform a dereferenced copy on “depth” number of items.
  2. Variadic ellipses become a va_list.

Here “filtered” means exactly one thing:

  1. Arguments which are declared to be “input only” in the original source are excluded.

Note that the implicit this pointer can never be declared to be an output.

setReturnGenerator()

The setReturnGenerator() endpoint is used to supply a callback for the mock to use when generating a default return value. Default returns are needed whenever a mock is called and no previous andReturn() call has been made for the mock to supply an explicit return value fur the current iteration.

For built-in types and default constructible types, Mimicc will automatically generate a zero or value initialized default return on each iteration, unless an explicit return generator has been set via the setReturnGenerator() call, in which case the user-supplied default generator will override Mimicc’s built-in generator.

For functions with non-default-constructible return types, an attempt to generate a default return without a user-supplied generator will trigger a call to mock_fatal(). The result of the mock function call in these cases is undefined.

setAssign()

The setAssign() endpoint is used to supply a callback for the mock to use when assigning an output value from the function’s argument list. Mimicc’s desired default assignment operator is the standard C/C++ = operator, which is always defined for built-in types, and may be defined for some compound types, dependent on the result of overload resolution check on the argument type.

In cases where it is not defined, a user-defined operation may be needed, which is provided either via the setAssign() call for the function implementation node, or the setAssign() call for the corresponding record type node, if it exists (see “Special Endpoints For Record Nodes” above.

If no default assignment operation exists, and no user-defined operation has been supplied either for the function or the type, the mock will call mock_failure() when asked to perform an assignment operation.

The arguments to the setAssign() call are a list of function pointers, one for each argument which is declared as an output in the declaration of the function being mocked. Each function pointer will return a bool type, and will take two input parameters, destination, and source, which are the left and right hand sides respectively of a logical assignment operation. The types should exactly mirror the types as they were declared in the mocked function declaration.

A NULL value given to any function pointer parameter in the setAssign() argument list will be treated as a NOP, meaning it will remain in whatever state it was in before the setAssign() call.

Assignment callbacks supplied via the function implementation node setAssign() call take precedence over the record node setAssign() call.

setAssignEnable()

The setAssignEnable() endpoint is used to enable and disable output copying on some or all parameters in a mock function argument list. The setAssignEnable() call simply takes a set of boolean values, one for each mock function parameter which is considered an output.

setCompare()

The setCompare() endpoint is used to supply a callback for the mock to use when assigning an output value from the function’s argument list. Mimicc’s desired default comparison operator is the standard C/C++ == operator, which is always defined for built-in types, and may be defined for some compound types, dependent on the result of overload resolution check on the argument type.

In cases where it is not defined, a user-defined operation may be needed, which is provided either via the setCompare() call for the function implementation node, or the setCompare() call for the corresponding record type node, if it exists (see “Special Endpoints For Record Nodes” above.

If no default comparison operation exists, and no user-defined operation has been supplied either for the function or the type, the mock will call mock_failure() when asked to perform a comparison.

The arguments to the setCompare() call are a list of function pointers, one for each argument which is declared as an input in the declaration of the function being mocked. Each function pointer will return a bool type, and take two input parameters, lhs, and rhs, which are the left and right hand sides respectively of a logical comparison operation. The types should exactly mirror the types as they were declared in the mocked function declaration.

A NULL value given to any function pointer parameter in the setCompare() argument list will be treated as a NOP, meaning it will remain in whatever state it was in before the setAssign() call.

Comparison callbacks supplied via the function implementation node setCompare() call take precedence over the record node setCompare() call.

setCompareEnable()

The setCompareEnable() endpoint is used to enable and disable input checking on some or all parameters in a mock function argument list. The setCompareEnable() call simply takes a set of boolean values, one for each mock function parameter which is considered an input, including the implicit this pointer for C++ instance methods.

This function can be particularly useful when testing functions which rely on object pointers which may be declared on the stack or otherwise “dynamic” in nature and undiscoverable at the time of test construction.

setHook()

The “hook” of a mock is a general-purpose user-defined side effect function that will be called as the last step in executing a mock function. In other words, after all argument handling and checking is performed including copying output arguments, the function will branch to the hook function and finish execution from there.

The arguments to the hook function are the “expanded” argument list for the mock function, where “expanded” means two things:

  1. If the function being mocked is a class member function, the implicit this pointer is included explicitly in the declaration, including CV qualifiers.
  2. Variadic ellipses become a va_list.

Since the hook is the last step in executing a mock function, the hook is expected to supply the return value for the mock. Values passed to the andReturn call for the current iteration will be ignored and discarded if a hook is set.

getImplementation()

The getImplementation() call is a special endpoint for function pointer mocks. Its purpose is to export the address of the global mock function implementation for the given function pointer typedef or variable/field. This is primarily useful when writing unit tests for code which has functions taking pointers as arguments.

In general when Mimicc encounters either a function pointer typedef declaration, function pointer global variable (declared with extern), or function pointer structure field, it will emit a mock function implementation which conforms to the function prototype in the declaration. Unlike ordinary functions, this implementation will have no globally exported/public symbol, but getImplementation() will return the address declared as a function pointer, which allows you to assign it directly to conforming function pointer types, including function arguments.

Note that for ordinary function nodes (i.e. non-function-pointer), the getImplementation() endpoint will not be emitted.

Structor Nodes

Structor nodes are identical to function nodes except that they only support two function names: constructor, and destructor. The constructor node may be overloaded if the input source file had multiple constructors declared including copy, move, or conversion constructors.

Special Endpoints For Constructors

makeInstance()

The makeInstance() call is a special endpoint for constructor mocks. Its purpose is to provide a “back door” for creating object instances when access specifiers (i.e. private, protected), would otherwise prevent this. If a constructor is declared public and non-deleted, makeInstance() can be ignored, and the usual C++ new operator can be used instead.

Operator Nodes

Operator nodes are identical to function nodes except that in place of a function name for a child node, there will be an operator name.

Operator names follow Clang spelling convention. The full set of overloaded operator node names is as follows:

  
Plus                 : "+"
Minus                : "-"
Star                 : "*"
Slash                : "/"
Percent              : "%"
Caret                : "^"
Amp                  : "&"
Pipe                 : "|"
Tilde                : "~"
Exclaim              : "!"
Equal                : "="
Less                 : "<"
Greater              : ">"
PlusEqual            : "+="
MinusEqual           : "-="
StarEqual            : "*="
SlashEqual           : "/="
PercentEqual         : "%="
CaretEqual           : "^="
AmpEqual             : "&="
PipeEqual            : "|="
LessLess             : "<<"
GreaterGreater       : ">>"
LessLessEqual        : "<<="
GreaterGreaterEqual  : ">>="
EqualEqual           : "=="
ExclaimEqual         : "!="
LessEqual            : "<="
GreaterEqual         : ">="
Spaceship            : "<=>"
AmpAmp               : "&&"
PipePipe             : "||"
PlusPlus             : "++"
MinusMinus           : "--"
Comma                : ","
ArrowStar            : "->*"
Arrow                : "->"
Call                 : "()"        
Subscript            : "[]"        
New                  : "new"       
Delete               : "delete"    
Array_New            : "new[]"     
Array_Delete         : "delete[]"
  

For example given a class bar in file foo.h declared as follows:

  
    class bar {
    public:
        bar &operator+(bar &arg);
    };
  

You can set an expectation on the mock ‘+’ operator using the API as follows:

  
    MOCK_OPERATORS(foo.bar).Plus.expect(uut);
  

Variable Nodes

Variable nodes are primarily used to access function pointer mocks. If a variable or field is a function pointer, the API for the variable node will be the same as a function node.

Mock Function Behavior Reference

The API described in the section above is used to control the behavior of the mock functions of which this tool is the focus. This section describes how each mock function behaves when called during unit test run time.

High Level Description

The specific instructions emitted in a mock function depend on the return type of the function, its argument types, and their intended I/O direction (i.e. in, out, or both). All mock functions run the following set of high level operations in the order shown:

Note that the shadowCall() is exported as an endpoint in the mock function node’s API. This can be used in the body of user-defined hooks to run the unit test machinery alongside your own custom side-effect code, though it is optional. The shadowCall() function behavior is as follows:

The generateReturn() call’s behavior is as follows:

Note that anywhere you see a “copy-out” operation, this could mean either a literal memory copy, a call to an object copy constructor, or a call to an object’s move constructor, depending on overload resolution, as described in the elaborating sections below.

In general you should not assume that type comparison and copy/assign operations are well defined by default. For some parameter and return types you may be required to provide a user-defined copy or compare operation for the mock function to run without a test failure. The details of these cases are described in the following sections on type-dependent behavior.

Similarly, you should not assume that the default generateReturn() call will always provide a meaningful result. In some cases a user-defined return generator may be required to avoid unexpected mock_fatal() calls. The details of this are described below in the section on type dependent return generator behavior.

Type-Dependent Behavior For Input Argument Comparison

For the purposes of describing Mimicc mock behavior, input parameter types can be broken into a small set of relevant categories:

  1. Constant-length array type
  2. Pointer type
  3. Reference type (rvalue or lvalue)
  4. Value type

Regardless of type, input comparison can always be disabled via the function node’s setCompareEnable() endpoint.

Constant-Length Array Type

To emit the comparison operation for a constant-length array, Mimicc first looks at the underlying data type exactly one level down in the array. The behavior is then further subdivided between mocked and un-mocked underlying types.

Constant-Length Arrays Of Mocked Type

The behavior of a constant-length array of mocked type is as follows:

Constant-Length Arrays Of Un-mocked Type

For arrays of un-mocked types, Mimicc first determines if the underlying type is “trivially comparable” or not. A “trivially comparable” type is one that has a non-ambiguous == operator defined after overload resolution has been performed. This includes all scalar types, as well as C++ struct and class types with overloaded operators.

Once overload resolution has been performed, and the comparability is known, comparison proceeds as follows:

Pointer Type

To emit the comparison operation for a pointer type, Mimicc first looks at the underlying data type that this pointer points to. The behavior is then further subdivided between mocked, un-mocked, and function underlying types.

Pointer To Mocked Type

The behavior of a pointer to mocked type is as follows:

Pointer To Un-mocked Type

For pointers to un-mocked types, Mimicc first determines if the underlying type is “trivially comparable” or not. A “trivially comparable” type is one that has a non-ambiguous == operator defined after overload resolution has been performed. This includes all scalar types, as well as C++ struct and class types with overloaded operators.

Once overload resolution has been performed, and the comparability is known, comparison proceeds as follows:

Pointer To Function

The behavior of a pointer to function is as follows. Note that pointers to function will never have an associated depth value.

Reference Type

To emit the comparison operation for a reference type, Mimicc first looks at the underlying data type that this reference refers to. The behavior is then further subdivided between mocked and un-mocked underlying types.

Reference To Mocked Type

The comparison for a reference to mocked type proceeds as follows:

Reference To Un-mocked Type

For references to un-mocked types, Mimicc first determines if the underlying type is “trivially comparable” or not. A “trivially comparable” type is one that has a non-ambiguous == operator defined after overload resolution has been performed. This includes all scalar types, as well as C++ struct and class types with overloaded operators. The comparison for a reference type then proceeds as follows:

Value Type

A ‘value’ type for the purposes of Mimicc comparison operations is anything that is neither pointer (including constant length array) nor reference. The specific comparison operation is further sub-divided between values of mocked and un-mocked types.

Mocked Value Type

Before determining how a mocked value comparison should be done, Mimicc must first perform overload resolution to determine whether this value type is passed via copy or move construction. Note that in order for an object to be usable as a function argument, it must have at least one and possibly both of these constructors available. Comparison then proceeds as follows:

Un-mocked Value Type

Before determining how a mocked value comparison should be done, Mimicc must first perform overload resolution to determine whether this value type is passed via copy or move construction. Note that in order for an object to be usable as a function argument, it must have at least one and possibly both of these constructors available.

Mimicc must also determine if this type is “trivially comparable” or not. A “trivially comparable” type is one that has a non-ambiguous == operator defined after overload resolution has been performed. This includes all scalar types, as well as C++ struct and class types with overloaded operators.

The comparison then proceeds as follows:

Type-Dependent Behavior For Output Argument Assignment

For the purposes of describing Mimicc mock behavior, output parameter types can be broken into a small set of relevant categories:

  1. Constant-length array type
  2. Pointer type
  3. Reference type (rvalue or lvalue)
  4. Value

Regardless of type, output assignment operations can always be disabled via the function node’s setAssignEnable() endpoint.

Constant-Length Array Type

To emit an assignment operation for a constant-length array, Mimicc first looks at the underlying data type exactly one level down in the array. The behavior is then further subdivided between mocked and un-mocked underlying types.

Constant-Length Arrays Of Mocked Type

The behavior of a constant-length array of mocked type is as follows:

Constant-Length Arrays Of Un-mocked Type

For arrays of un-mocked types, Mimicc first determines if the underlying type is “trivially assignable” or not. A “trivially assignable” type is one that has a non-ambiguous = operator (either copy or move) defined after overload resolution has been performed. This includes all scalar types, as well as C++ struct and class types with overloaded operators.

Once overload resolution has been performed, and the assignability is known, assignment proceeds as follows:

Pointer Type

To emit the assignment operation for a pointer type, Mimicc first looks at the underlying data type that this pointer points to. The behavior is then further subdivided between mocked and un-mocked types.

Pointer To Mocked Type

The behavior of a pointer to mocked type is as follows:

Pointer To Un-mocked Type

For pointers to un-mocked types, Mimicc first determines if the underlying type is “trivially assignable” or not. A “trivially assignable” type is one that has a non-ambiguous = operator defined after overload resolution has been performed (either copy or move). This includes all scalar types, as well as C++ struct and class types with overloaded operators.

Once overload resolution has been performed, and the assignability is known, assignment proceeds as follows:

Reference Type

To emit the assignment operation for a reference type, Mimicc first looks at the underlying data type that this pointer points to. The behavior is then further subdivided between mocked and un-mocked underlying types.

Reference To Mocked Type

The assignment for a reference to mocked type proceeds as follows:

Reference To Un-mocked Type

For references to un-mocked types, Mimicc first determines if the underlying type is “trivially assignable” or not. A “trivially assignable” type is one that has a non-ambiguous = operator defined after overload resolution has been performed. This includes all scalar types, as well as C++ struct and class types with overloaded operators. The assignment for a reference type then proceeds as follows:

Value Type

A ‘value’ type for the purposes of Mimicc assignment operations is anything that is neither pointer (including constant length array) nor reference. The specific assignment operation is further sub-divided between values of mocked and un-mocked types.

Note that in general “value” types are not typically used as function output types. Traditionally this is reserved for pointer and references. Regardless in both C and C++, it is possible to have “output-like” side effects on any object, even those passed by value. Mimicc makes no assumptions about whether or not a function argument declared as a value can or cannot be used as an output.

Mocked Value Type

Output assignment of a mocked value type is as follows:

Un-mocked Value Type

Output assignment of an un-mocked value type is as follows:

Type-Dependent Behavior For The Ordinary Return “Copy-Out”

In order for a type to be returnable from a function, it must be either copy or move constructible. All plain C types (i.e. POD types) are trivially copyable and can thus be returned directly. Types which are copy constructible will be copy constructed on return. Types which are move constructible will be move constructed on return. Types which are both copy and move constructible will be copy constructed.

Type-Dependent Behavior For The Default Return Generator

Keep in mind as was mentioned in the high level description, the return generator behavior can always be overridden with the setReturnGenerator() API endpoint on the function node. This section describes the default behavior of the return generator when no user override is given.

For the purposes of describing Mimicc mock behavior, ordinary return generator types can be broken into a small set of relevant categories:

  1. Pointer type
  2. Reference type (lvalue or rvalue)
  3. Value type

Pointer Type

The default return value for all pointer types is 0.

Reference Type

There is no default return value generator for reference types. If a function returns a reference type, and no user-provided return generator is available, mock_fatal() will be called.

Value Type

A ‘value’ type for the purposes of Mimicc default return generation is anything that is neither pointer (including constant length array) nor reference. Value types are further sub-divided into default constructible types, and non-default constructible types.

For default-constructible types, if the type is also POD, the value returned will be explicitly zero-initialized. If it is not POD, it will be value initialized via the default constructor.

For non-default-constructible types, mock_fatal() will be called. Return values for non-default-constructible types cannot be auto-generated without a user-provided return generator.

Common Problems

Missing Header Dependencies

The Issue

A missing header dependency error may take a few different forms. For example:

  
TemplateName.h:41:7: error: incomplete type 'clang::TemplateArgument' where a
complete type is required
class TemplateArgument;
      ^
  

Also:

  
In file included from example.h:4:
mimicc-x86_64-Darwin-dist/usr/local/bin/../include/c++/v1/memory:2258:19:
error: invalid application of 'sizeof' to an incomplete type 'device'
    static_assert(sizeof(_Tp) > 0,
                  ^~~~~~~~~~~
mimicc-x86_64-Darwin-dist/usr/local/bin/../include/c++/v1/memory:2517:7:
note: in instantiation of member function
'std::__1::default_delete<device>::operator()' requested here
      __ptr_.second()(__tmp);
      ^
mimicc-x86_64-Darwin-dist/usr/local/bin/../include/c++/v1/memory:2471:19:
note: in instantiation of member function
'std::__1::unique_ptr<device, std::__1::default_delete<device>>::reset' requested here
  ~unique_ptr() { reset(); }
                  ^
mock.cpp:288:120:
note: in instantiation of member function
'std::__1::unique_ptr<device, std::__1::default_delete<device>>::~unique_ptr'
requested here
    ...
                                                                                                                       ^
example.h:6:7: note: forward declaration of 'device'
class device;
  

If you see one of these errors, you likely need to add missing header dependencies in the mock implementation via Mimicc’s cdep or csysdep options. Example:

  
user-prompt$ mimicc -c -o mock.o -I include --hout=mock.h --cdep device.h \
             deviceTree.h
  

Explanation

In C and C++ it is extremely common to use forward declarations of types in header files to either limit the visibility of a class implementation, or to break circular dependencies. In some cases it’s simply a combination of luck and neglect that everywhere a header is used in the source, the appropriate dependency is already included before it.

Clang and LLVM themselves both use this pattern extensively in their source code.

Consider for example the following contrived device and deviceTree classes with a circular dependency.

deviceTree.h:

  
    #ifndef __deviceTree_H__
    #define __deviceTree_H__

    #include <memory>

    class device;

    class deviceTree {
    public:
        static std::unique_ptr<device> newChildDevice(void);
    };

    #endif // __deviceTree_H__
  

device.h:

  
    #ifndef __device_H__
    #define __device_H__

    class deviceTree;

    class device {
    public:
        deviceTree *parent;
    };

    #endif // __device_H__
  

Now consider the following naive usage of the deviceTree interface:

use.cpp:

  
    #include "deviceTree.h"

    void use(deviceTree &tree) {
        std::unique_ptr<device> dev = tree.newChildDevice();
    }
    // error: dev goes out of scope, device::~device() called
    // but no definition available
  

In order to use this interface, the use.cpp module is expected to include both deviceTree.h and device.h to provide a full definition of both classes.

The same problem will be encountered if you attempt to create a mock from deviceTree.h. Mimicc is perfectly happy to generate a mock function for newChildDevice, but this mock can’t compile without the inclusion of device.h to provide the definition of the device class.

The solution is to use the --cdep or --csysdep arguments, which will allow you to specify a header filename as if it were part of the mock source file. Mimicc will perform standard include path lookup including all paths added via command line arguments and produce an error if the file can’t be located.

Use cdep for standard “quote-style” includes, and csysdep for “angle-bracket-style” includes.

Unmocked Constructors

The Issue

If while building a mock you get the following warning:

  
construct.hpp:3:1: warning: Building a constructor for this class will require
constructing member variables which are not default or aggregate constructible.
Please instantiate this constructor in your test code with the desired member
variable construction and call the mock API's 'shadowCall' function from the
body of the constructor.
struct imp_del1 {
^
  

You will need to instantiate the constructor in your test code explicitly and use the mock API to track test statistics. Despite the scary warning, this is usually very straightforward. For example:

  
    notDefaultConstructible::notDefaultConstructible(const char *pArg, int otherArg) :
        m_pArg2("Constructor 2"), m_refArg(g_global)
    {
        MOCK_CONSTRUCTOR(variable_init.notDefaultConstructible).
            shadowCall(this, pArg, otherArg);
    }
  

This is only required if you plan to use this constructor in your tests. If you do call the constructor, and you haven’t provided an implementation stub like the one above, you will see unresolved symbol errors at link time.

Explanation

Mimicc takes a conservative approach to emitting constructor mocks. In particular, if a class constructor requires explicit non-trivial member or base initialization, Mimicc will refuse to emit a mock for the constructor and produce a warning like the one above.

The example class shown in this section has a reference member, which is what causes Mimicc to give up on trying to generate the initialization list and require the user to do it instead. In general Mimicc considers a constructor to be mockable if all of its members and direct bases are either default constructible or aggregate initializable and have no reference type members.

Overflow Of Recorded Expectations Or Returns

The Issue

If while running a test with a Mimicc-built mock, you see an assertion failure that looks roughly as follows:

  
assertion failure in '_muuid57 (expect)': iter 1, msg: expectations exceeds max
  

You have likely called the expect or andReturn functions on a mock function node more times than the default available recorder size allows. You can fix this by increasing the recorder entry counts to the size you require when building the mock.

  
(mimicc) user-prompt$ mimicc -c -o mock.o -DMAX_EXPECT_COUNT=256
-DMAX_RETURN_COUNT=256 foo.h --hout=mock.h
  

Explanation

In order to minimize system and library dependencies in the mock, Mimicc uses statically allocated buffers for storing recorded expectations and returns. These buffers will likely be big enough for the most common test scenarios, but in some cases you may need to record more than these buffers provide by default.

All mock implementations use three preprocessor macros for allocating memory: MAX_EXPECT_COUNT, MAX_RETURN_COUNT, and MAX_INSTANCE_COUNT. These can be overridden via the standard command line -D argument as shown in the example above.


  1. libc++ is not the default C++ standard library implementation on Linux systems. It is available for usage as part of the Mimicc distribution, but without specifying -stdlib=, Mimicc and Mimicc Clang will your built-in C++ standard library.↩︎