Test-driven C – CMake, cmocka

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s