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?
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.