Index: test/tools/llvm-cov/multiple-files.test =================================================================== --- test/tools/llvm-cov/multiple-files.test +++ test/tools/llvm-cov/multiple-files.test @@ -7,3 +7,15 @@ // CHECK-NEXT: {{^}}b{{[/\\]}}c{{[/\\]}}f4.c // CHECK-NEXT: {{^}}b{{[/\\]}}f3.c // CHECK-NEXT: {{^}}f1.c + +// RUN: llvm-cov show -format=html %S/Inputs/multiple-files.covmapping -instr-profile=%t.profdata -o %t.dir +// RUN: FileCheck -check-prefixes=HTML-INDEX -input-file=%t.dir/index.html %s + +// HTML-INDEX:
Project directory:
+// HTML-INDEX:
{{[/\\]}}tmp{{[/\\]}}coverage
+// HTML-INDEX:
Function coverage:
+// HTML-INDEX:
Line coverage:
+// HTML-INDEX:
{{[/\\]}}a
+// HTML-INDEX:
{{[/\\]}}b{{[/\\]}}c
+// HTML-INDEX:
{{[/\\]}}b
+// HTML-INDEX:
{{[/\\]}}
Index: test/tools/llvm-cov/native_separators.c =================================================================== --- test/tools/llvm-cov/native_separators.c +++ test/tools/llvm-cov/native_separators.c @@ -14,7 +14,7 @@ // RUN: FileCheck -check-prefixes=HTML -input-file=%t.dir/coverage/tmp/native_separators.c.html %s // TEXT-INDEX: \tmp\native_separators.c -// HTML-INDEX: >tmp\native_separators.c +// HTML-INDEX: >native_separators.c // HTML:
\tmp\native_separators.c (Binary: native_separators.covmapping)
int main() {} Index: test/tools/llvm-cov/showLineExecutionCounts.cpp =================================================================== --- test/tools/llvm-cov/showLineExecutionCounts.cpp +++ test/tools/llvm-cov/showLineExecutionCounts.cpp @@ -89,4 +89,4 @@ // HTML-INDEX: 80.00% (16/20) // HTML-INDEX: // HTML-INDEX: 70.00% (7/10) -// HTML-INDEX: TOTALS +// HTML-INDEX: Totals Index: tools/llvm-cov/SourceCoverageViewHTML.h =================================================================== --- tools/llvm-cov/SourceCoverageViewHTML.h +++ tools/llvm-cov/SourceCoverageViewHTML.h @@ -37,7 +37,10 @@ private: void emitFileSummary(raw_ostream &OS, StringRef SF, const FileCoverageSummary &FCS, - bool IsTotals = false) const; + const StringRef ProjectDir, bool IsTotals = false) const; + + void emitProjectSummary(raw_ostream &OS, StringRef ProjectDir, + const FileCoverageSummary &PCS) const; }; /// \brief A code coverage view which supports html-based rendering. Index: tools/llvm-cov/SourceCoverageViewHTML.cpp =================================================================== --- tools/llvm-cov/SourceCoverageViewHTML.cpp +++ tools/llvm-cov/SourceCoverageViewHTML.cpp @@ -287,63 +287,142 @@ static void emitColumnLabelsForIndex(raw_ostream &OS) { SmallVector Columns; Columns.emplace_back(tag("td", "Filename", "column-entry-left")); + Columns.emplace_back(tag("td", "File Directory", "column-entry-left")); for (const char *Label : {"Function Coverage", "Line Coverage", "Region Coverage"}) Columns.emplace_back(tag("td", Label, "column-entry")); OS << tag("tr", join(Columns.begin(), Columns.end(), "")); } +// Return the relative path from the project directory (\p ProjectDir) to the +// file (\p Filename). +// \code +// Filename: /output/dir1/dir2/dir3/test.cpp +// Dir: /output/dir1 +// return string: /dir2/dir3/test.cpp +// \endcode +static std::string getFileDir(const StringRef Filename, + const StringRef ProjectDir) { + assert(Filename.substr(0, ProjectDir.size()) == ProjectDir && + "Filename does not start with ProjectDir."); + SmallString<16> FileRelativeDir(Filename); + sys::path::remove_filename(FileRelativeDir); + if (FileRelativeDir.size() > ProjectDir.size()) + return FileRelativeDir.str().substr(ProjectDir.size()); + return sys::path::get_separator().data(); +} + +// Return the common path from the list of FilePaths (\p Files). +// \code +// FilePath: /output/dir1/test.cpp +// /output/dir1/dir2/dir3/test1.cpp +// return string: /output/dir1 +// \endcode +static std::string commonPath(const std::vector &Files) { + assert(!Files.empty() && "Source file vector is empty"); + std::string CompareString = Files[0]; + int MaxCommonPath = CompareString.size(); + for (auto FI = ++Files.begin(), End = Files.end(); FI != End; ++FI) { + auto MismatchPair = + std::mismatch(CompareString.begin(), CompareString.end(), FI->begin()); + if ((MismatchPair.first - CompareString.begin()) < MaxCommonPath) + MaxCommonPath = MismatchPair.first - CompareString.begin() - 1; + } + // If there is no common path, return an empty string. + if (MaxCommonPath <= 0) + return std::string(); + std::string::size_type found = + CompareString.rfind(sys::path::get_separator().data(), MaxCommonPath); + return CompareString.substr(0, found); +} + +/// Get the HTML for a coverage triple. +static std::string getCoverageTriple(unsigned Hit, unsigned Total, float Pctg) { + std::string S; + raw_string_ostream RSO{S}; + RSO << format("%*.2f", 7, Pctg) << "% (" << Hit << '/' << Total << ')'; + const char *CellClass = "column-entry-yellow"; + if (Pctg < 80.0) + CellClass = "column-entry-red"; + else if (Hit == Total) + CellClass = "column-entry-green"; + return tag("td", tag("pre", RSO.str()), CellClass); +} + /// Render a file coverage summary (\p FCS) in a table row. If \p IsTotals is /// false, link the summary to \p SF. void CoveragePrinterHTML::emitFileSummary(raw_ostream &OS, StringRef SF, const FileCoverageSummary &FCS, + const StringRef ProjectDir, bool IsTotals) const { - SmallVector Columns; - - // Format a coverage triple and add the result to the list of columns. - auto AddCoverageTripleToColumn = [&Columns](unsigned Hit, unsigned Total, - float Pctg) { - std::string S; - { - raw_string_ostream RSO{S}; - RSO << format("%*.2f", 7, Pctg) << "% (" << Hit << '/' << Total << ')'; - } - const char *CellClass = "column-entry-yellow"; - if (Pctg < 80.0) - CellClass = "column-entry-red"; - else if (Hit == Total) - CellClass = "column-entry-green"; - Columns.emplace_back(tag("td", tag("pre", S), CellClass)); - }; + SmallVector Columns; // Simplify the display file path, and wrap it in a link if requested. std::string Filename; - SmallString<128> LinkTextStr(sys::path::relative_path(FCS.Name)); - sys::path::remove_dots(LinkTextStr, /*remove_dot_dots=*/true); - sys::path::native(LinkTextStr); - std::string LinkText = escape(LinkTextStr, Opts); + std::string FileDir; + std::string LinkText = escape(sys::path::filename(SF), Opts); if (IsTotals) { + FileDir = std::string(); Filename = LinkText; } else { + FileDir = getFileDir(SF, ProjectDir); std::string LinkTarget = escape(getOutputPath(SF, "html", /*InToplevel=*/false), Opts); Filename = a(LinkTarget, LinkText); } Columns.emplace_back(tag("td", tag("pre", Filename))); - AddCoverageTripleToColumn(FCS.FunctionCoverage.Executed, - FCS.FunctionCoverage.NumFunctions, - FCS.FunctionCoverage.getPercentCovered()); - AddCoverageTripleToColumn( + Columns.emplace_back(tag("td", tag("pre", FileDir))); + Columns.emplace_back(getCoverageTriple( + FCS.FunctionCoverage.Executed, FCS.FunctionCoverage.NumFunctions, + FCS.FunctionCoverage.getPercentCovered())); + Columns.emplace_back(getCoverageTriple( FCS.LineCoverage.NumLines - FCS.LineCoverage.NotCovered, - FCS.LineCoverage.NumLines, FCS.LineCoverage.getPercentCovered()); - AddCoverageTripleToColumn( + FCS.LineCoverage.NumLines, FCS.LineCoverage.getPercentCovered())); + Columns.emplace_back(getCoverageTriple( FCS.RegionCoverage.NumRegions - FCS.RegionCoverage.NotCovered, - FCS.RegionCoverage.NumRegions, FCS.RegionCoverage.getPercentCovered()); - + FCS.RegionCoverage.NumRegions, FCS.RegionCoverage.getPercentCovered())); OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row"); } +/// Render the project coverage summary (\p PCS) and the project directory (\p +/// ProjectDir) above the index table. +void CoveragePrinterHTML::emitProjectSummary( + raw_ostream &OS, StringRef ProjectDir, + const FileCoverageSummary &PCS) const { + + OS << "" + << ""; + + // Add a coverage row to the coverage summary table. + auto AddCoverageSummaryRow = [&OS](StringRef CoverageTitle, + StringRef CoverageValue) { + OS << tag("tr", tag("td", tag("pre", CoverageTitle)) + CoverageValue.str()); + }; + + AddCoverageSummaryRow("Project directory:", + tag("td", tag("pre", ProjectDir))); + AddCoverageSummaryRow( + "Function coverage:", + getCoverageTriple(PCS.FunctionCoverage.Executed, + PCS.FunctionCoverage.NumFunctions, + PCS.FunctionCoverage.getPercentCovered())); + AddCoverageSummaryRow( + "Line coverage:", + getCoverageTriple(PCS.LineCoverage.NumLines - PCS.LineCoverage.NotCovered, + PCS.LineCoverage.NumLines, + PCS.LineCoverage.getPercentCovered())); + AddCoverageSummaryRow( + "Region coverage:", + getCoverageTriple(PCS.RegionCoverage.NumRegions - + PCS.RegionCoverage.NotCovered, + PCS.RegionCoverage.NumRegions, + PCS.RegionCoverage.getPercentCovered())); + + OS << "" + << "
" << "
"; +} + Error CoveragePrinterHTML::createIndexFile( ArrayRef SourceFiles, const coverage::CoverageMapping &Coverage) { @@ -372,15 +451,26 @@ if (Opts.hasCreatedTime()) OSRef << tag(CreatedTimeTag, escape(Opts.CreatedTimeStr, Opts)); - // Emit a table containing links to reports for each file in the covmapping. + // Emit a table containing the project coverage summary. CoverageReport Report(Opts, Coverage); - OSRef << BeginCenteredDiv << BeginTable; - emitColumnLabelsForIndex(OSRef); FileCoverageSummary Totals("TOTALS"); auto FileReports = Report.prepareFileReports(Totals, SourceFiles); + std::vector NativeFiles; + for (const auto &SourceFile : SourceFiles) { + SmallString<256> NativeFile(SourceFile); + sys::path::remove_dots(NativeFile, /* remove_dot_dot */ true); + sys::path::native(NativeFile); + NativeFiles.push_back(NativeFile.c_str()); + } + std::string ProjectDir = commonPath(NativeFiles); + emitProjectSummary(OSRef, ProjectDir, Totals); + + // Emit a table containing links to reports for each file in the covmapping. + OSRef << BeginCenteredDiv << BeginTable; + emitColumnLabelsForIndex(OSRef); for (unsigned I = 0, E = FileReports.size(); I < E; ++I) - emitFileSummary(OSRef, SourceFiles[I], FileReports[I]); - emitFileSummary(OSRef, "Totals", Totals, /*IsTotals=*/true); + emitFileSummary(OSRef, NativeFiles[I], FileReports[I], ProjectDir); + emitFileSummary(OSRef, "Totals", Totals, ProjectDir, /*IsTotals=*/true); OSRef << EndTable << EndCenteredDiv << tag("h5", escape(Opts.getLLVMVersionString(), Opts)); emitEpilog(OSRef);