Macro function name creation and then add to list

Viewed 40

I am trying to create a simple unit test library in C, similar to googletest (yes I know I could use that, not the point of the exercise).

/* In unit_test.h */
#define UNIT_TEST_HELPER(SuiteName, TestName) void SuiteName##TestName
#define UNIT_TEST(SuiteName, TestName) UNIT_TEST_HELPER(SuiteName, TestName)()

/* in some other file */
#include "unit_test.h"

/* This successfully creates a function 'void HelloTest() */
UNIT_TEST(Hello, Test) {
  /* This is where testing code goes */
  printf("Calling from HelloTest()\n");
}

/* In main I am to do the following */
int main() {
  HelloTest();
}

What I would like to do is either:

a) Somehow call HelloTest() after its fully defined

b) Add HelloTest() to a list of functions to call (a list of void function pointers)

int main() {
  UnitTestRun(); /* Loop through function pointers */
}

I have no idea if this is possible with out a lot of work (have to look for functions with a certain signature or something). Goal is to try and avoid having to call each UNIT_TEST() function explicility, what I am currently doing and trying to make life simpler.

1 Answers

"Accumulate a list" is not within the capabilities of the C preprocessor, I'm afraid.

But it's pretty easy to do with an external preprocessor, and any decent build system will have a way to automatically run an external preprocessor as part of the build process.

If you're careful not to use the sequence UNIT_TEST( anywhere in your source code other than in the definition of a unit test, and also to ensure that you never spread UNIT_TEST(a, b) over two lines (which doesn't seem like a big restriction), then you could use a simple shell script to build a stand-alone unit-test caller:

#!/bin/sh
echo '#define CONCAT_(a,b) a##b'
echo '#define UNIT_TEST(a,b) void CONCAT_(a,b)(void); CONCAT_(a,b)();'
echo 'void UnitTestRun(void) {';
grep -hEo 'UNIT_TEST\([^)]*)' "$@"
echo '}'

This allows you to define unit tests in various files, and accumulate them into a single file containing only the function UnitTestRun. The file is completely stand-alone; it contains both declarations and invocations of each unit test, so it doesn't require any #includes. (This does require C99; if you needed to separate the declarations from the invocations, you could do two scans over the files.)

Here's a simple sample run of the script. I placed declarations of the test functions in suite1.c and suite2.c, and the above script in unit_test_maker.sh:

$ # The input files
$ cat suite1.c
UNIT_TEST(Suite1, FirstTest) { 
  /* blah, blah, blah */
}

UNIT_TEST(Suite1, SecondTest) {
  /* More blah */
}
$ cat suite2.c
UNIT_TEST(Suite2, OnlyTest) { 
  /* blah, blah, blah */
}

$ # The output of the script
$ ./unit_test_maker.sh suite1.c suite2.c
#define CONCAT_(a,b) a##b
#define UNIT_TEST(a,b) void CONCAT_(a,b)(void); CONCAT_(a,b)();
void UnitTestRun(void) {
UNIT_TEST(Suite1, FirstTest)
UNIT_TEST(Suite1, SecondTest)
UNIT_TEST(Suite2, OnlyTest)
}

$ # The output from preprocessing the script
$ ./unit_test_maker.sh suite1.c suite2.c | gcc -E -x c -
# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "<stdin>"


void UnitTestRun(void) {
void Suite1FirstTest(void); Suite1FirstTest();
void Suite1SecondTest(void); Suite1SecondTest();
void Suite2OnlyTest(void); Suite2OnlyTest();
}

$ # Compiles without warnings with `-Wall`.
$ ./unit_test_maker.sh suite1.c suite2.c | gcc -Wall -x c -c -o unit_test_run.o -
$
Related