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