[38;5;244m┌─[0m [38;5;40mroot@joaocosta:~#[0m [38;5;245mcat posts/12.md[0m [38;5;244m───────────────────────────────────────────[0m

[38;5;40m#[0m [1mShort overview of Unit Testing tools in C[0m
[38;5;245mpublished 2024-10-31 · 5 min read · https://joaocosta.dev/blog/12[0m

Recently I've been tasked with choosing a Unit Testing framework for a new
project at work. Having only used proprietary test harnesses in C before, this
was an opportunity to see what's out there. TL;DR we ended up going with [38;5;40mCMocka[0m
[38;5;244m(https://cmocka.org/)[0m.

The requirements were:

[38;5;40m→[0m Easy to integrate into existing code-bases.
[38;5;40m→[0m Works on bare-metal systems.
[38;5;40m→[0m Can be compiled with standard C compilers.
[38;5;40m→[0m Supports mocking functions, including static functions.

[38;5;38m##[0m [1mUnit Testing frameworks[0m

I ended up investigating the following 5 projects:

[38;5;40m→[0m [38;5;40mUnity[0m [38;5;244m(https://github.com/ThrowTheSwitch/Unity)[0m
[38;5;40m→[0m [38;5;40mCUnit[0m [38;5;244m(https://cunit.sourceforge.net/)[0m
[38;5;40m→[0m [38;5;40mGoogle Test[0m [38;5;244m(https://github.com/google/googletest)[0m
[38;5;40m→[0m [38;5;40mCmockery[0m [38;5;244m(https://github.com/google/cmockery)[0m
[38;5;40m→[0m [38;5;40mCMocka[0m [38;5;244m(https://cmocka.org/)[0m

[38;5;135m###[0m [1mUnity[0m

This a nice a project. It is part of the [38;5;40mCeedling[0m
[38;5;244m(https://www.throwtheswitch.org/ceedling)[0m build system, but we can use it
standalone. The documentation is incredible, especially the information about
[38;5;40musing the framework on embedded systems[0m
[38;5;244m(https://github.com/ThrowTheSwitch/Unity/blob/master/docs/UnityConfigurationGuide.md)[0m.

The only reason we ended up using something else was because some of its
features use Ruby (for example test runner generation), which we couldn't use,
and we find something with more features.

Tests look a bit like this ([38;5;40msource[0m
[38;5;244m(https://github.com/ThrowTheSwitch/Unity/tree/master/examples/example_1)[0m):

[38;5;244m┄─────────────────────────────────────────────────────────────────────────── c [0m
[38;5;245m│[0m void setUp(void)
[38;5;245m│[0m {
[38;5;245m│[0m   /* This is run before EACH TEST */
[38;5;245m│[0m   Counter = 0x5a5a;
[38;5;245m│[0m }
[38;5;245m│[0m 
[38;5;245m│[0m void tearDown(void)
[38;5;245m│[0m {
[38;5;245m│[0m   /* This is run after EACH TEST */
[38;5;245m│[0m }
[38;5;245m│[0m 
[38;5;245m│[0m void test_FindFunction_WhichIsBroken_ShouldReturnZeroIfItemIsNotInList_WhichWorksEvenInOurBrokenCode(void)
[38;5;245m│[0m {
[38;5;245m│[0m   /* All of these should pass */
[38;5;245m│[0m   TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(78));
[38;5;245m│[0m   TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(2));
[38;5;245m│[0m   TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(33));
[38;5;245m│[0m   TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(999));
[38;5;245m│[0m   TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(-1));
[38;5;245m│[0m }
[38;5;244m┄──────────────────────────────────────────────────────────────────────────────[0m

[38;5;135m###[0m [1mCUnit[0m

This project is quite old and abandoned. Considering the other options
available, there isn't much reason to use this. It has an interesting feature
where users can add plugins to the framework, but I haven't explored what
plugins are out there.

Example test code ([source(https://cunit.sourceforge.net/screenshots.html)):

[38;5;244m┄─────────────────────────────────────────────────────────────────────────── c [0m
[38;5;245m│[0m int init_suite_success(void) { return 0; }
[38;5;245m│[0m int init_suite_failure(void) { return -1; }
[38;5;245m│[0m int clean_suite_success(void) { return 0; }
[38;5;245m│[0m int clean_suite_failure(void) { return -1; }
[38;5;245m│[0m 
[38;5;245m│[0m void test_success(void)
[38;5;245m│[0m {
[38;5;245m│[0m    CU_ASSERT(TRUE);
[38;5;245m│[0m }
[38;5;245m│[0m 
[38;5;245m│[0m int main() {
[38;5;245m│[0m    CU_pSuite pSuite = NULL;
[38;5;245m│[0m 
[38;5;245m│[0m    /* initialize the CUnit test registry */
[38;5;245m│[0m    if (CUE_SUCCESS != CU_initialize_registry())
[38;5;245m│[0m       return CU_get_error();
[38;5;245m│[0m 
[38;5;245m│[0m    /* add a suite to the registry */
[38;5;245m│[0m    pSuite = CU_add_suite("Suite_success", init_suite_success, clean_suite_success);
[38;5;245m│[0m    if (NULL == pSuite) {
[38;5;245m│[0m       CU_cleanup_registry();
[38;5;245m│[0m       return CU_get_error();
[38;5;245m│[0m    }
[38;5;245m│[0m 
[38;5;245m│[0m    /* add the tests to the suite */
[38;5;245m│[0m    if (NULL == CU_add_test(pSuite, "successful_test", test_success)) {
[38;5;245m│[0m       CU_cleanup_registry();
[38;5;245m│[0m       return CU_get_error();
[38;5;245m│[0m    }
[38;5;245m│[0m 
[38;5;245m│[0m    /* Run all tests using the basic interface */
[38;5;245m│[0m    CU_basic_set_mode(CU_BRM_VERBOSE);
[38;5;245m│[0m    CU_basic_run_tests();
[38;5;245m│[0m    printf("\n");
[38;5;245m│[0m    CU_basic_show_failures(CU_get_failure_list());
[38;5;245m│[0m    printf("\n\n");
[38;5;245m│[0m 
[38;5;245m│[0m    /* Run all tests using the automated interface */
[38;5;245m│[0m    CU_automated_run_tests();
[38;5;245m│[0m    CU_list_tests_to_file();
[38;5;245m│[0m 
[38;5;245m│[0m    /* Clean up registry and return */
[38;5;245m│[0m    CU_cleanup_registry();
[38;5;245m│[0m    return CU_get_error();
[38;5;245m│[0m }
[38;5;244m┄──────────────────────────────────────────────────────────────────────────────[0m

[38;5;135m###[0m [1mGoogle Test[0m

Although there are people testing C code using this framework, it is officially
a C++ testing framework. With this, we decided to not go with it, because some
of our code is related to the Linux Kernel, which can't be compiled with a C++
compiler.

[38;5;135m###[0m [1mCmockery[0m

Originally developed by Google, this is the equivalent of Google Test for C.
This is superseded by CMocka (see bellow).

[38;5;135m###[0m [1mCMocka[0m

This is an extremely popular and well tested project. Reportedly used by samba,
libssh, coreboot, OpenVPN, and libomemo (so both old and new projects).

In my opinion, the best part of this framework is the integrated support for
mocking functions. There's also some memory leak detection capabilities, but
nothing like [38;5;40mValgrind[0m [38;5;244m(https://valgrind.org/)[0m. Unfortunately, this isn't a
header-only lib, but that's not too hard to deal with.

This is an example of a test using mocks:

[38;5;244m┄─────────────────────────────────────────────────────────────────────────── c [0m
[38;5;245m│[0m #include <limits.h>
[38;5;245m│[0m #include <setjmp.h>
[38;5;245m│[0m #include <stdarg.h>
[38;5;245m│[0m #include <stddef.h>
[38;5;245m│[0m // IMPORTANT: cmocka needs the imports above
[38;5;245m│[0m #include <cmocka.h>
[38;5;245m│[0m 
[38;5;245m│[0m #include <stdio.h>
[38;5;245m│[0m 
[38;5;245m│[0m #include "add3.h"
[38;5;245m│[0m 
[38;5;245m│[0m int __wrap_add(int a, int b);
[38;5;245m│[0m 
[38;5;245m│[0m int __wrap_add(int a, int b) {
[38;5;245m│[0m   printf("------\nHELLO FROM __wrap_add\n------\n");
[38;5;245m│[0m   check_expected(a);
[38;5;245m│[0m   check_expected(b);
[38;5;245m│[0m   return (int)mock();
[38;5;245m│[0m }
[38;5;245m│[0m 
[38;5;245m│[0m static void test_add3_mock(void **state) {
[38;5;245m│[0m   // given
[38;5;245m│[0m   int a = 1, b = 2, c = 4;
[38;5;245m│[0m   int expected_result = 7;
[38;5;245m│[0m   int result;
[38;5;245m│[0m 
[38;5;245m│[0m   expect_value(__wrap_add, a, 1);
[38;5;245m│[0m   expect_value(__wrap_add, b, 2);
[38;5;245m│[0m   will_return(__wrap_add, 3);
[38;5;245m│[0m   expect_value(__wrap_add, a, 3);
[38;5;245m│[0m   expect_value(__wrap_add, b, 4);
[38;5;245m│[0m   will_return(__wrap_add, 7);
[38;5;245m│[0m 
[38;5;245m│[0m   // when
[38;5;245m│[0m   result = add3(a, b, c);
[38;5;245m│[0m 
[38;5;245m│[0m   // then
[38;5;245m│[0m   assert_int_equal(expected_result, result);
[38;5;245m│[0m }
[38;5;245m│[0m 
[38;5;245m│[0m int main(void) {
[38;5;245m│[0m   const struct CMUnitTest tests[] = {
[38;5;245m│[0m       cmocka_unit_test(test_add3_mock),
[38;5;245m│[0m   };
[38;5;245m│[0m 
[38;5;245m│[0m   return cmocka_run_group_tests(tests, NULL, NULL);
[38;5;245m│[0m }
[38;5;244m┄──────────────────────────────────────────────────────────────────────────────[0m

[38;5;38m##[0m [1mMocks/Stubs[0m

There are also lots of tools for mocking functions in C

I've looked at the following:

[38;5;40m→[0m [38;5;40mFake Function Framework (fff)[0m [38;5;244m(https://github.com/meekrosoft/fff)[0m
- This one is nice and is header-only, so we can add it to our projects really
easily. I just found it a bit too verbose, but the documentation is great and
full of usage examples.

[38;5;40m→[0m [38;5;40mCMock[0m [38;5;244m(https://github.com/ThrowTheSwitch/CMock)[0m
- Also part of the [38;5;40mCeedling[0m [38;5;244m(https://www.throwtheswitch.org/ceedling)[0m project.
Depends on Ruby.

[38;5;40m→[0m [38;5;40mCMocka[0m [38;5;244m(https://cmocka.org/)[0m
- Mentioned previously.

[38;5;40m→[0m [38;5;40mGMock[0m [38;5;244m(https://google.github.io/googletest/gmock_for_dummies.html)[0m
- Part of the Google Test suite. C++ only, so we'll ignore it.

None of these libs would report support to magically mock static functions.
There are several challenges in doing this, for example, static functions can
disappear (be in-lined) during compilation, so there's no symbol to replace.
We'll look at this later.

[38;5;135m###[0m [1mMocking in CMocka[0m

CMocka's function mocking makes use of the [38;5;38m--wrap[0m linker flag. This allows you
to replace a function at link time. From [38;5;38mld[0m's manual page:

[38;5;135m┃[0m 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. This can be used to provide a wrapper for a system
  function. The wrapper function should be called "__wrap_symbol". If it wishes
  to call the system function, it should call "__real_symbol".

Note that this might not be available on all linkers.

[38;5;135m###[0m [1mMocking static functions[0m

When testing legacy code-bases, it is frequent to find that the code wasn't
written in a way favorable for testing. For this reason, we used a fun way to
mock static functions:

[38;5;40m→[0m Build a mock function (replacement function).
[38;5;40m→[0m During testing, re-write the first instruction of the target function to a
  jump/branch to the replacement function.
[38;5;40m→[0m Now everything that would call the original function, would call your
  replacement function instead.

This process is easier than it seems. Bellow you have an example to do this in
ARM ([38;5;40msource[0m
[38;5;244m(https://github.com/JoaoCostaIFG/cmake-cmocka-template/blob/main/test/utils.c)[0m):

[38;5;244m┄─────────────────────────────────────────────────────────────────────────── c [0m
[38;5;245m│[0m #include <stdint.h>
[38;5;245m│[0m #include <sys/mman.h>
[38;5;245m│[0m 
[38;5;245m│[0m int hotpatch(void *target, void *replacement)
[38;5;245m│[0m {
[38;5;245m│[0m 	void *page = (void *)((uintptr_t)target & ~0xfff);
[38;5;245m│[0m 	uint32_t offset = (char *)replacement - (char *)target;
[38;5;245m│[0m 	uint32_t instruction = (0x14 << 24) | ((offset >> 2) & 0x3ffffff);
[38;5;245m│[0m 
[38;5;245m│[0m 	if (mprotect(page, 4096, PROT_WRITE | PROT_EXEC)) {
[38;5;245m│[0m 		return -2;
[38;5;245m│[0m 	}
[38;5;245m│[0m 
[38;5;245m│[0m 	*(uint64_t *)target = instruction;
[38;5;245m│[0m 
[38;5;245m│[0m 	return 0;
[38;5;245m│[0m }
[38;5;244m┄──────────────────────────────────────────────────────────────────────────────[0m

This code is quite simple. We're assuming that the system's page size is 4096
bytes and that the branching offset is within 32 MB. Still, we're just
calculating the offset from the first instruction of the target function to the
replacement function and writing an [38;5;40munconditional branch instruction[0m
[38;5;244m(https://developer.arm.com/documentation/dui0489/i/arm-and-thumb-instructions/b?lang=en)[0m
(0x14) with the given offset.

[38;5;38m##[0m [1mCoverage[0m

Generating coverage reports is easily done with [38;5;40mGCovr[0m
[38;5;244m(https://gcovr.com/en/stable/)[0m independently of the tool used. Just compile your
code with the following flags with you want to generate test reports: [38;5;38m-g -O0
--coverage[0m.

You can get reports on your cli or as HTML reports:

[38;5;244m┄───────────────────────────────────────────────────────────────────────── txt [0m
[38;5;245m│[0m ------------------------------------------------------------------------------
[38;5;245m│[0m                            GCC Code Coverage Report
[38;5;245m│[0m Directory: ..
[38;5;245m│[0m ------------------------------------------------------------------------------
[38;5;245m│[0m File                                       Lines    Exec  Cover   Missing
[38;5;245m│[0m ------------------------------------------------------------------------------
[38;5;245m│[0m src/add3.c                                     3       3   100%
[38;5;245m│[0m src/add.c                                      2       2   100%
[38;5;245m│[0m src/program.c                                  4       0     0%   5-7,9
[38;5;245m│[0m ------------------------------------------------------------------------------
[38;5;245m│[0m TOTAL                                          9       5    55%
[38;5;245m│[0m ------------------------------------------------------------------------------
[38;5;245m│[0m lines: 55.6% (5 out of 9)
[38;5;245m│[0m functions: 75.0% (3 out of 4)
[38;5;245m│[0m branches: 0.0% (0 out of 0)
[38;5;244m┄──────────────────────────────────────────────────────────────────────────────[0m

[38;5;38m##[0m [1mConclusion[0m

We've ended up going with CMocka. It has a decent enough documentation, all the
features we need, and is popular. I've published a [38;5;40mtemplate project on GitHub[0m
[38;5;244m(https://github.com/JoaoCostaIFG/cmake-cmocka-template/tree/main)[0m to quick start
a C project with CMake:

[38;5;40m→[0m Builds with [38;5;40mCMake[0m [38;5;244m(https://cmake.org/)[0m.
[38;5;40m→[0m Auto downloads and builds CMocka.
[38;5;40m→[0m Includes example code and tests.
[38;5;40m→[0m Has the funny function to mock static functions for ARM and x86 (including
  examples).
[38;5;40m→[0m Downloads and runs [38;5;40mCppCheck[0m [38;5;244m(https://cppcheck.sourceforge.io/)[0m and [38;5;40mClang-Tidy[0m
  [38;5;244m(https://clang.llvm.org/extra/clang-tidy/)[0m on your project.

[38;5;244m└─[0m [38;5;40m→[0m view online: [38;5;40mhttps://joaocosta.dev/blog/12[0m
