1
2
3
4
5 package mockit.coverage.reporting.packages;
6
7 import edu.umd.cs.findbugs.annotations.NonNull;
8 import edu.umd.cs.findbugs.annotations.Nullable;
9
10 import java.io.File;
11 import java.io.IOException;
12 import java.lang.reflect.Method;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.Date;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20
21 import mockit.coverage.CoveragePercentage;
22 import mockit.coverage.data.FileCoverageData;
23 import mockit.coverage.reporting.OutputFile;
24 import mockit.coverage.testRedundancy.TestCoverage;
25
26 import org.checkerframework.checker.index.qual.NonNegative;
27
28 public final class IndexPage extends ListWithFilesAndPercentages {
29 @Nullable
30 private final List<File> sourceDirs;
31 @NonNull
32 private final Map<String, List<String>> packageToFiles;
33 @NonNull
34 private final Map<String, Integer> packageToPackagePercentages;
35 @NonNull
36 private final PackageCoverageReport packageReport;
37 @NonNegative
38 private final int totalFileCount;
39
40 public IndexPage(@NonNull File outputFile, @Nullable List<File> sourceDirs,
41 @Nullable Collection<String> sourceFilesNotFound, @NonNull Map<String, List<String>> packageToFiles,
42 @NonNull Map<String, FileCoverageData> fileToFileData) throws IOException {
43 super(new OutputFile(outputFile), " ");
44 this.sourceDirs = sourceDirs;
45 this.packageToFiles = packageToFiles;
46 packageToPackagePercentages = new HashMap<>();
47 packageReport = new PackageCoverageReport(output, sourceFilesNotFound, fileToFileData, packageToFiles.values());
48 totalFileCount = totalNumberOfSourceFilesWithCoverageData(fileToFileData.values());
49 }
50
51 @NonNegative
52 private static int totalNumberOfSourceFilesWithCoverageData(@NonNull Collection<FileCoverageData> fileData) {
53 return fileData.size() - Collections.frequency(fileData, null);
54 }
55
56 public void generate() {
57 try {
58 writeHeader();
59
60 List<String> packages = new ArrayList<>(packageToFiles.keySet());
61 writeMetricsForEachFile(null, packages);
62 writeLineWithCoverageTotal();
63 output.println(" </table>");
64
65 writeListOfRedundantTestsIfAny();
66 writeFooter();
67 } finally {
68 output.close();
69 }
70 }
71
72 private void writeHeader() {
73 ((OutputFile) output).writeCommonHeader("Code Coverage Report");
74
75 output.println(" <table id='packages'>");
76
77 writeTableCaption();
78 writeTableFirstRowWithColumnTitles();
79 }
80
81 private void writeTableCaption() {
82 if (sourceDirs == null) {
83 output.println(" <caption>All Packages and Files</caption>");
84 } else {
85 output.write(" <caption>All Packages and Files<div style='font-size: smaller'>");
86 output.write(getCommaSeparatedListOfSourceDirs());
87 output.println("</div></caption>");
88 }
89 }
90
91 @NonNull
92 private String getCommaSeparatedListOfSourceDirs() {
93 List<File> dirs = sourceDirs;
94 assert dirs != null;
95 removeRedundantSourceDirectories(dirs);
96
97 String concatenatedSourceDirs = dirs.toString();
98 String prefixToRemove = ".." + File.separatorChar;
99 String commaSepDirs = concatenatedSourceDirs.replace(prefixToRemove, "");
100 return commaSepDirs.substring(1, commaSepDirs.length() - 1);
101 }
102
103 private static void removeRedundantSourceDirectories(@NonNull List<File> dirs) {
104 for (int i = 0; i < dirs.size(); i++) {
105 i = removeRedundantSourceDirectory(dirs, i);
106 }
107 }
108
109 private static int removeRedundantSourceDirectory(@NonNull List<File> dirs, @NonNegative int dirIndex) {
110 String dir1 = dirs.get(dirIndex).getPath();
111 int j = dirIndex + 1;
112
113 while (j < dirs.size()) {
114 String dir2 = dirs.get(j).getPath();
115
116 if (dir1.startsWith(dir2)) {
117 dirs.remove(j);
118 } else if (dir2.startsWith(dir1)) {
119 dirs.remove(dirIndex);
120 dirIndex--;
121 break;
122 } else {
123 j++;
124 }
125 }
126
127 return dirIndex;
128 }
129
130 private void writeTableFirstRowWithColumnTitles() {
131 output.println(" <tr>");
132 output.write(" <th style='cursor: col-resize' onclick='showHideAllFiles()'>Packages: ");
133 output.print(packageToFiles.size());
134 output.println("</th>");
135 output.write(" <th onclick='location.reload()' style='cursor: n-resize' title='"
136 + "Click on the column title to the right to sort by size (total number of items).'>Files: ");
137 output.print(totalFileCount);
138 output.println("</th>");
139 writeHeaderCellWithMetricNameAndDescription();
140 output.println(" </tr>");
141 }
142
143 private void writeHeaderCellWithMetricNameAndDescription() {
144 output.println(" <th onclick='sortTables()' style='cursor: n-resize' title='"
145 + "Measures how much of the executable production code (executable lines and fields) was exercised by tests.\n\n"
146 + "An executable line of code contains one or more executable segments, separated by branching points\n"
147 + "(if..else instructions, logical operators, etc.).\n\n"
148 + "A non-final field must have the last value assigned to it read by at least one test, to be considered as covered.\n\n"
149 + "Percentages are calculated as 100 × (CS + CF)\n"
150 + " ────────\n"
151 + " S + F\n\n"
152 + "where S+F is the total number of segments and fields, and CS+CF the covered segments and fields."
153 + "'>Cvrg</th>");
154 }
155
156 private void writeLineWithCoverageTotal() {
157 output.println(" <tr class='total'>");
158 output.println(" <td>Total</td><td> </td>");
159
160 int covered = coveredItems;
161 int total = totalItems;
162 int percentage = CoveragePercentage.calculate(covered, total);
163 printCoveragePercentage(covered, total, percentage);
164
165 output.println(" </tr>");
166 }
167
168 @Override
169 @SuppressWarnings("ParameterNameDiffersFromOverriddenParameter")
170 protected void writeMetricsForFile(String unused, @NonNull String packageName) {
171 writeRowStart();
172 writeTableCellWithPackageName(packageName);
173 writeInternalTableForSourceFiles(packageName);
174 writeCoveragePercentageForPackage(packageName);
175 writeRowClose();
176 }
177
178 private void writeTableCellWithPackageName(@NonNull String packageName) {
179 printIndent();
180 output.write(" <td");
181
182 List<String> filesInPackage = packageToFiles.get(packageName);
183
184 if (filesInPackage.size() > 1) {
185 output.write(" class='click' onclick='shFls(this)'");
186 }
187
188 output.write('>');
189 output.write(packageName.replace('/', '.'));
190 output.println("</td>");
191 }
192
193 private void writeInternalTableForSourceFiles(@NonNull String packageName) {
194 printIndent();
195 output.println(" <td>");
196
197 printIndent();
198 output.println(" <table>");
199
200 List<String> fileNames = packageToFiles.get(packageName);
201 packageReport.writeMetricsForEachFile(packageName, fileNames);
202
203 recordCoverageInformationForPackage(packageName);
204
205 printIndent();
206 output.println(" </table>");
207 printIndent();
208
209 writeInitiallyHiddenSourceFileCount(fileNames.size());
210 printIndent();
211 output.println(" </td>");
212 }
213
214 private void recordCoverageInformationForPackage(@NonNull String packageName) {
215 int coveredInPackage = packageReport.coveredItems;
216 int totalInPackage = packageReport.totalItems;
217 int packagePercentage = CoveragePercentage.calculate(coveredInPackage, totalInPackage);
218
219 totalItems += totalInPackage;
220 coveredItems += coveredInPackage;
221 packageToPackagePercentages.put(packageName, packagePercentage);
222 }
223
224 private void writeInitiallyHiddenSourceFileCount(@NonNegative int fileCount) {
225 output.write(" <span>(");
226 output.print(fileCount);
227 output.println(" source files)</span>");
228 }
229
230 private void writeCoveragePercentageForPackage(@NonNull String packageName) {
231 int filePercentage = packageToPackagePercentages.get(packageName);
232 printCoveragePercentage(packageReport.coveredItems, packageReport.totalItems, filePercentage);
233 }
234
235 private void writeListOfRedundantTestsIfAny() {
236 TestCoverage testCoverage = TestCoverage.INSTANCE;
237
238 if (testCoverage == null) {
239 return;
240 }
241
242 List<Method> redundantTests = testCoverage.getRedundantTests();
243
244 if (!redundantTests.isEmpty()) {
245 output.println(" <br>Redundant tests:");
246 output.println(" <ol title=\"Tests are regarded as redundant when they don't cover any additional line "
247 + "segments or fields that haven't already been covered by a previous test.\n"
248 + "Note this means the list of redundant tests depends on the order of test execution.\n"
249 + "Such a test can be removed without weakening the test suite, as long as another test "
250 + "for the same scenario performs its assertions.\">");
251
252 for (Method testMethod : redundantTests) {
253 String testDescription = testMethod.getDeclaringClass().getSimpleName() + '.' + testMethod.getName();
254
255 output.append("");
256 output.write(" <li>");
257 output.write(testDescription);
258 output.println("</li>");
259 }
260
261 output.println(" </ol>");
262 }
263 }
264
265 private void writeFooter() {
266 output.println(" <p>");
267 output.println(" <a href='http://jmockit.github.io'><img src='logo.png'></a>");
268 output.write(" Generated on ");
269 output.println(new Date());
270 output.println(" </p>");
271 ((OutputFile) output).writeCommonFooter();
272 }
273 }