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,7 @@ #define LLVM_COV_SOURCECOVERAGEVIEWHTML_H #include "SourceCoverageView.h" +#include "llvm/ADT/ArrayRef.h" namespace llvm { @@ -21,6 +22,15 @@ struct FileCoverageSummary; +struct CoverageTreeNode { + StringRef Name; + std::vector Children; + FileCoverageSummary FCS; + + CoverageTreeNode(StringRef Name, FileCoverageSummary FCS) + : Name(Name), FCS(FCS) {} +}; + /// A coverage printer for html output. class CoveragePrinterHTML : public CoveragePrinter { public: @@ -33,10 +43,16 @@ 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; 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,13 +10,16 @@ /// //===----------------------------------------------------------------------===// -#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/Format.h" #include "llvm/Support/Path.h" +#include using namespace llvm; @@ -65,7 +68,7 @@ } const char *BeginHeader = - "" + "" "" ""; @@ -318,6 +321,58 @@ OS << tag("tr", join(Columns.begin(), Columns.end(), "")); } +void printTree(CoverageTreeNode *T) { + std::cout << T->Name.str() << "\n"; + for (unsigned I = 0, E = T->Children.size(); I < E; I++) + printTree(&T->Children[I]); +} + +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]); + llvm::CoverageTreeNode ParentDir = + Dirs.try_emplace( + DirPath, + CoverageTreeNode{DirPath, FileCoverageSummary{DirPath}}) + .first->getValue(); + ParentDir.Children.emplace_back( + CoverageTreeNode{SourceFiles[I], FileReports[I]}); + ParentDir.FCS += FileReports[I]; + DirPath = llvm::sys::path::parent_path(DirPath); + // Maintain a reference to the current directory + llvm::CoverageTreeNode CurrentDir = ParentDir; + while (!DirPath.equals("/")) { + std::cout << "DEBUG: " << DirPath.str() << "\n"; + ParentDir = Dirs.try_emplace(DirPath, + CoverageTreeNode{ + DirPath, FileCoverageSummary{DirPath}}) + .first->getValue(); + ParentDir.Children.emplace_back(CurrentDir); + ParentDir.FCS += FileReports[I]; + CurrentDir = ParentDir; + DirPath = llvm::sys::path::parent_path(DirPath); + } + ParentDir = Dirs.find("/")->getValue(); + ParentDir.Children.emplace_back(CurrentDir); + } + std::cout << "PRINTING TREE: \n"; + printTree(Root); + + return Root; +} + std::string CoveragePrinterHTML::buildLinkToFile(StringRef SF, const FileCoverageSummary &FCS) const { @@ -392,8 +447,7 @@ } Error CoveragePrinterHTML::createIndexFile( - ArrayRef SourceFiles, const CoverageMapping &Coverage, - const CoverageFiltersMatchAll &Filters) { + CoverageTreeNode *Root, const CoverageFiltersMatchAll &Filters) { // Emit the default stylesheet. auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true); if (Error E = CSSOrErr.takeError()) @@ -430,17 +484,20 @@ // 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]); + + for (unsigned I = 0, E = Root->Children.size(); I < E; I++) { + if (Root->Children[I].Children.size()) { + if (Error E = createIndexFile(&Root->Children[I], Filters)) { + return E; + } + } + if (Root->Children[I].FCS.FunctionCoverage.getNumFunctions()) + emitFileSummary(OSRef, Root->Children[I].Name, Root->Children[I].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 +508,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 (unsigned I = 0, E = Root->Children.size(); I < E; I++) { + if (!Root->Children[I].FCS.FunctionCoverage.getNumFunctions()) { + std::string Link = + buildLinkToFile(Root->Children[I].Name, Root->Children[I].FCS); OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n'; } + } OSRef << EndTable << EndCenteredDiv; } @@ -465,6 +524,16 @@ return Error::success(); } +Error CoveragePrinterHTML::createIndexFile( + ArrayRef SourceFiles, const CoverageMapping &Coverage, + const CoverageFiltersMatchAll &Filters) { + 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 +662,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)