Unit Testing with the C Preprocessor
Jul 2, 2021[ I wrote this article in mid 2019 to persuade my collaborators that we need not labor under the burdens of an overwrought mocking framework. I hope others find encouragement here to seek simple solutions. ]
Motivation
I want an approach to unit testing with a near-zero learning curve that works practically everywhere, with no exotic dependencies and no performance penalties.
Simplicity is the height of sophistication. — Clare Boothe Luce
What is Unit Testing?
Unit testing is isolating code until it is observably deterministic and then arranging initial conditions to verify expected outcomes. This is my definition.
More concretely, a function to be tested is isolated by providing mock versions of any extraneous code it calls. Each test then initializes global variables as needed, runs the function with a given set of arguments, and checks the return value, output parameters, and side effects.
Ideally, I want 100% branch coverage of any non-trivial function. This requires every possible path through the code to be traversed; all error conditions are tested, and also the “happy path”. Traversing these error paths is possible by controlling the return values and side effects of the mocked functions.
How I make these mock functions is the primary focus of this document.
Methodology
The only essential tool used here is the C preprocessor; everything else
is window dressing. In a nutshell, in a C file write macros to replace
certain function calls, then #include
the code to test. Provide a
main function that calls the code under test and checks assertions.
Rinse and repeat. Use gcov to check test coverage of the code under test.
Below are examples and explanations taken from tests of the mbox functions in the lwIP port for FreeRTOS. More examples can be seen in CCAN, the C code archive network, where I first encountered the approach.
Here’s a short function, sys_mbox_new()
.
err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize )
{
err_t xReturn = ERR_MEM;
sys_mbox_t pxTempMbox;
pxTempMbox.xMbox = xQueueCreate( iSize, sizeof( void * ) );
if( pxTempMbox.xMbox != NULL )
{
pxTempMbox.xTask = NULL;
*pxMailBox = pxTempMbox;
xReturn = ERR_OK;
}
return xReturn;
}
It wraps a call to xQueueCreate()
, populates a structure, and returns
an error code.
I’ll start by testing the happy path and assume the function is alone
in a file, save for an initial #include "sys_arch.h"
. After running
the function, the following should be true:
- the return value is ERR_OK
- the xTask element of the passed structure is NULL
- the xMbox element of the passed structure is the result of calling xQueueCreate()
Here is a minimal unit test that I’ll refine in steps.
#include <stddef.h>
#include <assert.h>
/* prevent the inclusion of sys_arch.h */
#define __SYS_ARCH_H__
/* provide dummy versions of the types and macros */
typedef struct sys_mbox {
void *xMbox;
void *xTask;
} sys_mbox_t;
typedef int err_t;
#define ERR_OK 0
#define ERR_MEM 1
/* mock xQueueCreate */
void *mbox;
#define xQueueCreate(x, y) ( &mbox )
#include "sys_mbox_new.c"
int main(void){
int err;
sys_mbox_t foo = { &foo, &foo };
/* happy path */
err = sys_mbox_new(&foo, 123);
assert(err == ERR_OK);
assert(foo.xTask == NULL);
assert(foo.xMbox == &mbox);
return 0;
}
The macro definition of xQueueCreate()
causes the call in
sys_mbox_new()
to be rewritten as pxTempMbox.xMbox = (&mbox);
Should
any of the assertions prove false, the programs aborts immediately with
an error message.
Statement Expressions
I’ll test that the second argument to sys_mbox_new()
is passed as
the first argument to xQueueCreate()
.
int keep;
void *mbox;
#define xQueueCreate(x, y) ({ keep = (x); &mbox; })
To main, I add assert(keep == 123);
. The new macro definition uses a C
language extension called a statement expression which permits a block
to take the place of an expression. The value of the last statement
becomes the value of the expression as a whole.
For toolchains that don’t support statement expressions, the same result can be had with the comma operator, or by writing a small function with the macro calling that function.
Reset
To write a thorough unit test, more than the happy path needs to
be traversed. Between calls to the function under test, variables
like keep
need to be set to known values, and the return value of
xQueueCreate()
should vary at need.
#define UNDEFINED 0xcafecafe
int keep;
void *mbox;
#define xQueueCreate(x, y) ({ keep = (x); mbox; })
#define xqcReset() do { \
keep = -UNDEFINED; \
mbox = &mbox; \
} while (0)
A simple convention is to set pointers to their own addresses. The macro
now returns the value of mbox
, which defaults to the address of mbox
.
The value of mbox
can be set to NULL to test failure cases.
int main(void) {
int err;
sys_mbox_t foo, fooDflt = { &foo, &foo };
#define reset() do { \
xqcReset(); \
foo = fooDflt; \
} while (0)
/* ... */
/* xQueueCreate returns NULL */
reset();
mbox = NULL;
err = sys_mbox_new(&foo, 234);
assert(err == ERR_MEM);
assert(keep == 234);
assert(foo.xTask == &foo); /* unchanged */
assert(foo.xMbox == &foo); /* unchanged */
/* ... */
}
Test Anything Protocol
There are two drawback to using assert: there’s no indication that the tests are actually running, and testing stops with the first failure. I’ll provide an assert-like macro that counts successes but does not abort failures, and prints each result. A simple convention is to print “ok” or “not ok” followed by a test number. This convention is embodied as the Test Anything Protocol.
#include <stdio.h>
int ok_cnt_; /* number of passing tests */
int index_; /* ordinal of current test */
#define ok(x) do { \
char *msg = "not ok"; \
index_++; \
if (x) { \
ok_cnt_++; \
msg += 4; \
} \
printf("%s %d\n", msg, index_); \
} while (0)
This produces output like:
$ ./test-sys_mbox_new
ok 1
ok 2
ok 3
ok 4
TAP has some other useful ideas. The unit test should indicate how
many tests are expected to run by printing 1..N
where N is the number
of tests. This is called the plan. If a test fails, some diagnostic
output can be appended to the line. A summary line can indicate if the
plan matches the success count.
$ ./test-sys_mbox_new
1..4
ok 1
ok 2
not ok 3 - test-sys_mbox_new.c:26 foo.xTask == NULL
ok 4
FAILED
A minimally sufficient TAP implementation in a few dozen lines is shown as
an appendix. It provides three macros: plan()
, ok()
, and tally()
.
The Complete Unit Test
The typedefs and constants are useful for testing other mbox functions, so they are moved to a common header file.
#include "common.h"
int keep;
void *mbox;
#define xQueueCreate(x, y) ({ keep = (x); mbox; })
#define xqcReset() do { \
keep = -UNDEFINED; \
mbox = &mbox; \
} while (0)
#include "sys_mbox_new.c"
int main(void) {
int err;
sys_mbox_t foo, fooDflt = { &foo, &foo };
#define reset() do { \
xqcReset(); \
foo = fooDflt; \
} while (0)
plan(8);
/* happy path */
reset();
err = sys_mbox_new(&foo, 123);
ok(err == ERR_OK);
ok(keep == 123);
ok(foo.xTask == NULL);
ok(foo.xMbox == &mbox);
/* xQueueCreate returns NULL */
reset();
mbox = NULL;
err = sys_mbox_new(&foo, 234);
ok(err == ERR_MEM);
ok(keep == 234);
ok(foo.xTask == &foo); /* unchanged */
ok(foo.xMbox == &foo); /* unchanged */
return tally();
}
Scripting to Save Effort
The initial assumption of one function per file is both unlikely and
impractical. It’s certainly possible to write unit tests for multiple
functions in a single main()
, but it’s also reasonable to use a tool
to extract a single function into a temporary file. Combine this with
a few more tools, and the effort can be automated. I’ll use make
and
a patched ctags
to do the heavy lifting.
Nota bene, the makefile, report script, and tap.h
are available in the
mock4thewin repo.
make
make
is the traditional dependency-based build automation tool.
A Makefile contains rules that make
uses to generate an output file
from one or more input files. My Makefile starts by reading the list
of .c
files starting with test-
. From this list, it derives the
list of functions to extract, and the list of executables to build.
In $(pathsubst ...)
and later in rules, %
is a wildcard character.
TEST_SRC:=$(wildcard test-*.c)
TEMP_SRC:=$(patsubst test-%,%,$(TEST_SRC))
TESTS=$(patsubst %.c,%,$(TEST_SRC))
Given these directory contents:
Makefile README common.h report tap.h test-sys_arch_mbox_fetch.c test-sys_mbox_free.c test-sys_mbox_new.c
the variables have these values.
TEST_SRC := test-sys_arch_mbox_fetch.c test-sys_mbox_new.c test-sys_mbox_free.c
TEMP_SRC := sys_arch_mbox_fetch.c sys_mbox_new.c sys_mbox_free.c
TESTS := test-sys_arch_mbox_fetch test-sys_mbox_new test-sys_mbox_free
Here’s a rule describing how to compile each test.
$(TESTS): test-%: test-%.c %.c common.h tap.h
$(CC) $(CFLAGS) $< -o $@
The first line captures dependencies. For example, test-sys_mbox_new
depends on test-sys_mbox_new.c
, sys_mbox_new.c
, common.h
, and
tap.h
The second line, which must begin with a real tab character,
is the command to run. The $<
and $@
are replaced by make
with
the input and output files respectively.
Exuberant Ctags
While I used to use sed
to dump functions from source files, I wanted
a more robust approach. As I could find no satisfactory ready-made
solution, I adapted the faithful ctags
tool. To quote the man page:
The ctags program generates an index file for a variety of language objects found in source files. This tag file allows these items to be quickly and easily located by a text editor or other utility. A “tag” signifies a language object for which an index entry is available.
ctags
can record the starting line numbers of functions in the index
(the xref
format). My patch causes it to also record the ending line
numbers.
The code is available in the ctags-xref repo.
It includes a bash script, show-source
, that uses the line numbers from
the index to dump a given function.
Here are the calls to ctags-xref
and show-source
used in my Makefile.
make
replaces the $(pathsubst ...)
portion with the output filename
minus the .c
.
index: ../sys_arch.c
ctags-xref $< > index
$(TEMP_SRC): index
show-source $(patsubst %.c,%,$@) > $@
(The earlier section on using sed
in now in the appendix.)
Makefile
Here is the complete Makefile. A more general and reusable version may be found in Mock4thewin.mk.
TEST_SRC:=$(wildcard test-*.c)
TEMP_SRC:=$(patsubst test-%,%,$(TEST_SRC))
TESTS=$(patsubst %.c,%,$(TEST_SRC))
CFLAGS+=-std=c99 -Wall -fprofile-arcs -ftest-coverage -O0 -ggdb
build: $(TESTS)
test: build
@rm -f *.gcov *.gcda
@unit-test-report $(TESTS)
clean:
rm -f $(TESTS) $(TEMP_SRC) index *.gc??
.PHONY: build test clean
index: ../sys_arch.c
ctags-xref $< > index
$(TEMP_SRC): index
show-source $(patsubst %.c,%,$@) > $@
$(TESTS): test-%: test-%.c %.c common.h tap.h
$(CC) $(CFLAGS) $< -o $@
unit-test-report
above will be described after gcov
.
gcov
gcov
is a tool to determine how many times each line of source code is
executed for a given program run. It combines metadata from the program’s
compilation with trace data from the program’s execution. Two args
are given to the compiler, -fprofile-arcs
and -ftest-coverage
.
The metadata file has the name of the source file, and ends with .gcno
.
When the program is run, it outputs a file ending with .gcda
. To get
the final counts, gcov
is run with the program file as argument.
It writes the counts to a file that ends with .gcov
.
$ gcov -f test-sys_mbox_new
Function 'main'
Lines executed:100.00% of 17
Function 'sys_mbox_new'
Lines executed:100.00% of 8
File 'test-sys_mbox_new.c'
Lines executed:100.00% of 17
Creating 'test-sys_mbox_new.c.gcov'
File 'sys_mbox_new.c'
Lines executed:100.00% of 8
Creating 'sys_mbox_new.c.gcov'
Here are the contents of sys_mbox_new.c.gcov
.
-: 0:Source:sys_mbox_new.c
-: 0:Graph:test-sys_mbox_new.gcno
-: 0:Data:test-sys_mbox_new.gcda
-: 0:Runs:1
-: 0:Programs:1
2: 1:err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize )
-: 2:{
2: 3: err_t xReturn = ERR_MEM;
-: 4: sys_mbox_t pxTempMbox;
-: 5:
2: 6: pxTempMbox.xMbox = xQueueCreate( iSize, sizeof( void * ) );
-: 7:
2: 8: if( pxTempMbox.xMbox != NULL )
-: 9: {
1: 10: pxTempMbox.xTask = NULL;
1: 11: *pxMailBox = pxTempMbox;
1: 12: xReturn = ERR_OK;
-: 13: }
-: 14:
2: 15: return xReturn;
-: 16:}
Lines beginning with a dash have no executable code. Any line that was
not traversed will have five #
characters instead of a count. If I
comment out the happy path case from the sys_mbox_new()
unit test,
the contents of sys_mbox_new.c.gcov
are this:
-: 0:Source:sys_mbox_new.c
-: 0:Graph:test-sys_mbox_new.gcno
-: 0:Data:test-sys_mbox_new.gcda
-: 0:Runs:1
-: 0:Programs:1
1: 1:err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize )
-: 2:{
1: 3: err_t xReturn = ERR_MEM;
-: 4: sys_mbox_t pxTempMbox;
-: 5:
1: 6: pxTempMbox.xMbox = xQueueCreate( iSize, sizeof( void * ) );
-: 7:
1: 8: if( pxTempMbox.xMbox != NULL )
-: 9: {
#####: 10: pxTempMbox.xTask = NULL;
#####: 11: *pxMailBox = pxTempMbox;
#####: 12: xReturn = ERR_OK;
-: 13: }
-: 14:
1: 15: return xReturn;
-: 16:}
gcov
will also show branch coverage with the -b
switch.
$ gcov -b -f ./test-sys_mbox_new
Function 'main'
Lines executed:100.00% of 17
Branches executed:93.33% of 60
Taken at least once:46.67% of 60
Calls executed:56.52% of 23
Function 'sys_mbox_new'
Lines executed:100.00% of 8
Branches executed:100.00% of 2
Taken at least once:100.00% of 2
No calls
File 'test-sys_mbox_new.c'
Lines executed:100.00% of 17
Branches executed:93.33% of 60
Taken at least once:46.67% of 60
Calls executed:56.52% of 23
Creating 'test-sys_mbox_new.c.gcov'
File 'sys_mbox_new.c'
Lines executed:100.00% of 8
Branches executed:100.00% of 2
Taken at least once:100.00% of 2
No calls
Creating 'sys_mbox_new.c.gcov'
Here are the contents of sys_mbox_new.c.gcov
with branch coverage.
-: 0:Source:sys_mbox_new.c
-: 0:Graph:./test-sys_mbox_new.gcno
-: 0:Data:./test-sys_mbox_new.gcda
-: 0:Runs:1
-: 0:Programs:1
function sys_mbox_new called 2 returned 100% blocks executed 100%
2: 1:err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize )
-: 2:{
2: 3: err_t xReturn = ERR_MEM;
-: 4: sys_mbox_t pxTempMbox;
-: 5:
2: 6: pxTempMbox.xMbox = xQueueCreate( iSize, sizeof( void * ) );
-: 7:
2: 8: if( pxTempMbox.xMbox != NULL )
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
-: 9: {
1: 10: pxTempMbox.xTask = NULL;
1: 11: *pxMailBox = pxTempMbox;
1: 12: xReturn = ERR_OK;
-: 13: #if SYS_STATS
-: 14: {
-: 15: SYS_STATS_INC_USED( mbox );
-: 16: }
-: 17: #endif /* SYS_STATS */
-: 18: }
-: 19:
2: 20: return xReturn;
-: 21:}
Summary Report
A small bash
script runs a test and prints a summary.
$ unit-test-report test-sys_mbox_new
FUNCTION UNIT TESTS LINES EXECUTED BRANCHES TAKEN
sys_mbox_new PASSED 100.00% of 8 100.00% of 2
With the happy path case commented out, this is the report.
$ unit-test-report test-sys_mbox_new
FUNCTION UNIT TESTS LINES EXECUTED BRANCHES TAKEN
sys_mbox_new FAILED 62.50% of 8 50.00% of 2
branch 1 taken 100%
-: 9: {
#####: 10: pxTempMbox.xTask = NULL;
#####: 11: *pxMailBox = pxTempMbox;
#####: 12: xReturn = ERR_OK;
-: 13: #if SYS_STATS
-: 14: {
Running make test
builds all the tests and prints all the reports.
$ make test
FUNCTION UNIT TESTS LINES EXECUTED BRANCHES TAKEN
sys_arch_mbox_fetch PASSED 100.00% of 26 100.00% of 40
sys_mbox_new PASSED 100.00% of 8 100.00% of 2
sys_mbox_free PASSED 100.00% of 13 100.00% of 8
The complete script may be seen in unit-test-report.
A More Involved Example
The sys_mbox_free()
function releases the resources acquired by
sys_mbox_new()
. It has one critical section.
void sys_mbox_free( volatile sys_mbox_t *pxMailBox )
{
unsigned long ulMessagesWaiting;
QueueHandle_t xMbox;
TaskHandle_t xTask;
if( pxMailBox != NULL )
{
ulMessagesWaiting = uxQueueMessagesWaiting( pxMailBox->xMbox );
configASSERT( ( ulMessagesWaiting == 0 ) );
taskENTER_CRITICAL();
xMbox = pxMailBox->xMbox;
xTask = pxMailBox->xTask;
pxMailBox->xMbox = NULL;
taskEXIT_CRITICAL();
if( xTask != NULL )
{
xTaskAbortDelay( xTask );
}
if( xMbox != NULL )
{
vQueueDelete( xMbox );
}
}
}
This unit test requires six functions to be mocked. I’ll use counts to ensure
each is called the appropriate number of times. sys_mbox_free()
has several
invariants. Whenever the critical section is reached, the xMbox
element is
set to NULL. Every entry to a critical section has a matching exit.
#include "common.h"
int critical;
#define taskENTER_CRITICAL() do { critical++; } while (0)
#define taskEXIT_CRITICAL() do { critical--; } while (0)
#define critReset() do { critical = 0; } while (0)
int uqmwRet;
void *uqmwHandle;
#define uxQueueMessagesWaiting(x) ({ uqmwHandle = (x); uqmwRet; })
#define uqmwReset() do { uqmwRet = 0; uqmwHandle = &uqmwHandle; } while (0)
void *xtadHandle;
int xtadCallCount;
#define xTaskAbortDelay(x) do { xtadCallCount++; xtadHandle = (x); } while (0)
#define xtadReset() do { xtadCallCount = 0; xtadHandle = &xtadHandle; } while (0)
void *vqdHandle;
int vqdCallCount;
#define vQueueDelete(x) do { vqdCallCount++; vqdHandle = (x); } while (0)
#define vqdReset() do { vqdCallCount = 0; vqdHandle = &vqdHandle; } while (0)
int assertCount;
#define configASSERT(x) do { if (!(x)) { assertCount++; return; } } while (0)
#define assertReset() do { assertCount = 0; } while (0)
#include "sys_mbox_free.c"
int main(void) {
void *mbox, *task;
sys_mbox_t foo, fooDflt = { &mbox, &task };
#define reset() do { \
critReset(); \
uqmwReset(); \
xtadReset(); \
vqdReset(); \
assertReset(); \
foo = fooDflt; \
} while (0)
reset();
/* invariant sets are cumulative */
#define invariants(x) do { \
switch (x) { \
/* crit section reached */ \
case 2: \
ok(foo.xMbox == NULL); \
ok(assertCount == 0); \
/* always */ \
case 1: \
ok(critical == 0); \
} \
reset(); \
} while (0)
plan(38);
/* NULL arg is NOOP */
sys_mbox_free(NULL);
ok(uqmwHandle == &uqmwHandle);
ok(assertCount == 0);
ok(xtadCallCount == 0);
ok(vqdCallCount == 0);
invariants(1);
/* trip no-messages assertion */
uqmwRet = 1;
sys_mbox_free(&foo);
ok(uqmwHandle == &mbox);
ok(assertCount == 1);
ok(xtadCallCount == 0);
ok(vqdCallCount == 0);
invariants(1);
/* NULL struct members */
foo = (sys_mbox_t){NULL, NULL};
sys_mbox_free(&foo);
ok(uqmwHandle == NULL);
ok(xtadCallCount == 0);
ok(vqdCallCount == 0);
invariants(2);
/* NULL mbox member */
foo.xMbox = NULL;
sys_mbox_free(&foo);
ok(uqmwHandle == NULL);
ok(xtadCallCount == 1);
ok(xtadHandle == &task);
ok(vqdCallCount == 0);
invariants(2);
/* NULL task member */
foo.xTask = NULL;
sys_mbox_free(&foo);
ok(uqmwHandle == &mbox);
ok(xtadCallCount == 0);
ok(vqdCallCount == 1);
ok(vqdHandle == &mbox);
invariants(2);
/* happy path */
sys_mbox_free(&foo);
ok(uqmwHandle == &mbox);
ok(xtadCallCount == 1);
ok(xtadHandle == &task);
ok(vqdCallCount == 1);
ok(vqdHandle == &mbox);
invariants(2);
return tally();
}
I check invariants after every call to the function under test. I bundle
the call to reset()
within to minimize boilerplate. The switch separates
the invariants which must always hold, i.e., that entry and exit to critical
sections are balanced, from those that must hold after the critical section.
Conclusion
The C preprocessor provides everything I need to write thorough unit tests. I can read, write, and reason about tests with the same skills I use for C in general. To this, I add five dozen lines of tooling for automation. The sum is familiar, fast, and flexible.
Appendix A: tap.h
/* minimal TAP: test anything protocol */
/* https://testanything.org/ */
#include <stdio.h>
#include <stdlib.h>
int plan_; /* number of tests */
int ok_count_; /* number of passing tests */
int index_; /* ordinal of current test */
int verbose_; /* flag: show all expressions */
/* how many tests should run? */
/* set verbose flag if environment var VERBOSE exists */
#define plan(x) do { \
plan_ = (x); \
if (plan_) { \
printf("%d..%d\n", 1, plan_); \
} \
if (getenv("VERBOSE")) { \
verbose_ = 1; \
} \
} while (0)
/* test expression x and keep count of true values */
/* output diagnostic if false or if VERBOSE is set */
#define ok(x) do { \
int pass = (x); \
char *msg = "not ok"; \
index_++; \
if (pass) { \
ok_count_++; \
msg += 4; \
} \
if (verbose_ || !pass) { \
printf("%s %d - %s:%d\t%s\n", msg, index_, \
__FILE__, __LINE__, #x); \
} \
else { \
printf("%s %d\n", msg, index_); \
} \
} while (0)
/* summarize test run, and return an exit value for main() */
#define grade() ({ \
int pass; \
if (!plan_) { \
plan(index_); \
} \
pass = plan_ == ok_count_; \
printf("%s\n", pass ? "PASSED" : "FAILED"); \
pass ? EXIT_SUCCESS : EXIT_FAILURE; \
})
/* vim: set ts=8 noet */
Appendix B: sed
Originally, I used sed
to dump a function into a temporary file.
sed
is a non-interactive editor that uses regular expressions to
transform text. For example, I can use sed
to replace the earlier
calls to assert()
with calls to ok
.
$ sed 's/assert/ok/' old.c > new.c
The following sed
will extract sys_mbox_new()
from sys_arch.c
.
$ sed -nr '/^err_t sys_mbox_new\(/,/^}/p' sys_arch.c > sys_mbox_new.c
The quoted argument to sed
, called a sed script, says to start printing
with a line that begins with err_t sys_mbox_new(
and stop printing
after a line that begins with }
. The printed output is redirected to
the file, sys_mbox_new.c
.
Here’s a slightly more general sed script.
$ sed -nr '/^(void|u32_t|err_t) sys_mbox_.*\(.*[^;]$/,/^}/p' sys_arch.c > tmp.c
This time, the line can begin with one of three types: void
, u32_t
,
or err_t
, but it must not end with a semicolon. The .*
portions
are wildcards.
Here is the call to sed
used in my Makefile. In a Makefile, a literal
$
character must be quoted as $$
. make
replaces the $(pathsubst ...)
portion with the output filename minus the .c
.
$(TEMP_SRC): ../sys_arch.c
sed -nr '/^(void|u32_t|err_t) $(patsubst %.c,%,$@)\(.*[^;]$$/,/^}/p' $< > $@
This rule causes make
to run the following commands.
sed -nr '/^(void|u32_t|err_t) sys_arch_mbox_fetch\(.*[^;]$/,/^}/p' ../sys_arch.c > sys_arch_mbox_fetch.c
sed -nr '/^(void|u32_t|err_t) sys_mbox_new\(.*[^;]$/,/^}/p' ../sys_arch.c > sys_mbox_new.c
sed -nr '/^(void|u32_t|err_t) sys_mbox_free\(.*[^;]$/,/^}/p' ../sys_arch.c > sys_mbox_free.c