Setting up CMocka with CMake for Linux and Windows
All notes in this series:
- Setting up Emscripten with CMake in Git Bash on Windows 10
- Setting up Emscripten with CMake on Linux
- Porting a simple SDL2 game to Emscripten
- Setting up CMocka with CMake for Linux and Windows
CMocka is a unit testing framework for C. These notes cover how to configure CMake for an existing C project to use CMocka, for both Linux and Windows, in the following steps:
- Install CMocka.
- Update your CMake modules to find CMocka.
- Update your
CMakeLists.txt
to link against CMocka. - Update your project structure to add a
tests
directory. - Add a basic CMocka test to your project.
- Run the test.
- Add a more advanced test which hooks into your project.
In these notes I’m using Windows 10 and Debian 9.3 “stretch”. For a terminal, on Windows I’m using Git Bash.
Install CMocka §
Clone CMocka §
First things first: CMocka must be built and installed on your system. Your C program will then link against it. You do not need to copy the CMocka source into your program (except for the CMake modules, which is covered later).
Start by cloning cmocka from source and change directory into the clone:
Generate makefiles §
Before running the next step of generating the makefiles, note that the default CMocka install locations are:
C:/Program Files (x86)
on Windows./usr/local
on Linux.
If desired, change the install location with -DCMAKE_INSTALL_PREFIX
. On Windows, I installed to C:/c_libs/cmocka
(partly because I don’t want to clutter Program Files, partly because it avoids needing administrator privileges in the install step later, and partly because I still distrust spaces in path names):
On Linux I used /usr
as the install location. I omit the -G "MinGW Makefiles"
since that’s only needed for Windows:
Running this command generates the makefiles (but does not yet actually build or install CMocka).
Following is example output from generating the makefiles on Windows:
Build CMocka §
Next, perform the actual build:
Install CMocka §
Then install cmocka. It will install to the location you specified earlier (or the default if unspecified):
If it fails with Maybe need administrative privileges.
(likely on Windows if you’re using the default installation location of C:/Program Files (x86)
) then run again as administrator.
Configure PATH
§
On Windows, update your system PATH
to add the CMocka bin
directory. This is required so that at runtime Windows will be able to find the cmocka.dll
:
On Linux, if you used /usr
as your install location, then you shouldn’t need to make any PATH
changes.
Configuring your C application to use CMocka §
Copy CMake files §
Copy the CMocka provided *.cmake
files into your project. These are needed to enable CMocka commands such as add_cmocka_test
. In my project, I store CMake modules here:
To do the same, go to the CMocka git clone and copy everything from cmocka/cmake/Modules/*
into <project root>/cmake/cmocka/
. This includes:
Add FindCMocka.cmake
§
To help support Windows builds, we want to add a CMOCKA_PATH
option which allows passing in the path to where CMocka is installed when generating the makefiles. While there are other approaches here (such as ensuring the CMocka libraries and include dirs are on your PATH
) I prefer to explicitly pass in -DCMOCKA_PATH="C:/c_libs/cmocka"
on Windows.
Create a new file <project root>/cmake/cmocka/FindCMocka.cmake
and paste in the following:
Update top-level CMakeLists.txt
§
Now update your project’s top-level CMakeLists.txt
.
First, ensure it includes all the CMake modules we just added:
Then, below that add a UNIT_TESTING
option, which includes a tests/
directory in the build (we’ll create that directory in a moment). This conditional means that unit tests will only be built if you include the option -DUNIT_TESTING=ON
:
Create a simple test §
Create a tests/
directory, then create a simple_test.c
inside that directory:
Note that this test will not be picked up by CMocka until we do the next step of hooking it up in CMake.
(For more example tests, see cmocka/example
in the CMocka code base).
Hook up test to CMake §
Inside the tests/
directory create a CMakeLists.txt
. Within this file, put:
This registers the CMocka test with CMake, links it to the CMocka library (specified by CMOCKA_LIBRARIES
) and ensures that cmocka.h
is included (specified by CMOCKA_INCLUDE_DIRS
).
Building and running the unit tests §
Building the unit tests §
As mentioned earlier, to compile tests, first use the option -DUNIT_TESTING=ON
when generating the makefiles. For example, on Windows:
On Linux the -G "MinGW Makefiles"
can be omitted:
Then build as normal:
This should have created a tests/simple_test.exe
file. If you’re curious, you can just run that file manually to run that individual test, however there’s better ways to run the tests.
Running the unit tests §
To run all your built tests:
Use -V
to see more details.
A test which links to your code §
You may have noticed that our simple_test.c
does not actually test any of our project’s code. Before we continue, let’s take a look at our project structure:
As an overview:
- The top-level
CMakeLists.txt
callsproject(MyProject VERSION 0.1.0)
to define the project.- It calls
add_subdirectory(src)
to add thesrc
directory. - It also calls
add_subdirectory(app)
to add theapp
directory.
- It calls
- The
src/CMakeLists.txt
:- Calls
file(GLOB HEADER_LIST CONFIGURE_DEPENDS "${MyProject_SOURCE_DIR}/include/*.h")
to populateHEADER_LIST
with all the header files (level.h
,objects.h
,sprites.h
) - Calls
add_library(my_lib STATIC level.c objects.c sprites.c ${HEADER_LIST})
to create a library calledmy_lib
which includes all the source files insrc/
with the matching header files. - Calls
target_include_directories(my_lib PUBLIC "${MyProject_SOURCE_DIR}/include")
to include the directories with the header files.
- Calls
- The
app/CMakeLists.txt
:- Calls
add_executable(my_game main.c)
to define the executable. Themain()
method is inmain.c
. - Calls
target_link_libraries(my_game PUBLIC my_lib)
to linkmy_game
to our librarymy_lib
.
- Calls
So, to test my_lib
we must make the following changes to tests/CMakeLists.txt
.
- Link to the library we want to test (i.e.
my_lib
). - Also include the
my_lib
source code so that we can test thestatic
(private) functions, by doing something like#include "objects.c"
.- Typically you would only ever include a header (
*.h
) file, but including the source file (*.c
) is a useful trick for unit testing private functions.
- Typically you would only ever include a header (
In this example, the code being tested is the target named my_lib
. We modify our tests/CMakeLists.txt
from earlier to support the above two points:
- Create a new list
TEST_LIBS
, which containsCMOCKA_LIBRARIES
and now alsomy_lib
. - Create a new list
TEST_DIRS
, which containsCMOCKA_INCLUDE_DIRS
and now also thesrc/
directory.
It is now possible to do the following from within simple_test.c
:
- Use
#include "objects.h"
, and write tests against the public functions available in that header file. - Use
#include "objects.c"
, and write tests against the public and private functions available in that source file.
Multiple unit tests §
So far we have just one unit test, simple_test.c
. Adding new unit tests by copying and pasting the calls to add_cmocka_test
, add_cmocka_test_environment
and target_include_directories
would add a lot of boilerplate code.
Instead, we can use the list
approach with the unit tests. By defining a list named TEST_TARGETS
which contains the filename of all the tests (omitting the .c
extension), we can then use this for the CMake target name, and stick the extension back on for the source:
The above example will test: test_simple.c
, test_level.c
, test_objects.c
and test_sprites.c
in order.
Troubleshooting §
Cannot find CMocka §
If you get this error when running cmake -S . -B build ...
:
It means the CMocka include
and lib
directories could not be found. The parent folder of these directories should be on your PATH
, or you can explicitly specify the parent folder using -DCMOCKA_PATH="C:/c_libs/cmocka"
(assuming you have your CMocka include
and lib
directories at C:/c_libs/cmocka/include
and C:/c_libs/cmocka/lib
respectively).
Unit tests build but fails at runtime §
If everything works until you run the unit tests, and then all the tests fail with exceptions, e.g.:
Or if you get an error such as the following when manually running an individual test on Windows:
Make sure that you have correctly got cmocka.dll
available on your PATH
. For example, if you installed CMocka to C:\c_libs\cmocka
then your path should have C:\c_libs\cmocka\bin
. Alternatively, as a quick workaround, copy the cmocka.dll
from C:\c_libs\cmocka\bin\cmocka.dll
into your tests/
directory.
Links §
Some useful CMake information:
All notes in this series:
- Setting up Emscripten with CMake in Git Bash on Windows 10
- Setting up Emscripten with CMake on Linux
- Porting a simple SDL2 game to Emscripten
- Setting up CMocka with CMake for Linux and Windows