1 /** 2 This tool aggregates D coverage files to a common directory. 3 4 D code coverage files are written to the directory where the test was initiated. When 5 multiple tests are run from different directories, multiple output files are produced. 6 This tool moves these files to a common directory, aggregating them in the process. 7 8 Copyright (c) 2017, eBay Software Foundation 9 Initially written by Jon Degenhardt 10 11 License: Boost Licence 1.0 (http://boost.org/LICENSE_1_0.txt) 12 13 **/ 14 module aggregate_codecov; 15 16 import std.file : exists, isDir, isFile, remove, rename; 17 import std.path : baseName, buildPath, stripExtension; 18 import std.stdio; 19 20 int main(string[] cmdArgs) 21 { 22 auto programName = (cmdArgs.length > 0) ? cmdArgs[0].stripExtension.baseName : "Unknown_program_name"; 23 24 if (cmdArgs.length < 3) 25 { 26 writefln("Synopsis: %s target-dir coverage-file [coverage-file...]", programName); 27 return 1; 28 } 29 30 auto targetDir = cmdArgs[1]; 31 auto coverageFiles = cmdArgs[2..$]; 32 33 if (!targetDir.exists || !targetDir.isDir) 34 { 35 writefln("%s is not a directory", targetDir); 36 return 1; 37 } 38 39 foreach (cf; coverageFiles) 40 { 41 if (!cf.exists || !cf.isFile) 42 { 43 writefln("%s is not a file", cf); 44 return 1; 45 } 46 } 47 48 foreach (cf; coverageFiles) 49 { 50 auto targetFile = buildPath(targetDir, cf.baseName); 51 if (!targetFile.exists) cf.rename(targetFile); 52 else mergeCoverageFiles(cf, targetFile); 53 } 54 55 return 0; 56 } 57 58 void mergeCoverageFiles(string fromFile, string toFile) 59 { 60 import std.algorithm : find, findSplit, max; 61 import std.array : appender; 62 import std.conv : to; 63 import std.format : format; 64 import std.math : log10; 65 import std.range : empty, lockstep, StoppingPolicy; 66 67 struct LineCounter 68 { 69 long count; 70 string line; 71 } 72 73 auto lines = appender!(LineCounter[])(); 74 string lastLine = ""; 75 long maxCounter = -1; 76 77 { // Scope for file opens 78 auto toInput = toFile.File; 79 auto fromInput = fromFile.File; 80 81 foreach (lineNum, f1, f2; lockstep(toInput.byLine, fromInput.byLine, StoppingPolicy.requireSameLength)) 82 { 83 if (!lastLine.empty) 84 throw new Exception(format("Unexpected file input. File: %s; File: %s; Line: %d", 85 fromFile, toFile, lineNum)); 86 87 auto f1Split = f1.findSplit("|"); 88 auto f2Split = f2.findSplit("|"); 89 90 if (f1Split[0].empty) 91 throw new Exception(format("Unexpected input. File: %s, %d", toFile, lineNum)); 92 if (f2Split[0].empty) 93 throw new Exception(format("Unexpected input. File: %s, %d", fromFile, lineNum)); 94 95 if ((f1Split[2].empty && !f2Split[2].empty) || 96 (!f1Split[2].empty && f2Split[2].empty) || 97 (!f1Split[2].empty && !f2Split[2].empty && f1Split[2] != f2Split[2])) 98 { 99 throw new Exception(format("Inconsistent file code line. File: %s; File: %s; Line: %d", 100 fromFile, toFile, lineNum)); 101 } 102 103 if (f1Split[1].empty) 104 { 105 lastLine = f1.to!string; 106 continue; 107 } 108 auto f1CounterStr = f1Split[0].find!(c => c != ' '); 109 auto f2CounterStr = f2Split[0].find!(c => c != ' '); 110 111 long f1Counter = f1CounterStr.empty ? -1 : f1CounterStr.to!long; 112 long f2Counter = f2CounterStr.empty ? -1 : f2CounterStr.to!long; 113 long counter = 114 (f1Counter == -1) ? f2Counter : 115 (f2Counter == -1) ? f1Counter : 116 f1Counter + f2Counter; 117 118 auto lc = LineCounter(counter, f1Split[2].to!string); 119 lines ~= lc; 120 if (counter > maxCounter) maxCounter = counter; 121 } 122 } 123 124 auto toBackup = toFile ~ ".backup"; 125 toFile.rename(toBackup); 126 127 size_t minDigits = max(7, (maxCounter <= 0) ? 1 : log10(maxCounter).to!long + 1); 128 string blanks; 129 string zeros; 130 foreach (i; 0 .. minDigits) 131 { 132 blanks ~= ' '; 133 zeros ~= '0'; 134 } 135 136 size_t codeLines = 0; 137 size_t coveredCodeLines = 0; 138 auto ofile = toFile.File("w"); 139 foreach (lc; lines.data) 140 { 141 if (lc.count >= 0) codeLines++; 142 if (lc.count > 0) coveredCodeLines++; 143 ofile.writeln( 144 (lc.count < 0) ? blanks : 145 (lc.count == 0) ? zeros : 146 format("%*d", minDigits, lc.count), 147 '|', lc.line); 148 } 149 auto lastLineSplit = lastLine.findSplit(" "); 150 ofile.write(lastLineSplit[0]); 151 if (codeLines == 0) ofile.writeln(" has no code"); 152 else ofile.writefln( 153 " is %d%% covered", 154 ((coveredCodeLines.to!double / codeLines.to!double) * 100.0).to!size_t); 155 toBackup.remove; 156 fromFile.remove; 157 }