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 @@ -14,6 +14,8 @@ #define LLVM_COV_SOURCECOVERAGEVIEWHTML_H #include "SourceCoverageView.h" +#include "llvm/ADT/ArrayRef.h" +#include namespace llvm { @@ -21,6 +23,15 @@ struct FileCoverageSummary; +struct CoverageTreeNode { + StringRef Name; + std::unordered_set Children; + FileCoverageSummary FCS; + + CoverageTreeNode(StringRef Name, FileCoverageSummary FCS) + : Name(Name), FCS(FCS) {} +}; + /// A coverage printer for html output. class CoveragePrinterHTML : public CoveragePrinter { public: @@ -33,15 +44,21 @@ const coverage::CoverageMapping &Coverage, const CoverageFiltersMatchAll &Filters) override; + Error createIndexFile(CoverageTreeNode *Root, + const CoverageFiltersMatchAll &Filters); + CoveragePrinterHTML(const CoverageViewOptions &Opts) : CoveragePrinter(Opts) {} private: + CoverageTreeNode *buildCoverageTree(ArrayRef &SourceFiles, + const coverage::CoverageMapping &Coverage, + const CoverageFiltersMatchAll &Filters); 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 InTopLevel = false) 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,11 +10,14 @@ /// //===----------------------------------------------------------------------===// -#include "CoverageReport.h" #include "SourceCoverageViewHTML.h" +#include "CoverageReport.h" +#include "CoverageSummaryInfo.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Debug.h" #include "llvm/Support/Format.h" #include "llvm/Support/Path.h" @@ -65,7 +68,7 @@ } const char *BeginHeader = - "" + "" "" ""; @@ -301,6 +304,16 @@ emitEpilog(*OS.get()); } +void printTree(CoverageTreeNode *Root) { + LLVM_DEBUG(dbgs() << "\nName: " << Root->Name); + LLVM_DEBUG(dbgs() << "\nFCS Name: " << Root->FCS.Name); + + for (auto Child = Root->Children.begin(), E = Root->Children.end(); + Child != E; Child++) { + printTree((*Child)); + } +} + /// Emit column labels for the table in the index. static void emitColumnLabelsForIndex(raw_ostream &OS, const CoverageViewOptions &Opts) { @@ -318,15 +331,64 @@ OS << tag("tr", join(Columns.begin(), Columns.end(), "")); } -std::string -CoveragePrinterHTML::buildLinkToFile(StringRef SF, - const FileCoverageSummary &FCS) const { +CoverageTreeNode *CoveragePrinterHTML::buildCoverageTree( + ArrayRef &SourceFiles, + const coverage::CoverageMapping &Coverage, + const CoverageFiltersMatchAll &Filters) { + // Generate file reports + FileCoverageSummary Totals("TOTALS"); + auto FileReports = CoverageReport::prepareFileReports( + Coverage, Totals, SourceFiles, Opts, Filters); + StringMap Dirs; + CoverageTreeNode *Root = new CoverageTreeNode{"/", Totals}; + Dirs.insert_or_assign("/", Root); + + // Build the tree + for (unsigned I = 0, E = SourceFiles.size(); I < E; I++) { + StringRef DirPath = llvm::sys::path::parent_path(SourceFiles[I]); + StringRef DirName = llvm::sys::path::filename(DirPath); + StringRef FCSPath = llvm::sys::path::parent_path(FileReports[I].Name); + llvm::CoverageTreeNode *ParentDir = + Dirs.try_emplace( + DirName, + new CoverageTreeNode{DirName, FileCoverageSummary{FCSPath}}) + .first->getValue(); + ParentDir->Children.emplace( + new CoverageTreeNode{SourceFiles[I], FileReports[I]}); + ParentDir->FCS += FileReports[I]; + DirPath = llvm::sys::path::parent_path(DirPath); + DirName = llvm::sys::path::filename(DirPath); + FCSPath = llvm::sys::path::parent_path(FCSPath); + // Maintain a reference to the current directory + llvm::CoverageTreeNode *CurrentDir = ParentDir; + while (!DirName.equals("llvm-project")) { + ParentDir = Dirs.try_emplace(DirName, + new CoverageTreeNode{ + DirName, FileCoverageSummary{FCSPath}}) + .first->getValue(); + ParentDir->Children.emplace(CurrentDir); + ParentDir->FCS += FileReports[I]; + CurrentDir = ParentDir; + DirPath = llvm::sys::path::parent_path(DirPath); + DirName = llvm::sys::path::filename(DirPath); + FCSPath = llvm::sys::path::parent_path(FCSPath); + } + ParentDir = Dirs.find("/")->getValue(); + ParentDir->Children.emplace(CurrentDir); + } + printTree(Root); + + return Root; +} + +std::string CoveragePrinterHTML::buildLinkToFile(StringRef SF, + const FileCoverageSummary &FCS, + bool InTopLevel) 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); + std::string LinkTarget = escape(getOutputPath(SF, "html", InTopLevel), Opts); return a(LinkTarget, LinkText); } @@ -334,7 +396,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,6 +423,8 @@ std::string Filename; if (IsTotals) { Filename = std::string(SF); + } else if (IsDir) { + Filename = buildLinkToFile(SF, FCS, true); } else { Filename = buildLinkToFile(SF, FCS); } @@ -392,25 +456,16 @@ } Error CoveragePrinterHTML::createIndexFile( - ArrayRef SourceFiles, const CoverageMapping &Coverage, - const CoverageFiltersMatchAll &Filters) { - // Emit the default stylesheet. - auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true); - if (Error E = CSSOrErr.takeError()) - return E; - - OwnedStream CSS = std::move(CSSOrErr.get()); - CSS->operator<<(CSSForCoverage); - + CoverageTreeNode *Root, const CoverageFiltersMatchAll &Filters) { // Emit a file index along with some coverage statistics. - auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true); + auto OSOrErr = createOutputStream(Root->FCS.Name.str(), "html", 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("")); + emitPrelude(OSRef, Opts, getPathToStyle(Root->FCS.Name)); // Emit some basic information about the coverage report. if (Opts.hasProjectTitle()) @@ -430,17 +485,25 @@ // 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); bool EmptyFiles = false; - for (unsigned I = 0, E = FileReports.size(); I < E; ++I) { - if (FileReports[I].FunctionCoverage.getNumFunctions()) - emitFileSummary(OSRef, SourceFiles[I], FileReports[I]); - else - EmptyFiles = true; + + for (auto Child = Root->Children.begin(), E = Root->Children.end(); + Child != E; Child++) { + if (!(*Child)->Children.empty()) { + if (Error E = createIndexFile((*Child), Filters)) { + return E; + } + emitFileSummary(OSRef, (*Child)->Name, (*Child)->FCS, + /*IsTotals=*/false, + /*IsDir=*/true); + } else { + if ((*Child)->FCS.FunctionCoverage.getNumFunctions()) + emitFileSummary(OSRef, (*Child)->Name, (*Child)->FCS); + else + EmptyFiles = true; + } } - emitFileSummary(OSRef, "Totals", Totals, /*IsTotals=*/true); + emitFileSummary(OSRef, "Totals", Root->FCS, /*IsTotals=*/true); OSRef << EndTable << EndCenteredDiv; // Emit links to files which don't contain any functions. These are normally @@ -451,11 +514,13 @@ "files contain code pulled into other files " "by the preprocessor.)\n"); OSRef << BeginCenteredDiv << BeginTable; - for (unsigned I = 0, E = FileReports.size(); I < E; ++I) - if (!FileReports[I].FunctionCoverage.getNumFunctions()) { - std::string Link = buildLinkToFile(SourceFiles[I], FileReports[I]); + for (auto Child = Root->Children.begin(), E = Root->Children.end(); + Child != E; Child++) { + if (!(*Child)->FCS.FunctionCoverage.getNumFunctions()) { + std::string Link = buildLinkToFile((*Child)->Name, (*Child)->FCS); OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n'; } + } OSRef << EndTable << EndCenteredDiv; } @@ -465,6 +530,25 @@ return Error::success(); } +Error CoveragePrinterHTML::createIndexFile( + ArrayRef SourceFiles, const CoverageMapping &Coverage, + const CoverageFiltersMatchAll &Filters) { + // Emit the default stylesheet. + auto CSSOrErr = createOutputStream("style", "css", true); + if (Error E = CSSOrErr.takeError()) + return E; + + OwnedStream CSS = std::move(CSSOrErr.get()); + CSS->operator<<(CSSForCoverage); + + // Traverse the directory tree and emit the index files + CoverageTreeNode *IndexTree = + buildCoverageTree(SourceFiles, Coverage, Filters); + if (Error E = createIndexFile(IndexTree, Filters)) + return E; + return Error::success(); +} + void SourceCoverageViewHTML::renderViewHeader(raw_ostream &OS) { OS << BeginCenteredDiv << BeginTable; } @@ -593,8 +677,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)