A hack using the preprocessor to get (non-branching) code coverage.
Why not use the __LINE__
preprocessor directive to keep track of what lines have been executed?
Let's say we have simple function like this:
void maybe_trigger(float temperature) {
if(temperature > 100) {
printf("Triggered at value %d.\n", temperature);
}
else {
printf(Not triggered yet.\n");
}
}
We want to write unit tests to cover as much as possible of this function, be we don't have the tooling available. What can we do?
Well, we can define a macro C (for cover) like this:
#define C (lineVisited[numLinesVisited++] = __LINE__);
int lineVisited[50000];
int numLinesVisited = 0;
lineVisited
together with numLinesVisited
keeps track of what lines are visited, using the
__LINE__
preprocessor macro. They can e.g be defined just above the function we want to cover.
Then we can put this C macro at the start of every line we want to cover:
void maybe_trigger(float temperature) { // Line 37
C if(temperature > 100) {
C printf("Triggered at value %d.\n", a);
C }
else { // Line 40
C printf(Not triggered yet.\n");
C } // Line 42
}
Note that we cannot put a coverage C on line 40, since it would break the syntax of the language putting a statement inbetween '}' and 'else'.
Now we can write unit tests, which will update the lineVisited buffer, and using this little helper function, we can display what coverage we have in percent, and what lines are missing:
void coverageReport(int lineStart, int lineEnd) {
int totalLines = lineEnd - lineStart + 1;
int numCovered = 0;
for(int line = lineStart; line <= lineEnd; line++) {
int covered = 0;
for(int i=0; i<numLinesVisited; i++) {
if(lineVisited[i] == line) {
covered = 1;
numCovered++;
break;
}
}
if(!covered)
printf("Line %d was not covered.\n", line);
}
printf("Coverage: %1.1f\n", 100 * numCovered / totalLines);
}
Note that coverageReport
needs to know what line to start and end with. For example, maybe
the above function was defined at line 37-42 in some module, then it would be called like this:
coverageReport(37, 42);
One final challenge: when to call coverageReport? Well, one idea is from a 'reportTest' unit test, which is somehow run last, after all other (relevant) unit tests. In CGreen[1] style:
Ensure(some_suite_name, reportTest) {
coverageReport(37, 42);
}