Squish Coco

Code Coverage Measurement for C/C++

Part I
Code Coverage Overview

Chapter 1  Overview of Squish Coco’s Code Coverage Analysis

CoverageScanner is a C++ program that—in effect—replaces the usual compiler. It preprocesses the source code using the native preprocessor program, in the course of which it inserts instrumentation code, and at the end it compiles and links the project file, just the same as for a normal build.


CoverageScanner’s instrumentation consists of:

  1. declaring a static table that contains the of instrumentation results.
  2. generating a library which produces an execution report upon program termination.
  3. adding the instrumentation code itself for each relevant C and C++ statement or Boolean expression.

The compiler, linker and preprocessor used are from the native environments (gcc, Visual Studio, etc.). These tools are used transparently and the developer has only to prepend ’cs’ to the name of the compiler executable to activate the code coverage 1. For example, using ’csgcc’ instruments the source code and uses the ’gcc’ compiler to generate objects.
Another code coverage technique involves adding measurement points according to debug information. Since CoverageScanner produces instrumented code during the generation phase, it is possible to analyse the optimized code and achieve a more fine-grained analysis than some other tools achieve. (Many compilers produce unusable debug information on optimized code, but this doesn’t affect CoverageScanner.)
CoverageScanner’s principle code coverage analysis is not to highlight executed source lines, but rather to place marks on execution paths. This makes the analysis independent of the source code’s formatting and groups all sequential instructions together in a single measurement point. This also minimizes the extra memory and processing overhead that the instrumented code incurs.

1.1  Code instrumentation

This section describes the work CoverageScanner does to instrument the code, with each aspect described in its own subsection.

1.1.1  Detection

CoverageScanner parses all C++ language constructs and detects:

  1. Execution paths.
  2. Boolean expressions which produce different execution paths (if, for, while, switch, …).

To put this in concrete terms we present the following very simple example function:

   1 void foo()
   2 {
   3   bool found=false;
   4   for (int i=0; (i<100) && (!found); ++i) {
   5     if (i==50) break;
   6     if (i==20) found=true;
   7     if (i==30) found=true;
   8   }
   9   printf("foo\n");
  10 }
Source code sample

CoverageScanner first analyses the sequential statements to record each execution path. It determines which instructions need to be instrumented and places a flag to record their execution. To avoid performance problems, CoverageScanner groups all sequential instructions together and records their execution only once: before

coverage instrumentation is usually called Statement Coverage.

In the following version of the example, the instrumented statements are underlined:

   1 void foo()
   2 {
   3   bool found=false// [3] not instrumented
   4   for (int i=0; (i<100) && (!found); ++i) {
   5     if (i==50) break;
   6     if (i==20) found=true;
   7     if (i==30) found=true;
   8   }
   9   printf("foo\n");  // [9] not instrumented
  10 } // [10]
Code coverage at branch level

In this example, lines 3 and 9 are not monitored since lines 3, 9 and 10 are sequential operations. This doesn’t have an impact concerning coverage because if line 10 is executed then lines 3 and 9 must have be executed before it.

CoverageScanner’s analysis of conditions determines which expressions are used in conditional statements (if, while, for, …). These statements separate the execution path into two or more branches. Code coverage monitors execution by recording if the condition was true or false during the application’s execution.
This kind of code coverage instrumentation is usually called Decision Coverage.
In the example, the instrumented conditions are underlined (the instrumented statements are displayed in bold):

   1 void foo()
   2 {
   3   bool found=false;
   4   for (int i=0; (i<100) && (!found); ++i) {
   5     if (i==50break;
   6     if (i==20found=true;
   7     if (i==30found=true;
   8   }
   9   printf("foo\n");
  10 }
Code coverage at decision level

The analysis of Boolean expressions is used to determine which combinations are used in a conditional statements’ (if, while, for, …) Boolean operations (and, or, …). This makes it possible to have a more fine grained analysis of why a particular code segment was executed.
This kind of code coverage instrumentation is usually called Condition Coverage.
For example, the statement return 0; of if (a || b) return 0; can be executed if a or b is true. Analysis of the Boolean expressions will record if variables a and b were true or false. This tells us if the statement ‘return 0;’ was executed, which it will be if either or both of the two expressions was true during the test.



In the example, the instrumented Boolean expressions are underlined:

   1 void foo()
   2 {
   3   bool found=false;
   4   for (int i=0; (i<100) && (!found); ++i) {
   5     if (i==50break;
   6     if (i==20found=true;
   7     if (i==30found=true;
   8   }
   9   printf("foo\n");
  10 }
Code coverage at condition level



Squish Coco’s analysis is not limited to control statements (such as dowhile or ifthen constructs). For example, CoverageScanner also instruments the assignment of Boolean expressions. Constant or static expressions are not instrumented since their values are computed at compile time or once during program initialization.


In the example, the instrumented Boolean expressions are underlined:

   1 void foo()
   2 {
   3   static bool a = x && y;
   4   bool b = x && y;
   5   bool c = b;
   6   int d = b?0:1;
   7 }
Code coverage of assignments

The statement static bool a = x && y is not instrumented because it is static. And the statement bool c = b is not instrumented because the expression is a value (albeit a Boolean value), not a Boolean expression.

1.1.2  Code insertion

After the detection phase, CoverageScanner inserts the instrumentation code itself as the following examples illustrate. (Note that the inserted code is displayed in bold blue.)

Sequential Statement
The statements are instrumented before their execution. The instrumentation consists in allocating a Boolean variable which detects if the code was executed or not.
Example
   1 a=foo();
   2 a++;
   3 break;
will be changed into:
   1 a=foo();
   2 a++;
   3 { inst[0]=1; break}
 inst[0]  is set to 1 if the ‘break’ statement is executed.
Conditional Statements and Boolean Expressions (full instrumentation)
For Boolean expressions the same principle applies except that in addition to recording the execution itself, the state (true or false) is also recorded.
Example
   1 if ( a<b )
   2   return 1;
will be changed into:
   1 if ( (a<b) ? inst[0]=1 : inst[1]=1,0 )
   2   return 1;
 inst[0]  is set to 1 if the Boolean expression a<b was true.
 inst[1]  is set to 1 if the Boolean expression a<b was false.
Conditional Statements and Boolean Expressions (partial instrumentation)
In some cases, recording the value of a Boolean expression is unnecessary for sequential statement instrumentation. For example, the statement if (b) return 0; else return 1; is completely covered by a statement coverage—after all, recording whether b becomes true or false doesn’t provide any extra information. Similarly, for the statement if (b) return 0; it is only necessary to check if b was false. By default, Squish Coco suppresses the generation of redundant instrumentation in order to minimize the instrumented code’s size and to maximize execution speed.
Example
   1 if ( a<b )
   2   return 1;
will be changed into:
   1 if ( a<b )
   2   return 1;
   3 else inst[0]=1 ;
 inst[0]  is set to 1 if the Boolean expression a<b was false. Since the statement coverage records the instruction return 1;, it is not necessary to record if a<b was true;

The generated instrumentation code that needs to be inserted to provide statement coverage for the earlier foo() function example means that the function’s code is transformed to the code shown here:

   1 char inst[5];
   2 void foo()
   3 {
   4   bool found=false;
   5   for (int i=0; (i<100) && (!found); ++i)
   6   {
   7     if (i==50 ) { inst[0]=1;break;}
   8     if (i==20 ) { inst[1]=1;found=true;}
   9     if (i==30 ) { inst[2]=1;found=true;}
  10   inst[3]=1; }
  11   printf("foo\n");
  12 inst[4]=1; }
Code coverage instrumentation at branch level

If we insert the instrumentation code necessary to support decision coverage into this example, the resulting code will look like this:

   1 char inst[13];
   2 void foo()
   3 {
   4   bool found=false;
   5   for (int i=0; ((i<100 && !found)?inst[0]=1:inst[1]=1,0); ++i)
   6   {
   7     if ((i==50?inst[2]=1:inst[3]=1,0){ inst[4]=1; break;}
   8     if ((i==20?inst[5]=1:inst[6]=1,0){inst[7]=1; found=true;}
   9     if ((i==30?inst[8]=1:inst[9]=1,0){inst[10]=1; found=true;}
  10   inst[11]=1; }
  11   printf("foo\n");
  12 inst[12]=1; }
Code coverage instrumentation at decision level

If we insert the instrumentation code necessary to support condition coverage into this example, the resulting code will look like this (except that we have wrapped one line to fit better on the page):

   1 char inst[15];
   2 void foo()
   3 {
   4   bool found=false;
   5   for (int i=0;((i<100)?inst[0]=1:inst[1]=1,0) &&
   6        ((!found)?inst[2]=1:inst[3]=1,0); ++i) {
   7     if ((i==50?inst[4]=1:inst[5]=1,0){inst[6]=1; break;}
   8     if ((i==20?inst[7]=1:inst[8]=1,0){inst[9]=1; found=true;}
   9     if ((i==30?inst[10]=1:inst[11]=1,0){inst[12]=1; found=true;}
  10   inst[13]=1; }
  11   printf("foo\n");
  12 inst[14]=1; }
Full code coverage instrumentation at condition level

Here is what the code would look like if we inserted the partial instrumentation code for condition coverage (again, with one line wrapped):

   1 char inst[12];
   2 void foo()
   3 {
   4   bool found=false;
   5   for (int i=0; ((i<100)?inst[0]=1:inst[1]=1,0) &&
   6        ((!found)?inst[2]=1:inst[3]=1,0); ++i) {
   7     if (i==50 ) {inst[4]=1; break;} else inst[5]=1;
   8     if (i==20 ) {inst[6]=1; found=true;} else inst[7]=1;
   9     if (i==30 ) {inst[8]=1; found=true;} else inst[9]=1;
  10   inst[10]=1; }
  11   printf("foo\n");
  12 inst[11]=1; }
Partial code coverage instrumentation at condition level

1.1.3  Result of coverage analysis

The CoverageBrowser program features a graphical user interface which it uses to display the analysed results of the instrumentation. CoverageBrowser uses color coding to indicate statement status. For this manual we have used the same colors as the program, but in addition we have applied text effects to aid those reading a monochrome print out.

  • bold green indicates statements which are executed.
  • shaded bold red indicates statements which are not executed.
  • italic orange indicates statements which are partially executed. These are Boolean expressions or conditional statements which were always true or always false.

The output for the example would be:

   1 void foo()
   2 {
   3   bool found=false;
   4   for (int i=0; (i<100) && (!found); ++i)
   5   {
   6     if (i==50break;
   7     if (i==20found=true;
   8     if (i==30found=true;
   9   }
  10   printf("foo\n");
  11 }
Code coverage output

The expression i<100 is partially executed because is was always true. The expressions i==50 and i==30 are always false, yet they are shown as being fully executed. Since these expression are incorporated in an if ... then statement, it is not necessary to check if they were true (here the non-execution of the break; and found=true; statements provide the same information).

1.2  Performance

The insertion of instrumentation increases the code size and also affects the instrumented application’s performance—it will use more memory and will run slower.
For non-conditional expressions the instrumentation code involves a simple write to a fixed memory location. However, for conditional expressions more detailed analysis is needed and this is more computationally expensive.
Overall, an instrumented application will be from 60% to 90% larger, and will run from 10% to 30% slower.

Detailed measurements are available in the Appendix (see chap. benchmarks).

1.3  Statistics

Some developers write more lines of code than others simply by using a particular coding style—for example, putting opening braces on a line of their own rather than on the same line as, say, an if statement. In view of this, Squish Coco computes its statistics using a more subtle measure that isn’t susceptible the the vagaries of coding style: its calculations are based on the number of executed instrumented instructions compared with the total number of instrumented instructions.

Each simple instrumented instruction (return, break, last instruction of a function, etc.) is recorded by one single instrumentation. A fully instrumented condition in an IF...THEN...ENDIF statement uses two instrumentations: one for the true case and one for the false case. If the code is only partially instrumented, only one condition is recorded (either the false case or the true case).

The statistics themselves depend of the type of instrumentation. It is not generally possible to compare code coverage at branch level with that at decision level: having 80% coverage at decision level, does not tell us anything about the coverage at branch level, which could be bigger or smaller. The only thing we can be sure of is that reaching 100% coverage is more difficult with condition coverage than with decision coverage and than branch coverage.
In our example, the coverage is between 60% and 75%. The following table shows the details for each instrumentation.

Code coverage typeInstrumentations ExecutedCoverage
Branch (see figure stat_coverage_branch)5360%
Decision partial (see figure stat_coverage_decision_partial)9777%
Decision full (see figure stat_coverage_decision_full)13969%
Condition partial (see figure stat_coverage_condition_partial)12975%
Condition full (see figure stat_coverage_condition_full)151066%



In the examples below, the details of the calculations are displayed using subscripts. The first number is how many instrumentations were executed and the second is the number of instrumentations in total.

   1 void foo()
   2 {
   3   bool found=false;
   4   for (int i=0; (i<100) && (!found); ++i)
   5   {
   6     if (i==50) break;0/1 [not executed]
   7     if (i==20) found=true;1/1 [executed]
   8     if (i==30) found=true;0/1 [not executed]
   9   }1/1 [executed]
  10   printf("foo\n");
  11 }1/1 [executed]
Code coverage statistics for full branch level instrumentation
   1 void foo()
   2 {
   3   bool found=false;
   4   for (int i=0; (i<100) && (!found)2/2 [was false and true]; ++i)
   5   {
   6     if (i==501/2 [was false but not true]break;0/1 [not executed]
   7     if (i==202/2 [was false and true]found=true;1/1 [executed]
   8     if (i==301/2 [was false but not true]found=true;0/1 [not executed] }1/1 [executed]
   9   printf("foo\n");
  10 }1/1 [executed]
Code coverage statistics for full decision level instrumentation
   1 void foo()
   2 {
   3   bool found=false;
   4   for (int i=0; (i<100) && (!found)1/1 [was false]; ++i)
   5   {
   6     if (i==501/1 [was false]break;0/1 [not executed]
   7     if (i==201/1 [was false]found=true;1/1 [executed]
   8     if (i==301/1 [was false]found=true;0/1 [not executed]
   9   }1/1 [executed]
  10   printf("foo\n");
  11 }1/1 [executed]
Code coverage statistics for partial decision level instrumentation
   1 void foo()
   2 {
   3   bool found=false;
   4   for (int i=0; (i<100)1/2 [was true but not false] && (!found)2/2 [was false and true]; ++i)
   5   {
   6     if (i==501/2 [was false but not true]break;0/1 [not executed]
   7     if (i==202/2 [was false and true]found=true;1/1 [executed]
   8     if (i==301/2 [was false but not true]found=true;0/1 [not executed]
   9   }1/1 [executed]
  10   printf("foo\n");
  11 }1/1 [executed]
Code coverage statistics for full condition level instrumentation
   1 void foo()
   2 {
   3   bool found=false;
   4   for (int i=0; (i<100)1/2 [was true but not false] && (!found)2/2 [was false and true]; ++i)
   5   {
   6     if (i==501/1 [was false]break;0/1 [not executed]
   7     if (i==201/1 [was false]found=true;1/1 [executed]
   8     if (i==301/1 [was false]found=true;0/1 [not executed]
   9   }1/1 [executed]
  10   printf("foo\n");
  11 }1/1 [executed]
Code coverage statistics for partial condition level instrumentation

Chapter 2  Testing Methodologies

This chapter discusses the various testing strategies that can be carried out so as to make the best use of the Squish Coco tools.

2.1  Coverage Hits vs. Counts

If we want to set very stringent standards for an application to ensure that it is of the highest possible quality, executing just portions of its code, or executing code just once, is insufficient.

To address this issue the Squish Coco tools provide two possible approaches:

Code Coverage Count
We can require that each portion of instrumented code is executed a minimum number of times. This can be achieved by adding to the instrumented code so that it counts each execution.1 Naturally, setting such minimums, and the extra bookkeeping itself, can make the application consume more memory and run a bit slower. An important disadvantage of this approach is that loops in the source code end up having a high count with only a single execution, so counting this way isn’t always useful.
Test Coverage Count
We can require that each portion of instrumented code is executed by a minimum number of test items. This avoids the code coverage count disadvantage regarding loops, since by setting a minimum number of test executions for each portion of instrumented code, we give equal value to loops, recursions, and to instructions that are executed only once.

2.2  Testing Strategies

Squish Coco can be adapted to fit in with many different testing strategies, from unit tests during early development all the way through to complete product validation (e.g., acceptance tests). CoverageBrowser supports the merging of code coverage results at each stage of testing, and so provides a precise overview of the software’s overall quality.

2.2.1  Manual White Box Tests

White box testing (a.k.a. clear box testing, glass box testing or structural testing) uses an internal perspective of the system to design test cases based on internal structure. It requires programming skills to identify all paths through the software. The tester chooses test case inputs to exercise paths through the code and determines the appropriate outputs.
Definition from Wikipedia



Interactive white box tests are easy to do using Squish Coco: once the application has been compiled with CoverageScanner, the test engineer can execute the application, and after each execution they can import the execution report into CoverageBrowserfor analysis.

2.2.2  Manual Black Box Tests

Black box testing takes an external perspective of the test object to derive test cases. These tests can be functional or non-functional, though usually functional. The test designer selects valid and invalid input and determines the correct output. There is no knowledge of the test object’s internal structure.
Definition from Wikipedia



Black box tests assume that test engineers have no knowledge of the application’s internals—they work purely in terms of inputs and the corresponding expected outputs (whether of data or of error reports for invalid input).

For this kind of testing it is possible to follow the same pattern as for white box testing, but without providing the test engineer with the instrumentation database (.csmes file) generated by CoverageScanner. The application itself will generate an execution report which can be analyzed after the testing cycle. However, this approach has the disadvantage that test engineers cannot manage their own execution reports (e.g., to add comments).

Squish Coco has a facility which makes black box testing possible without disadvantaging test engineers. The CoverageBrowser tool can generate an instrumentation database (.csmes file) which does not contain any source code or source browsing information. This at least allows test engineers to manage the list of tests and view the statistics, but does not provide any source level coverage information.

2.2.3  Unit Tests

Unit testing is a method by which individual units of source code are tested to determine if they are fit for use. A unit is the smallest testable part of an application. In procedural programming a unit could be an entire module but is more commonly an individual function or procedure. In object-oriented programming a unit is often an entire interface, such as a class, but could be an individual method.
Definition from Wikipedia



CoverageScanner supports the saving of the code coverage data generated by each unit test using its library. (see chap. unit-test)

As long as all objects are well compiled2, it is possible to merge the unit tests instrumentation database into those of the real application. And so see the code coverage of the unit tests on the main application.

2.2.4  Automatic Tests

Squish Coco allows us to annotate the execution report with the execution name and state. This does not require any specific library—the information can be inserted directly by editing the execution report before and after the execution of a test item. This makes it possible to integrate the report into an automatic testing framework or for use with a test executing script. (see chap. execution-name-status)


1
This can be done using CoverageScanner’s --cs-on command line option.
2
Well compiled means that a source file is not be compiled twice or at least it is not be compiled with different preprocessor options.