TL;DR
A project template for testing C-code with system calls see on github. It uses FFF for mocking and Catch2 as a testing framework. The solution is based on linker --wrap
feature.
Intro
If you ever wrote a low-level library that uses system calls you know that it’s not easy to test such code. In this short post I want to share a “turn key” template with FFF and Catch2 testing frameworks that allows easy test such code.
The Problem
Let’s say we have a function that checks if we run on a multi or single-core system.
The problem of unit testing such function that it calls an external function get_nprocs_conf() behavior of which we do not control. Moreover result from this function will change from machine to machine, so unit testing such code is not really easy.
The Solution
We will use a wrap trick provided by a GCC linker (also available in LLVM’s gold linker). By specifying the linker option --wrap=get_nprocs_conf
, we will get a chance to redefine get_nprocs_conf()
as we want.
From ld manual
— wrap=symbol
Use a wrapper function for symbol. Any undefined reference to symbol will be resolved to “__wrap_symbol”. Any undefined reference to “__real_symbol” will be resolved to symbol.
The only thing we need now is to define __wrap_get_nprocs_conf()
.
Now compile and run
gcc main.c && ./a.out
Will return multicore system
. If we let linker to wrap get_nprocs_conf()
result will be different.
gcc -Wl,--wrap=get_nprocs_conf main.c && ./a.out
Will return singlecore system
. Magic!
Mocking And Testing With FFF and Catch2
Now let’s try to do the same, but using proper frameworks.
- FFF is a mocking framework that defines wrapper functions for us (creates mocks).
- Catch2 is a C++ testing framework. Modern replacement for google test. I use it for more than two years and really enjoying writing BDD-style tests (see why you want to write BDD-style tests with Catch2 too).
Template that I provide below is actually the main the goal of this post, i.e. I want to show how a real project can be tested with FFF and Catch2.
The final test_lib.cpp looks like that. Let’s quickly jump thru it.
Include catch2 and fff. DEFINE_FFF_GLOBALS is a mandatory directive from FFF, should only be added once.
Then let FFF define __wrap_get_nprocs_conf()
. FFF creates a special struct to store all mock information. Struct name has a suffix_fake
(i.e. __wrap_get_nprocs_conf_fake
).
If you have a big project you may want to move all includes and wrapper definitions to a separate header and include it in your test files.
Finally, define a test. The most important to mention that we should call a RESET_FAKE
in every test. This is related to the fact that pure-C FFF uses global variables.
In the test itself, we set get_nproc_conf() to return 2 and check that is_multicore_system() will return true. Finally we also check that get_nprocs_conf was called only once.
FFF supports different kinds of functions and allows e.g. to check arguments and many more. Please check the readme.
What’s next?
You can clone and compile my template. It includes both FFF and Catch2 as third_party dependencies (they both header-only).
git clone https://github.com/r7vme/testing-c-code-with-system-calls
./build_and_test.sh
Then you can reuse my template for your project of just copy-paste some “best practices” from it.
Happy Testing!