1

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?

4

1 回答 1

1

我通过定义一个专门用于构建测试库的新库目标来解决这个问题,并将所有测试可执行文件链接到测试库。我必须在测试库目标上设置编译选项,PUBLIC以便这些选项适用于构建测试目标。然后我不再使用编译选项中的生成器表达式,library-testing因为它暗示这个目标只会被构建用于与测试可执行文件链接。

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
+)

然后在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)

进行这些更改后,我的测试可执行文件都按预期运行。

于 2019-07-16T00:26:40.473 回答