0

I am attempting some conditional compilation for unit testing static functions in C. (roughly following the method outlined in this answer https://stackoverflow.com/a/593437/8347016)

I have it set up like so:

check_blah.c

#define UNIT_TEST 1   
#include "blah.h"
#include "testframework.h"

... 

blah.h

#if UNIT_TEST
#define u_static
#else
#define u_static static
#endif

...

#if UNIT_TEST
void foo(/* some parameters */)
#endif

blah.c

#include "blah.h"
...
u_static void foo(/*some parameters */) {
  /* some definition */ 
}
...

And just to cover my bases, here is how the files are compiled in make

check_blah: check_blah.c blah.o testframework.o
    gcc check_blah.c blah.o testframework.o -o check_blah

blah.o: blah.c blah.h
    gcc -c blah.c -o blah.h

testframework.o: /* similar to above */

If I compile check_blah without it having any references to foo, and then run gdb info functions, I see that foo is considered static even though UNIT_TEST is defined. Even more confusing, the compiler somehow does recognize UNIT_TEST as defined. In an experiment I defined a macro, SPEEP to be 10 if UNIT_TEST was defined and 100 otherwise. Then in check_blah.c I set some int to SPEEP and then printed it. It would print 10! So the line #define ustatic MUST be being hit as well, but somehow it isn't since foo remains static.

If I take the line #define UNIT_TEST 1 and move it to the top of blah.h, everything seems to work (i.e, info functions claims foo is not static).

So does anyone know the reason for this awkward (and inconsistent looking) behavior with preprocessor macros and directives?

aurreco
  • 21
  • 3
  • 1
    When you compile `blah.c`, the compiler is only going to look at blah.c and what it includes, nothing else. – Mat Jun 05 '22 at 16:41
  • @Mat oh damn you're right! The order of compilation entirely missed me as a reason why. Is there any way to work around this do you think? – aurreco Jun 05 '22 at 16:44
  • A lot of codebases have a "config.h" (or similar) included at the top of everything for common settings like that. Adding defines to the compiler command line is also possible. – Mat Jun 05 '22 at 16:46
  • Okay, so there would be no way for me to have the macro automatically defined if I'm compiling a unit test? I would have to toggle it manually in some config.h? – aurreco Jun 05 '22 at 16:48
  • If you want `blah.c` to be compiled using different preprocessor options, once for testing and once not, then you have to compile it twice, once with the option set and once without. There is no way to avoid that, because these setting are not _dynamic_ (at runtime), they change the way the compiler works and what it compiles. So you have to compile again. – MadScientist Jun 05 '22 at 16:58
  • @MadScientist I think I understand what you mean, but how could you force it to compile twice? – aurreco Jun 05 '22 at 17:06
  • You have to create two different targets, so you need two different rules in your makefile. One like `blah.o : blah.c ...` and another like `unit_blah.o : blah.c ...` where the recipe for the `unit_blah.o` adds `-DUNIT_TEST=1` to the compile line. Then when you link your unit test you link it with `unit_blah.o`, not `blah.o`. You should really investigate using pattern rules in your makefile, and using make variables, rather than writing out all the rules and options over and over. "DRY" is a critical principle for good programming. – MadScientist Jun 05 '22 at 17:29
  • A simple way is to define your macros, then *include* `blah.c` from `check_blah.c`. Normally you shouldn't include source files from other source files, but for unit testing this is typically OK, as long as you write a comment describing what you do and why. (You might not even need macros at all, since using this method, you can unit-test static functions.) – Lindydancer Jun 05 '22 at 19:35
  • @MadScientist I like this solution, thank you for bringing it up. I'm going to look into this. As for pattern rules, I do use them in my real makefile not to worry! – aurreco Jun 05 '22 at 23:15
  • @Lindydancer See I considered including the source since it would make my life a lot easier as you say, but I couldn't find a lot of examples where people did this and thought it best to air on the side of caution and try to stick to convention. Do you know of many example projects where the unit tests include source files? – aurreco Jun 05 '22 at 23:18
  • @aurreco, unfortunately, I have no concrete example to point to... – Lindydancer Jun 06 '22 at 04:33

0 Answers0