diff --git a/llvm/tools/llvm-cov/CoverageReport.h b/llvm/tools/llvm-cov/CoverageReport.h --- a/llvm/tools/llvm-cov/CoverageReport.h +++ b/llvm/tools/llvm-cov/CoverageReport.h @@ -43,13 +43,12 @@ const CoverageViewOptions &Options, const CoverageFilter &Filters = CoverageFiltersMatchAll()); - static void - prepareSingleFileReport(const StringRef Filename, - const coverage::CoverageMapping *Coverage, - const CoverageViewOptions &Options, - const unsigned LCP, - FileCoverageSummary *FileReport, - const CoverageFilter *Filters); + static void prepareSingleFileReport(const StringRef Filename, + const coverage::CoverageMapping *Coverage, + const CoverageViewOptions &Options, + const unsigned LCP, + FileCoverageSummary *FileReport, + const CoverageFilter *Filters); /// Render file reports for every unique file in the coverage mapping. void renderFileReports(raw_ostream &OS, @@ -62,6 +61,10 @@ /// in \p Filters. void renderFileReports(raw_ostream &OS, ArrayRef Files, const CoverageFiltersMatchAll &Filters) const; + + /// Determine the length of the longest redundant prefix of the paths in + /// \p Paths. + static unsigned getRedundantPrefixLen(ArrayRef Paths); }; } // end namespace llvm diff --git a/llvm/tools/llvm-cov/CoverageReport.cpp b/llvm/tools/llvm-cov/CoverageReport.cpp --- a/llvm/tools/llvm-cov/CoverageReport.cpp +++ b/llvm/tools/llvm-cov/CoverageReport.cpp @@ -147,24 +147,6 @@ return NumRedundant; } -/// Determine the length of the longest redundant prefix of the paths in -/// \p Paths. -unsigned getRedundantPrefixLen(ArrayRef Paths) { - // If there's at most one path, no path components are redundant. - if (Paths.size() <= 1) - return 0; - - unsigned PrefixLen = 0; - unsigned NumRedundant = getNumRedundantPathComponents(Paths); - auto Component = sys::path::begin(Paths[0]); - for (unsigned I = 0; I < NumRedundant; ++I) { - auto LastComponent = Component; - ++Component; - PrefixLen += Component - LastComponent; - } - return PrefixLen; -} - } // end anonymous namespace namespace llvm { @@ -229,9 +211,10 @@ OS << format("%*u", FileReportColumns[10], (unsigned)File.LineCoverage.getNumLines()); - Options.colored_ostream(OS, LineCoverageColor) << format( - "%*u", FileReportColumns[11], (unsigned)(File.LineCoverage.getNumLines() - - File.LineCoverage.getCovered())); + Options.colored_ostream(OS, LineCoverageColor) + << format("%*u", FileReportColumns[11], + (unsigned)(File.LineCoverage.getNumLines() - + File.LineCoverage.getCovered())); if (File.LineCoverage.getNumLines()) Options.colored_ostream(OS, LineCoverageColor) << format("%*.2f", FileReportColumns[12] - 1, @@ -260,8 +243,7 @@ } void CoverageReport::render(const FunctionCoverageSummary &Function, - const DemangleCache &DC, - raw_ostream &OS) const { + const DemangleCache &DC, raw_ostream &OS) const { auto FuncCoverageColor = determineCoveragePercentageColor(Function.RegionCoverage); auto LineCoverageColor = @@ -355,8 +337,8 @@ } } -void CoverageReport::prepareSingleFileReport(const StringRef Filename, - const coverage::CoverageMapping *Coverage, +void CoverageReport::prepareSingleFileReport( + const StringRef Filename, const coverage::CoverageMapping *Coverage, const CoverageViewOptions &Options, const unsigned LCP, FileCoverageSummary *FileReport, const CoverageFilter *Filters) { for (const auto &Group : Coverage->getInstantiationGroups(Filename)) { @@ -380,7 +362,7 @@ FileReport->addFunction(GroupSummary); } -} +} // ! It seems LCP is not used? std::vector CoverageReport::prepareFileReports( const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals, @@ -402,8 +384,8 @@ for (StringRef Filename : Files) { FileReports.emplace_back(Filename.drop_front(LCP)); - Pool.async(&CoverageReport::prepareSingleFileReport, Filename, - &Coverage, Options, LCP, &FileReports.back(), &Filters); + Pool.async(&CoverageReport::prepareSingleFileReport, Filename, &Coverage, + Options, LCP, &FileReports.back(), &Filters); } Pool.wait(); @@ -424,8 +406,8 @@ renderFileReports(OS, UniqueSourceFiles); } -void CoverageReport::renderFileReports( - raw_ostream &OS, ArrayRef Files) const { +void CoverageReport::renderFileReports(raw_ostream &OS, + ArrayRef Files) const { renderFileReports(OS, Files, CoverageFiltersMatchAll()); } @@ -488,4 +470,20 @@ render(Totals, OS); } +unsigned CoverageReport::getRedundantPrefixLen(ArrayRef Paths) { + // If there's at most one path, no path components are redundant. + if (Paths.size() <= 1) + return 0; + + unsigned PrefixLen = 0; + unsigned NumRedundant = getNumRedundantPathComponents(Paths); + auto Component = sys::path::begin(Paths[0]); + for (unsigned I = 0; I < NumRedundant; ++I) { + auto LastComponent = Component; + ++Component; + PrefixLen += Component - LastComponent; + } + return PrefixLen; +} + } // end namespace llvm diff --git a/llvm/tools/llvm-cov/SourceCoverageView.h b/llvm/tools/llvm-cov/SourceCoverageView.h --- a/llvm/tools/llvm-cov/SourceCoverageView.h +++ b/llvm/tools/llvm-cov/SourceCoverageView.h @@ -99,7 +99,7 @@ CoveragePrinter(const CoverageViewOptions &Opts) : Opts(Opts) {} /// Return `OutputDir/ToplevelDir/Path.Extension`. If \p InToplevel is - /// false, skip the ToplevelDir component. If \p Relative is false, skip the + /// true, skip the ToplevelDir component. If \p Relative is true, skip the /// OutputDir component. std::string getOutputPath(StringRef Path, StringRef Extension, bool InToplevel, bool Relative = true) const; diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.h b/llvm/tools/llvm-cov/SourceCoverageViewHTML.h --- a/llvm/tools/llvm-cov/SourceCoverageViewHTML.h +++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.h @@ -38,10 +38,17 @@ private: void emitFileSummary(raw_ostream &OS, StringRef SF, - const FileCoverageSummary &FCS, - bool IsTotals = false) const; - std::string buildLinkToFile(StringRef SF, - const FileCoverageSummary &FCS) const; + const FileCoverageSummary &FCS, bool IsTotals = false, + bool IsDir = false) const; + + std::string buildLinkToFile(StringRef SF, const FileCoverageSummary &FCS, + bool IsDir = false) const; + + Error createHierarchicalIndexFile(StringRef LCP, // Longest Common Prefix + ArrayRef SourceFiles, + const coverage::CoverageMapping &Coverage, + const CoverageFiltersMatchAll &Filters, + FileCoverageSummary &Totals) const; }; /// A code coverage view which supports html-based rendering. diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp --- a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp +++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp @@ -10,10 +10,11 @@ /// //===----------------------------------------------------------------------===// -#include "CoverageReport.h" #include "SourceCoverageViewHTML.h" +#include "CoverageReport.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" #include "llvm/Support/Path.h" #include @@ -65,7 +66,7 @@ } const char *BeginHeader = - "" + "" "" ""; @@ -318,15 +319,19 @@ OS << tag("tr", join(Columns.begin(), Columns.end(), "")); } -std::string -CoveragePrinterHTML::buildLinkToFile(StringRef SF, - const FileCoverageSummary &FCS) const { +std::string CoveragePrinterHTML::buildLinkToFile(StringRef SF, + const FileCoverageSummary &FCS, + bool IsDir) const { SmallString<128> LinkTextStr(sys::path::relative_path(FCS.Name)); sys::path::remove_dots(LinkTextStr, /*remove_dot_dot=*/true); sys::path::native(LinkTextStr); std::string LinkText = escape(LinkTextStr, Opts); - std::string LinkTarget = - escape(getOutputPath(SF, "html", /*InToplevel=*/false), Opts); + + if (IsDir) + LinkText.push_back('/'); + auto OutputPath = getOutputPath(SF, "html", /*InToplevel=*/true, true); + std::string LinkTarget = escape(OutputPath, Opts); + return a(LinkTarget, LinkText); } @@ -334,7 +339,7 @@ /// false, link the summary to \p SF. void CoveragePrinterHTML::emitFileSummary(raw_ostream &OS, StringRef SF, const FileCoverageSummary &FCS, - bool IsTotals) const { + bool IsTotals, bool IsDir) const { SmallVector Columns; // Format a coverage triple and add the result to the list of columns. @@ -361,8 +366,10 @@ std::string Filename; if (IsTotals) { Filename = std::string(SF); + } else if (IsDir) { + Filename = buildLinkToFile(std::string(SF) + "index", FCS, true); } else { - Filename = buildLinkToFile(SF, FCS); + Filename = a(FCS.Name.str() + ".html", FCS.Name.str()); } Columns.emplace_back(tag("td", tag("pre", Filename))); @@ -402,20 +409,56 @@ OwnedStream CSS = std::move(CSSOrErr.get()); CSS->operator<<(CSSForCoverage); + // Create hierarchical html files. + StringRef LCP(SourceFiles[0].c_str(), + CoverageReport::getRedundantPrefixLen(SourceFiles)); + FileCoverageSummary Totals("TOTALS"); + if (auto E = createHierarchicalIndexFile(LCP, SourceFiles, Coverage, Filters, + Totals)) + return E; + + // Generate top level index.html. + auto OSOrErr = createOutputStream("index", "html", true); + if (auto E = OSOrErr.takeError()) + return E; + auto OS = std::move(OSOrErr.get()); + + auto TopIndexHtmlPath = + getOutputPath(std::string(LCP) + "index", "html", false, true); + *OS.get() << R"( + + + + + + + )"; // Use redirection for top level index.html. + + return Error::success(); +} + +Error CoveragePrinterHTML::createHierarchicalIndexFile( + StringRef LCP, ArrayRef SourceFiles, + const coverage::CoverageMapping &Coverage, + const CoverageFiltersMatchAll &Filters, FileCoverageSummary &Totals) const { // Emit a file index along with some coverage statistics. - auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true); + auto OSOrErr = createOutputStream(std::string(LCP) + "index", "html", + /*InToplevel=*/false); if (Error E = OSOrErr.takeError()) return E; auto OS = std::move(OSOrErr.get()); raw_ostream &OSRef = *OS.get(); assert(Opts.hasOutputDirectory() && "No output directory for index file"); - emitPrelude(OSRef, Opts, getPathToStyle("")); + auto IndexHtmlPath = getOutputPath(std::string(LCP) + "index", "html", + /*InToplevel=*/false, false); + emitPrelude(OSRef, Opts, getPathToStyle(IndexHtmlPath)); // Emit some basic information about the coverage report. if (Opts.hasProjectTitle()) OSRef << tag(ProjectTitleTag, escape(Opts.ProjectTitle, Opts)); - OSRef << tag(ReportTitleTag, "Coverage Report"); + OSRef << tag(ReportTitleTag, "Coverage Report (" + LCP.str() + ")"); if (Opts.hasCreatedTime()) OSRef << tag(CreatedTimeTag, escape(Opts.CreatedTimeStr, Opts)); @@ -426,17 +469,56 @@ "here") + " for information about interpreting this report."); + // Filter out files in current directory and group other files by there + // diretory. + // TODO Unnecessary string copying here! + std::vector Files; + StringMap> Dirs; + for (auto &&SourceFile : SourceFiles) { + auto Path = SourceFile.substr(LCP.size()); + + auto It = sys::path::begin(Path), End = sys::path::end(Path); + assert(It != End); + + auto Name = *It; + if (++It == End) + Files.push_back(SourceFile); + else + Dirs[Name].push_back(SourceFile); + } + // Emit a table containing links to reports for each file in the covmapping. // Exclude files which don't contain any regions. OSRef << BeginCenteredDiv << BeginTable; emitColumnLabelsForIndex(OSRef, Opts); - FileCoverageSummary Totals("TOTALS"); - auto FileReports = CoverageReport::prepareFileReports( - Coverage, Totals, SourceFiles, Opts, Filters); + auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals, Files, + Opts, Filters); + + // Emit subdirectory first to make them at the top of the list. + for (auto &&Dir : Dirs) { + FileCoverageSummary DirTotals("TOTALS"); + StringRef DirLCP(Dir.second[0].data(), + CoverageReport::getRedundantPrefixLen(Dir.second)); + if (DirLCP.size() == 0) { + Files.push_back(DirLCP.str()); + continue; // In this case, only one file in that directory. + // TODO It's better-looking to list those files above all other files in + // current directory. + } + + if (auto E = createHierarchicalIndexFile(DirLCP, Dir.second, Coverage, + Filters, DirTotals)) + return E; + DirTotals.Name = DirLCP.substr(LCP.size()); + emitFileSummary(OSRef, DirTotals.Name, DirTotals, false, true); + Totals += DirTotals; + } + + // Then emit the files in current directory. bool EmptyFiles = false; for (unsigned I = 0, E = FileReports.size(); I < E; ++I) { if (FileReports[I].FunctionCoverage.getNumFunctions()) - emitFileSummary(OSRef, SourceFiles[I], FileReports[I]); + emitFileSummary(OSRef, Files[I], FileReports[I]); else EmptyFiles = true; } @@ -593,8 +675,9 @@ continue; Snippets[I + 1] = - tag("div", Snippets[I + 1] + tag("span", formatCount(CurSeg->Count), - "tooltip-content"), + tag("div", + Snippets[I + 1] + + tag("span", formatCount(CurSeg->Count), "tooltip-content"), "tooltip"); if (getOptions().Debug)