View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
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>&nbsp;</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 }