1 #!/usr/bin/env dub
2 /+ dub.sdl:
3     dependency "liblstparse" version="~>1.1.2"
4 +/
5 
6 /**
7  * Run this script to generate code coverage reports under the coverage/
8  * directory.
9  */
10 module gen_coverage;
11 
12 import liblstparse.parser;
13 
14 import std.process;
15 import std.file;
16 import std.path;
17 import std.stdio;
18 import std.regex;
19 import std.algorithm;
20 
21 const double MIN_COVERAGE_PERCENT = 0.95;
22 
23 const FILE_IGNORE_PATTERNS = [
24     `_test_root`,
25 ];
26 
27 const LINE_IGNORE_PATTERNS = [
28     `cov-ignore$`
29 ];
30 
31 int main() {
32     if (!exists("coverage")) mkdir("coverage");
33 
34     auto result = executeShell("dub test --build=unittest-cov -- --DRT-covopt=\"dstpath:coverage\"");
35     if (result.status != 0) {
36         stderr.writefln!"Code coverage generation failed with code %d"(result.status);
37         stderr.writeln(result.output);
38         return result.status;
39     }
40 
41     uint fileCount = 0;
42     ulong lineCount = 0;
43     ulong coveredLineCount = 0;
44 
45     foreach (DirEntry entry; dirEntries("coverage", SpanMode.shallow, false)) {
46         bool shouldSkipFile = false;
47         foreach (pattern; FILE_IGNORE_PATTERNS) {
48             auto r = regex(pattern);
49             if (matchFirst(entry.name, r)) {
50                 shouldSkipFile = true;
51                 break;
52             }
53         }
54         if (shouldSkipFile) continue;
55 
56         LSTFile file = LSTFile(entry);
57         fileCount++;
58 
59         writeln(file.filename);
60         bool coveredCompletely = true;
61         foreach (idx, line; file.lines) {
62             if (!line.coverage.isNull) {
63                 bool shouldSkipLine = false;
64                 foreach (pattern; LINE_IGNORE_PATTERNS) {
65                     auto r = regex(pattern);
66                     if (matchFirst(line.content, r)) {
67                         shouldSkipLine = true;
68                         break;
69                     }
70                 }
71                 if (shouldSkipLine) continue;
72                 uint coverage = line.coverage.get();
73                 if (coverage > 0) {
74                     coveredLineCount++;
75                 } else {
76                     writefln!"  ! L%04d -> %s"(idx, line.content);
77                     coveredCompletely = false;
78                 }
79                 lineCount++;
80             }
81         }
82         if (coveredCompletely) {
83             writeln("  100% Covered!");
84         }
85     }
86     double coverageAvg = cast(double) coveredLineCount / lineCount;
87     writefln!"Total coverage of %.2f%% (%d/%d) lines in %d files."(
88         coverageAvg * 100, coveredLineCount, lineCount, fileCount
89     );
90     if (coverageAvg < MIN_COVERAGE_PERCENT) {
91         stderr.writefln!"Coverage is less than the required %.2f%%."(MIN_COVERAGE_PERCENT * 100);
92         return 1;
93     }
94     return 0;
95 }