cunit-testingcmakecmocka

CMake and CMocka standard assertions for compiling tests


I have a small static library project that I'm rewriting from building with Makefiles to modern CMake, which I am trying to learn. My project uses assertions quite heavily for checking preconditions, so I have written a very simple custom assertion macro that conditionally expands into a function that prints formatted diagnostics and then aborts if the library was compiled in debug mode, or expands to nothing if the library was compiled in release mode.

However, I want to be able to test whether these assertions fire correctly. Cmocka allows you to test this by calling mock_assert in library code, which cmocka will intercept as part of testing. To this end, I want to have another macro, say LIBRARY_TESTING, that will redefine my custom assertion macro to invoke mock_assert instead of my own assertion function, so assertions can be tested. The final assertion macro can be considered morally equivalent to the following:

// In file include/assertion.h
#ifdef LIBRARY_DEBUG
    #ifdef LIBRARY_TESTING

        // mock_assert is provided by cmocka
        void mock_assert(
            int const result,
            char const *const expression,
            char const *const file,
            int const line);

        #define ASSERT(cond) \
            mock_assert((cond), #cond, __FILE__, __LINE__)
    #else
        // emit_assertion is defined in src/assertion.c
        void emit_assertion(int cond, char const *const msg);

        #define ASSERT(cond) emit_assertion((cond), #cond)
    #endif
#else
    #define ASSERT(cond) // Nothing
#endif

I have been able to get the desired behaviour for building in debug mode (where ASSERT expands to a call to emit_assertion) and release mode (where ASSERT expands into nothing, as desired) through the following Cmake snippet in src/CMakeLists.txt):

target_compile_options(library PRIVATE
    $<$<CONFIG:Debug>:-Og -ggdb3 -DLIBRARY_DEBUG >>
    $<$<CONFIG:Testing>:-Og -ggdb3 -DLIBRAY_DEBUG -DLIBRARY_TESTING >>
)

From which building with either -DCMAKE_BUILD_TYPE=Debug or -DCMAKE_BUILD_TYPE=Release produces the intended behaviour. Extrapolating how CONFIG works here, I also added a generator expression that checks Testing, which defines BASIC_TESTING when compiling. All is well so far.

I start to run into problems when executing unit tests. For the purposes of exposition, the function that I want to test is equivalent to this, defined in include/example.h:

static inline bool example(int *arg)
{
    ASSERT(arg != NULL);
    return *arg == 0;
}

With a corresponding unit test in tests/example.c:

#include "example.h"
#include <cmocka.h>
// Other cmocka required #includes

static void test_example(void **state)
{
    (void) state;
    expect_assert_failure(example(NULL));
}

And the contents of test/CMakeLists.txt:

add_executable(example
    ${CMAKE_CURRENT_SOURCE_DIR}/example.c
)

add_test(example example)

target_include_directories(example PRIVATE
    "${PROJECT_SOURCE_DIR}/include"
)

# 'library' is the static library target defined in the top-level
# CMakeLists.txt
target_link_libraries(example library cmocka)

Now, in order to unit test my library, I want my custom assertions to expand to mock_assert, so I compile my library for testing (as I understand it):

# In ./build
$ cmake -DCMAKE_BUILD_TYPE=Testing .. && make

Everything builds correctly and I have my static library liblibrary.a where I expect it to be. Additionally, my test executable example also compiles and links successfully, but when I run it, the test fails with a segmentation fault as if my custom assertion was never called (and the function attempts to dereference the NULL pointer I intentionally gave it to trigger the assertion). I am reasonably confident that there were no issues linking with cmocka itself, because running the test results in cmocka's fancy command-line output formatting.

In my original makefile-oriented build, all test executable targets would compile a special "testing" library target, and the test executables link to this library target and all assertions are correctly intercepted by cmocka as I would expect. However, in this case, it appears as if the static library I compiled is behaving as if neither LIBRARY_DEBUG or LIBRARY_TESTING were defined -- as evidenced by the segmentation fault.

I'm very new to modern cmake, so I feel like I am misunderstanding something conceptual. My question is:

How can I ensure that my static library is compiled with a particular (set of) compilation option(s) (here it is -DBASIC_TESTING) to ensure that the custom assertions that it fires can be tested with cmocka?


Solution

  • I solved this problem by defining a new library target specifically for building the testing library, and linked all test executables to the testing library. I had to set the compile options on the testing library target to PUBLIC for these options to apply to building the testing targets. I then no longer had any use for the generator expression in the compile options for library-testing as it was implied that this target will only ever be built for linking with testing executables.

    In src/CMakeLists.txt

    add_library(
        library,
        src/example.c)
    
    +add_library(
    +   library-testing,
    +   src/example.c)
    
    target_compile_options(library PRIVATE
        $<$<CONFIG:Debug>:-Og -ggdb3 -DLIBRARY_DEBUG >>
    )
    
    +target_compile_options(library-testing PUBLIC
    +   -Og -ggdb3 -DLIBRARY_DEBUG -DLIBRARY_TESTING
    +)
    

    And then in test/CMakeLists.txt:

    add_executable(example
        ${CMAKE_CURRENT_SOURCE_DIR}/example.c
    )
    
    add_test(example example)
    
    target_include_directories(example PRIVATE
        "${PROJECT_SOURCE_DIR}/include"
    )
    
    # Link with library-testing target instead of library target
    -target_link_libraries(example library cmocka)
    +target_link_libraries(example library-testing cmocka)
    

    After making these changes my test executables all behaved as expected.