Doing C code unit testing on a shoestring: Part 2- Code coverage analysis

Ark Khasin, MacroExpressions

September 18, 2007

Ark Khasin, MacroExpressions

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.

< Previous
Page 2 of 5
Next >

Loading comments...

Most Commented

Parts Search Datasheets.com

KNOWLEDGE CENTER