/* * Copyright (c) 2006 RogĂ©rio Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.coverage.reporting.packages; import java.io.*; import java.lang.reflect.*; import java.util.*; import javax.annotation.*; import mockit.coverage.*; import mockit.coverage.data.*; import mockit.coverage.reporting.*; import mockit.coverage.testRedundancy.*; public final class IndexPage extends ListWithFilesAndPercentages { @Nullable private final List<File> sourceDirs; @Nonnull private final Map<String, List<String>> packageToFiles; @Nonnull private final Map<String, int[]> packageToPackagePercentages; @Nonnull private final PackageCoverageReport packageReport; private final int totalFileCount; public IndexPage( @Nonnull File outputFile, @Nullable List<File> sourceDirs, @Nullable Collection<String> sourceFilesNotFound, @Nonnull Map<String, List<String>> packageToFiles, @Nonnull Map<String, FileCoverageData> fileToFileData) throws IOException { super(new OutputFile(outputFile), " "); this.sourceDirs = sourceDirs; this.packageToFiles = packageToFiles; packageToPackagePercentages = new HashMap<String, int[]>(); packageReport = new PackageCoverageReport(output, sourceFilesNotFound, fileToFileData, packageToFiles.values()); totalFileCount = totalNumberOfSourceFilesWithCoverageData(fileToFileData.values()); } private static int totalNumberOfSourceFilesWithCoverageData(@Nonnull Collection<FileCoverageData> fileData) { return fileData.size() - Collections.frequency(fileData, null); } public void generate() { try { writeHeader(); List<String> packages = new ArrayList<String>(packageToFiles.keySet()); writeMetricsForEachFile(null, packages); writeLineWithCoverageTotals(); output.println(" </table>"); writeListOfRedundantTestsIfAny(); writeFooter(); } finally { output.close(); } } private void writeHeader() { ((OutputFile) output).writeCommonHeader("Code Coverage Report"); output.println(" <table id='packages'>"); writeTableCaption(); writeTableFirstRowWithColumnTitles(); } private void writeTableCaption() { if (sourceDirs == null) { output.println(" <caption>All Packages and Files</caption>"); } else { output.write(" <caption>All Packages and Files<div style='font-size: smaller'>"); output.write(getCommaSeparatedListOfSourceDirs()); output.println("</div></caption>"); } } @Nonnull private String getCommaSeparatedListOfSourceDirs() { List<File> dirs = sourceDirs; assert dirs != null; removeRedundantSourceDirectories(dirs); String concatenatedSourceDirs = dirs.toString(); String prefixToRemove = ".." + File.separatorChar; String commaSepDirs = concatenatedSourceDirs.replace(prefixToRemove, ""); return commaSepDirs.substring(1, commaSepDirs.length() - 1); } private void removeRedundantSourceDirectories(@Nonnull List<File> dirs) { for (int i = 0; i < dirs.size(); i++) { String dir1 = dirs.get(i).getPath(); int j = i + 1; while (j < dirs.size()) { String dir2 = dirs.get(j).getPath(); if (dir1.startsWith(dir2)) { dirs.remove(j); } else if (dir2.startsWith(dir1)) { dirs.remove(i); i--; break; } else { j++; } } } } private void writeTableFirstRowWithColumnTitles() { output.println(" <tr>"); output.write(" <th style='cursor: col-resize' onclick='showHideAllFiles()'>Packages: "); output.print(packageToFiles.keySet().size()); output.println("</th>"); output.write( " <th onclick='location.reload()' style='cursor: n-resize' title='" + "Click on the title for each metric to sort by size (total number of line segments, paths, or fields)." + "'>Files: "); output.print(totalFileCount); output.println("</th>"); Metrics.performAction(new Metrics.Action() { int tableColumn = 1; @Override public void perform(@Nonnull Metrics metric) { writeHeaderCellWithMetricNameAndDescription(metric); tableColumn++; } private void writeHeaderCellWithMetricNameAndDescription(@Nonnull Metrics metric) { output.write(" <th onclick='sortTables("); output.print(tableColumn); output.write(")' style='cursor: n-resize' title='"); output.write(metric.htmlDescription); output.write("'>"); output.write(metric.toString()); output.println("</th>"); } }); output.println(" </tr>"); } private void writeLineWithCoverageTotals() { output.println(" <tr class='total'>"); output.println(" <td>Total</td><td> </td>"); Metrics.performAction(new Metrics.Action() { @Override public void perform(@Nonnull Metrics metric) { writeLineWithCoverageTotals(metric); } }); output.println(" </tr>"); } private void writeLineWithCoverageTotals(@Nonnull Metrics metric) { int covered = coveredItems[metric.ordinal()]; int total = totalItems[metric.ordinal()]; int percentage = CoveragePercentage.calculate(covered, total); printCoveragePercentage(metric, covered, total, percentage); } @Override @SuppressWarnings("ParameterNameDiffersFromOverriddenParameter") protected void writeMetricsForFile(String unused, @Nonnull final String packageName) { writeRowStart(); writeTableCellWithPackageName(packageName); writeInternalTableForSourceFiles(packageName); Metrics.performAction(new Metrics.Action() { @Override public void perform(@Nonnull Metrics metric) { writeCoveragePercentageForPackage(packageName, metric); } }); writeRowClose(); } private void writeTableCellWithPackageName(@Nonnull String packageName) { printIndent(); output.write(" <td class='package"); List<String> filesInPackage = packageToFiles.get(packageName); if (filesInPackage.size() > 1) { output.write(" click' onclick='showHideFiles(this)"); } output.write("'>"); output.write(packageName.replace('/', '.')); output.println("</td>"); } private void writeInternalTableForSourceFiles(@Nonnull final String packageName) { printIndent(); output.println(" <td>"); printIndent(); output.println(" <table width='100%'>"); List<String> fileNames = packageToFiles.get(packageName); packageReport.writeMetricsForEachFile(packageName, fileNames); Metrics.performAction(new Metrics.Action() { @Override public void perform(@Nonnull Metrics metric) { recordCoverageInformationForPackage(packageName, metric); } }); printIndent(); output.println(" </table>"); printIndent(); writeInitiallyHiddenSourceFileCount(fileNames.size()); output.println(" </td>"); } private void recordCoverageInformationForPackage(@Nonnull String packageName, @Nonnull Metrics metric) { int coveredInPackage = packageReport.coveredItems[metric.ordinal()]; int totalInPackage = packageReport.totalItems[metric.ordinal()]; int packagePercentage = CoveragePercentage.calculate(coveredInPackage, totalInPackage); setPackageCoveragePercentage(packageName, metric, packagePercentage); totalItems[metric.ordinal()] += totalInPackage; coveredItems[metric.ordinal()] += coveredInPackage; } private void setPackageCoveragePercentage(@Nonnull String packageName, @Nonnull Metrics metric, int percentage) { int[] percentages = packageToPackagePercentages.get(packageName); if (percentages == null) { percentages = new int[Metrics.values().length]; packageToPackagePercentages.put(packageName, percentages); } percentages[metric.ordinal()] = percentage; } private void writeInitiallyHiddenSourceFileCount(int fileCount) { output.write(" <span>("); output.print(fileCount); output.println(" source files)</span>"); } private void writeCoveragePercentageForPackage(@Nonnull String packageName, @Nonnull Metrics metric) { int coveredInPackage = packageReport.coveredItems[metric.ordinal()]; int totalInPackage = packageReport.totalItems[metric.ordinal()]; int filePercentage = packageToPackagePercentages.get(packageName)[metric.ordinal()]; printCoveragePercentage(metric, coveredInPackage, totalInPackage, filePercentage); } @Override protected void writeClassAttributeForCoveragePercentageCell() { output.write("class='pt' "); } private void writeListOfRedundantTestsIfAny() { List<Method> redundantTests = TestCoverage.INSTANCE.getRedundantTests(); if (!redundantTests.isEmpty()) { output.println(" <br/>Redundant tests:"); output.println( " <ol title=\"Tests are regarded as redundant when they don't cover any additional line " + "segments or paths that haven't already been covered by a previous test.\n" + "Note this means the list of redundant tests depends on the order of test execution.\n" + "Such a test can be removed without weakening the test suite, as long as another test " + "for the same scenario performs its assertions.\">"); for (Method testMethod : redundantTests) { String testDescription = testMethod.getDeclaringClass().getSimpleName() + '.' + testMethod.getName(); output.append(""); output.write(" <li>"); output.write(testDescription); output.println("</li>"); } output.println(" </ol>"); } } private void writeFooter() { output.println(" <p>"); output.println(" <a href='http://jmockit.org'><img src='logo.png'></a>"); output.write(" Generated on "); output.println(new Date()); output.println(" </p>"); ((OutputFile) output).writeCommonFooter(); } }