Integrating cmake with unit-tests including testing for memory leaks.
1. Write some tests
We’re going to create a dummy “state” module, so let’s invent some dummy functionality and test it.
[test/state.c]
#include <stdarg.h> #include <stddef.h> #include <setjmp.h> #include <cmocka.h> #include "state.h" void test1() { state *s; s = state_new(); assert_non_null(s); assert_int_equal(state_get(s), 0); state_free(s); } void test2() { state *s; int e; s = state_new(); assert_non_null(s); e = state_set(s, 5); assert_int_equal(e, 0); assert_int_equal(state_get(s), 5); e = state_set(s, 42); assert_int_equal(e, -1); assert_int_equal(state_get(s), 5); } int main(void) { const UnitTest tests[] = { unit_test(test1), unit_test(test2), }; return run_tests(tests); }
We’re using cmocka here so make sure it is installed.
Compile
$ mkdir build $ cd build $ cmake .. CMake Error: The source directory "/project/test-driven-c" does not appear to contain CMakeLists.txt. Specify --help for usage, or press the help button on the CMake GUI.
So, we probably need to setup the cmake environment…
2. Setup CMake
[CMakeLists.txt]cmake_minimum_required(VERSION 3.0) project(test-driven-C) enable_testing() set(CMAKE_C_FLAGS "-Wall -Werror -Wpedantic -std=c11") add_subdirectory(src) add_subdirectory(test)[test/CMakeLists.txt]
include_directories ("${PROJECT_SOURCE_DIR}/src") add_executable(test_state test_state.c) add_dependencies(test_state test_state) add_test(test_state test_state) target_link_libraries(test_state state cmocka)
Compile
$ cmake .. && make -- Configuring done -- Generating done -- Build files have been written to: /project/test-driven-c/build [100%] Building C object test/CMakeFiles/test_state.dir/test_state.c.o /project/test-driven-c/test/test_state.c:6:19: fatal error: state.h: No such file or directory #include "state.h" ^ compilation terminated.
So, we probably need to write some actual code…
3. Write some code
src/state.h#ifndef STATE_H_INCLUDED #define STATE_H_INCLUDED typedef struct state state; struct state { int value; }; state *state_new(); int state_set(state *, int); int state_get(state *); void state_free(state *); #endif /* STATE_H_INCLUDED */src/state.c
#include <stdlib.h> #include "state.h" state *state_new() { state *s; s = malloc(sizeof *s); if (!s) return NULL; s->value = 0; return s; } int state_set(state *s, int value) { if (value < 1 || value > 10) return -1; s->value = value; return 0; } int state_get(state *s) { return s->value; } void state_free(state *s) { free(s); }src/CMakeLists.txt
add_library(state state.c)
Compile
$ cmake .. && make -- The C compiler identification is GNU 4.9.1 -- The CXX compiler identification is GNU 4.9.1 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Configuring done -- Generating done -- Build files have been written to: /project/test-driven-c/build Scanning dependencies of target state [ 50%] Building C object src/CMakeFiles/state.dir/state.c.o Linking C static library libstate.a [ 50%] Built target state Scanning dependencies of target test_state [100%] Building C object test/CMakeFiles/test_state.dir/test_state.c.o Linking C executable test_state [100%] Built target test_state
Run tests
$ ctest -V UpdateCTestConfiguration from :/project/test-driven-c/build/DartConfiguration.tcl UpdateCTestConfiguration from :/project/test-driven-c/build/DartConfiguration.tcl Test project /project/test-driven-c/build Constructing a list of tests Done constructing a list of tests Checking test dependency graph... Checking test dependency graph end test 1 Start 1: test_state 1: Test command: /project/test-driven-c/build/test/test_state 1: Test timeout computed to be: 9.99988e+06 1: [==========] Running 2 test(s). 1: [ RUN ] test1 1: [ OK ] test1 1: [ RUN ] test2 1: [ OK ] test2 1: [==========] 2 test(s) run. 1: [ PASSED ] 2 test(s). 1: 1: 0 FAILED TEST(S) 1/1 Test #1: test_state ....................... Passed 0.00 sec 100% tests passed, 0 tests failed out of 1 Total Test time (real) = 0.00 sec
All right, we are on our way…
4. Check for memory leakage
We will use valgrind here so make sure it is installed.
[test/CMakeLists.txt]include_directories ("${PROJECT_SOURCE_DIR}/src") add_executable(test_state test_state.c) add_dependencies(test_state test_state) add_test(test_state test_state) add_test(test_state_valgrind valgrind --error-exitcode=1 --read-var-info=yes --leak-check=full --show-leak-kinds=all ./test_state) target_link_libraries(test_state state cmocka)
Run
$ make && ctest [ 50%] Built target state Scanning dependencies of target test_state [100%] Building C object test/CMakeFiles/test_state.dir/test_state.c.o Linking C executable test_state [100%] Built target test_state Test project /project/test-driven-c/build Start 1: test_state 1/2 Test #1: test_state ....................... Passed 0.00 sec Start 2: test_state_valgrind 2/2 Test #2: test_state_valgrind ..............***Failed 0.26 sec 50% tests passed, 1 tests failed out of 2 Total Test time (real) = 0.26 sec The following tests FAILED: 2 - test_state_valgrind (Failed) Errors while running CTest
Turns out we missed a state_free() in test2. Let’s add it and try again.
[test/state.c][...] assert_int_equal(state_get(s), 5); state_free(s); } [...]
Run
$ make && ctest [ 50%] Built target state Scanning dependencies of target test_state [100%] Building C object test/CMakeFiles/test_state.dir/test_state.c.o Linking C executable test_state [100%] Built target test_state Test project /project/test-driven-c/build Start 1: test_state 1/2 Test #1: test_state ....................... Passed 0.00 sec Start 2: test_state_valgrind 2/2 Test #2: test_state_valgrind .............. Passed 0.26 sec 100% tests passed, 0 tests failed out of 2 Total Test time (real) = 0.26 sec
Advertisements