| 1 |
Ddoc |
|---|
| 2 |
|
|---|
| 3 |
$(D_S Code Coverage Analysis, |
|---|
| 4 |
|
|---|
| 5 |
$(P A major part of the engineering of a professional software project |
|---|
| 6 |
is creating a test suite for it. Without some sort of test suite, |
|---|
| 7 |
it is impossible to know if the software works at all. |
|---|
| 8 |
The D language has |
|---|
| 9 |
many features to aid in the creation of test suites, such as |
|---|
| 10 |
$(LINK2 unittest.html#unittest, unit tests) and |
|---|
| 11 |
$(LINK2 dbc.html, contract programming). |
|---|
| 12 |
But there's the issue of how thoroughly the test suite tests |
|---|
| 13 |
the code. |
|---|
| 14 |
The $(LINK2 http://www.digitalmars.com/ctg/trace.html, profiler) |
|---|
| 15 |
can give valuable information on which functions were called, and |
|---|
| 16 |
by whom. But to look inside a function, and determine which statements |
|---|
| 17 |
were executed and which were not, requires a code coverage analyzer. |
|---|
| 18 |
) |
|---|
| 19 |
|
|---|
| 20 |
$(P A code coverage analyzer will help in these ways:) |
|---|
| 21 |
|
|---|
| 22 |
$(OL |
|---|
| 23 |
$(LI Expose code that is not exercised by the test suite. |
|---|
| 24 |
Add test cases that will exercise it.) |
|---|
| 25 |
|
|---|
| 26 |
$(LI Identify code that is unreachable. Unreachable code is often |
|---|
| 27 |
the leftover result of program design changes. |
|---|
| 28 |
Unreachable code should be removed, |
|---|
| 29 |
as it can be very confusing to the maintenance programmer.) |
|---|
| 30 |
|
|---|
| 31 |
$(LI It can be used to track down why a particular section of code |
|---|
| 32 |
exists, as the test case that causes it to execute will |
|---|
| 33 |
illuminate why.) |
|---|
| 34 |
|
|---|
| 35 |
$(LI Since execution counts are given for each line, it is possible |
|---|
| 36 |
to use the coverage analysis to reorder the basic blocks in |
|---|
| 37 |
a function to minimize jmps in the most used path, thus |
|---|
| 38 |
optimizing it.) |
|---|
| 39 |
) |
|---|
| 40 |
|
|---|
| 41 |
$(P Experience with code coverage analyzers show that they dramatically |
|---|
| 42 |
reduce the number of bugs in shipping code. |
|---|
| 43 |
But it isn't a panacea, a code coverage analyzer won't help with:) |
|---|
| 44 |
|
|---|
| 45 |
$(OL |
|---|
| 46 |
$(LI Identifying race conditions.) |
|---|
| 47 |
$(LI Memory consumption problems.) |
|---|
| 48 |
$(LI Pointer bugs.) |
|---|
| 49 |
$(LI Verifying that the program got the correct result.) |
|---|
| 50 |
) |
|---|
| 51 |
|
|---|
| 52 |
$(P Code coverage analysers are available for many popular languages |
|---|
| 53 |
such as C++, but they are often third party products that integrate |
|---|
| 54 |
poorly with the compiler, and are often very expensive. |
|---|
| 55 |
A big problem with third party products is, in order to instrument |
|---|
| 56 |
the source code, they must include what is essentially a full blown |
|---|
| 57 |
compiler front end for the same language. Not only is this an expensive |
|---|
| 58 |
proposition, it often winds up out of step with the various compiler |
|---|
| 59 |
vendors as their implementations change and as they evolve various extensions. |
|---|
| 60 |
($(LINK2 http://gcc.gnu.org/onlinedocs/gcc-3.0/gcc_8.html, gcov), |
|---|
| 61 |
the Gnu coverage analyzer, is an exception as it is both free |
|---|
| 62 |
and is integrated into gcc.) |
|---|
| 63 |
) |
|---|
| 64 |
|
|---|
| 65 |
$(P The D code coverage analyser is built in as part of the D compiler. |
|---|
| 66 |
Therefore, it is always in perfect synchronization with the language |
|---|
| 67 |
implementation. It's implemented by establishing a counter for each |
|---|
| 68 |
line in each module compiled with the $(B -cov) switch. Code is inserted |
|---|
| 69 |
at the beginning of each statement to increment the corresponding counter. |
|---|
| 70 |
When the program finishes, a static destructor for std.cover collects all |
|---|
| 71 |
the counters, merges it with the source files, and writes the reports out |
|---|
| 72 |
to listing (.lst) files.) |
|---|
| 73 |
|
|---|
| 74 |
$(P For example, consider the Sieve program:) |
|---|
| 75 |
---------------------- |
|---|
| 76 |
/* Eratosthenes Sieve prime number calculation. */ |
|---|
| 77 |
|
|---|
| 78 |
import std.stdio; |
|---|
| 79 |
|
|---|
| 80 |
bit flags[8191]; |
|---|
| 81 |
|
|---|
| 82 |
int main() |
|---|
| 83 |
{ int i, prime, k, count, iter; |
|---|
| 84 |
|
|---|
| 85 |
writefln("10 iterations"); |
|---|
| 86 |
for (iter = 1; iter <= 10; iter++) |
|---|
| 87 |
{ count = 0; |
|---|
| 88 |
flags[] = true; |
|---|
| 89 |
for (i = 0; i < flags.length; i++) |
|---|
| 90 |
{ if (flags[i]) |
|---|
| 91 |
{ prime = i + i + 3; |
|---|
| 92 |
k = i + prime; |
|---|
| 93 |
while (k < flags.length) |
|---|
| 94 |
{ |
|---|
| 95 |
flags[k] = false; |
|---|
| 96 |
k += prime; |
|---|
| 97 |
} |
|---|
| 98 |
count += 1; |
|---|
| 99 |
} |
|---|
| 100 |
} |
|---|
| 101 |
} |
|---|
| 102 |
writefln("%d primes", count); |
|---|
| 103 |
return 0; |
|---|
| 104 |
} |
|---|
| 105 |
---------------------- |
|---|
| 106 |
|
|---|
| 107 |
$(P Compile and run it with:) |
|---|
| 108 |
|
|---|
| 109 |
$(CONSOLE |
|---|
| 110 |
dmd sieve -cov |
|---|
| 111 |
sieve |
|---|
| 112 |
) |
|---|
| 113 |
|
|---|
| 114 |
$(P The output file will be created called $(TT sieve.lst), the contents of |
|---|
| 115 |
which are:) |
|---|
| 116 |
|
|---|
| 117 |
$(CONSOLE |
|---|
| 118 |
|/* Eratosthenes Sieve prime number calculation. */ |
|---|
| 119 |
| |
|---|
| 120 |
|import std.stdio; |
|---|
| 121 |
| |
|---|
| 122 |
|bit flags[8191]; |
|---|
| 123 |
| |
|---|
| 124 |
|int main() |
|---|
| 125 |
5|{ int i, prime, k, count, iter; |
|---|
| 126 |
| |
|---|
| 127 |
1| writefln("10 iterations"); |
|---|
| 128 |
22| for (iter = 1; iter <= 10; iter++) |
|---|
| 129 |
10| { count = 0; |
|---|
| 130 |
10| flags[] = true; |
|---|
| 131 |
163840| for (i = 0; i < flags.length; i++) |
|---|
| 132 |
81910| { if (flags[i]) |
|---|
| 133 |
18990| { prime = i + i + 3; |
|---|
| 134 |
18990| k = i + prime; |
|---|
| 135 |
168980| while (k < flags.length) |
|---|
| 136 |
| { |
|---|
| 137 |
149990| flags[k] = false; |
|---|
| 138 |
149990| k += prime; |
|---|
| 139 |
| } |
|---|
| 140 |
18990| count += 1; |
|---|
| 141 |
| } |
|---|
| 142 |
| } |
|---|
| 143 |
| } |
|---|
| 144 |
1| writefln("%d primes", count); |
|---|
| 145 |
1| return 0; |
|---|
| 146 |
|} |
|---|
| 147 |
sieve.d is 100% covered |
|---|
| 148 |
) |
|---|
| 149 |
|
|---|
| 150 |
$(P The numbers to the left of the $(B |) are the execution counts for that |
|---|
| 151 |
line. Lines that have no executable code are left blank. |
|---|
| 152 |
Lines that have executable code, but were not executed, have a "0000000" |
|---|
| 153 |
as the execution count. |
|---|
| 154 |
At the end of the .lst file, the percent coverage is given. |
|---|
| 155 |
) |
|---|
| 156 |
|
|---|
| 157 |
$(P There are 3 lines with an exection count |
|---|
| 158 |
of 1, these were each executed once. The declaration line for $(TT i, prime), |
|---|
| 159 |
etc., has 5 because there are 5 declarations, and the initialization of |
|---|
| 160 |
each declaration counts as one statement.) |
|---|
| 161 |
|
|---|
| 162 |
$(P The first $(TT for) loop shows 22. This is the sum of the 3 parts |
|---|
| 163 |
of the for header. If the for header is broken up into 3 lines, the |
|---|
| 164 |
data is similarly divided:) |
|---|
| 165 |
|
|---|
| 166 |
$(CONSOLE |
|---|
| 167 |
1| for (iter = 1; |
|---|
| 168 |
11| iter <= 10; |
|---|
| 169 |
10| iter++) |
|---|
| 170 |
) |
|---|
| 171 |
|
|---|
| 172 |
$(P which adds up to 22.) |
|---|
| 173 |
|
|---|
| 174 |
$(P $(TT e1&&e2) and $(TT e1||e2) expressions conditionally |
|---|
| 175 |
execute the rvalue $(TT e2). |
|---|
| 176 |
Therefore, the rvalue is treated as a separate statement with its own |
|---|
| 177 |
counter:) |
|---|
| 178 |
|
|---|
| 179 |
$(CONSOLE |
|---|
| 180 |
|void foo(int a, int b) |
|---|
| 181 |
|{ |
|---|
| 182 |
5| bar(a); |
|---|
| 183 |
8| if (a && b) |
|---|
| 184 |
1| bar(b); |
|---|
| 185 |
|} |
|---|
| 186 |
) |
|---|
| 187 |
|
|---|
| 188 |
$(P By putting the rvalue on a separate line, this illuminates things:) |
|---|
| 189 |
|
|---|
| 190 |
$(CONSOLE |
|---|
| 191 |
|void foo(int a, int b) |
|---|
| 192 |
|{ |
|---|
| 193 |
5| bar(a); |
|---|
| 194 |
5| if (a && |
|---|
| 195 |
3| b) |
|---|
| 196 |
1| bar(b); |
|---|
| 197 |
|} |
|---|
| 198 |
) |
|---|
| 199 |
|
|---|
| 200 |
$(P Similarly, for the $(TT e?e1:e2) expressions, $(TT e1) and |
|---|
| 201 |
$(TT e2) are treated as separate statements.) |
|---|
| 202 |
|
|---|
| 203 |
<h3>Controlling the Coverage Analyser</h3> |
|---|
| 204 |
|
|---|
| 205 |
$(P The behavior of the coverage analyser can be controlled through |
|---|
| 206 |
the $(LINK2 phobos/std_cover.html, std.cover) module.) |
|---|
| 207 |
|
|---|
| 208 |
$(P When the $(B -cov) switch is thrown, the version identifier |
|---|
| 209 |
$(B D_Coverage) is defined.) |
|---|
| 210 |
|
|---|
| 211 |
<h3>References</h3> |
|---|
| 212 |
|
|---|
| 213 |
$(LINK2 http://en.wikipedia.org/wiki/Code_coverage, Wikipedia) |
|---|
| 214 |
|
|---|
| 215 |
) |
|---|
| 216 |
|
|---|
| 217 |
Macros: |
|---|
| 218 |
TITLE=Code Coverage Analysis |
|---|
| 219 |
WIKI=Dcover |
|---|
| 220 |
RPAREN=) |
|---|