Doing C code unit testing on a shoestring: Part 2- Code coverage analysis
Instrumenting the switch
statements
If the goal of instrumentation is simply to announce the value of the
controlling expression of a switch
statement, we can follow the pattern
established above and place
#ifdef INSTRUM_SWITCH
# define switch(ctl_stmt)
INSTRUM_SWITCH(ctl_stmt)
#endif
in instrum.h etc.
This would be good enough for establishing a regression base and using it in regression tests. However, if we want to prove code coverage, we need information on whether a given case or default was hit. That is to say, we need to instrument the case and default labels, which is our next subject.
Instrumenting the default
statements
To instrument default, let's follow our usual pattern and put in
instrum.h
#ifdef
INSTRUM_DEFAULT
# define
default INSTRUM_DEFAULT
#endif
Now, to sensibly define INSTRUM_DEFAULT, we need to use the default keyword and to stick instrumentation code somewhere around. To do so, observe that if falseval evaluates to 0, then
default:in any context is functionally equivalent to
default: if(!falseval) a_unique_label:
and to
default: if(falseval) ; else a_unique_label:
Between the two I don't have a preference. (A unique label will be needed to consume a dangling colon. It will remain unused, and most compilers will issue warnings about unused labels. You can safely ignore or suppress them for the unit under test.)
For falseval we can take
(instrum_default(__FILE__,__LINE__,__FUNCTION__),
!instrum_false)
where
extern void
instrum_default(const
char *filename,
int line,
const char
*function_name);
is some function that e.g. announces hitting a default statement, and
extern int instrum_false;
is a variable with the value 0 in a different translation unit (so that the compiler is not tempted to optimize anything out). Note that because of the comma operator, the whole expression for falseval must be parenthesized (because if is already a macro!)
For a unique label we can take a concatenation of the word
instrum_label and the line number (e.g. a unique label for line 2007
will be
instrum_label2007. This
is a common C fare -
CAT(instrum_label, __LINE__)
where the CAT macro concatenates the two expanded arguments:
#define
CAT1(a,b) a ## b
#define
CAT(a,b) CAT1(a,b)
NOTE. You may choose to run your (instrumented) tests in the host environment when possible. If you use Microsoft Visual C++ to build the executable, the construction of the artificial label " CAT(instrum_label_, __LINE__) " may be broken because of the broken implementation of __LINE__. To fix this, you can remove the support for "Edit and Continue" (command-line option /ZI) in the project, or, for version 7.0 and above, use the non-standard __COUNTER__ instead of __LINE__. Many thanks to Alf P. Steinbach for pointing it out. (Of course, other compilers may have their idiosyncrasies, too.)
Now we are in a position to put the pieces together and to define INSTRUM_DEFAULT as follows:
#define
INSTRUM_DEFAULT \
default: \
if((instrum_default(__FILE__,__LINE__,__FUNCTION__),
\
!instrum_false))
\
CAT(instrum_label, __LINE__)
For this instrumentation to compile there cannot be two (or more)
default
labels on the same line (or else we'll produce two identical
artificial labels). But for this to happen, there must be two or more
switch
statements on the same line, which is not a terribly good
practice; we can ignore it. After all, if you do engage in this
practice, a compilation error will notify you.


Loading comments... Write a comment