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 = - "" + "" "" ""; @@ -318,15 +321,72 @@ OS << tag("tr", join(Columns.begin(), Columns.end(), "")); } -std::string -CoveragePrinterHTML::buildLinkToFile(StringRef SF, - const FileCoverageSummary &FCS) const { +void printTree(CoverageTreeNode *N) { + LLVM_DEBUG(dbgs() << "\nParent: "); + LLVM_DEBUG(dbgs() << N->Name); + for (auto *I : N->Children) { + printTree(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]); + 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 +394,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 +421,8 @@ std::string Filename; if (IsTotals) { Filename = std::string(SF); + } else if (IsDir) { + Filename = buildLinkToFile(SF, FCS, true); } else { Filename = buildLinkToFile(SF, FCS); } @@ -391,77 +453,106 @@ OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row"); } +Error CoveragePrinterHTML::createIndexFile( + CoverageTreeNode *Root, const CoverageFiltersMatchAll &Filters) { + { + // Emit a file index along with some coverage statistics. + 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(Root->FCS.Name)); + + // Emit some basic information about the coverage report. + if (Opts.hasProjectTitle()) + OSRef << tag(ProjectTitleTag, escape(Opts.ProjectTitle, Opts)); + OSRef << tag(ReportTitleTag, "Coverage Report"); + if (Opts.hasCreatedTime()) + OSRef << tag(CreatedTimeTag, escape(Opts.CreatedTimeStr, Opts)); + + // Emit a link to some documentation. + OSRef << tag("p", "Click " + + a("http://clang.llvm.org/docs/" + "SourceBasedCodeCoverage.html#interpreting-reports", + "here") + + " for information about interpreting this report."); + + // 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); + bool EmptyFiles = false; + + 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", Root->FCS, /*IsTotals=*/true); + OSRef << EndTable << EndCenteredDiv; + + // Emit links to files which don't contain any functions. These are normally + // not very useful, but could be relevant for code which abuses the + // preprocessor. + if (EmptyFiles && Filters.empty()) { + OSRef << tag("p", "Files which contain no functions. (These " + "files contain code pulled into other files " + "by the preprocessor.)\n"); + OSRef << BeginCenteredDiv << BeginTable; + 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; + } + + OSRef << tag("h5", escape(Opts.getLLVMVersionString(), Opts)); + emitEpilog(OSRef); + } + 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; + } + } + } + return Error::success(); +} + Error CoveragePrinterHTML::createIndexFile( ArrayRef SourceFiles, const CoverageMapping &Coverage, const CoverageFiltersMatchAll &Filters) { // Emit the default stylesheet. - auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true); + auto CSSOrErr = createOutputStream("style", "css", true); if (Error E = CSSOrErr.takeError()) return E; OwnedStream CSS = std::move(CSSOrErr.get()); CSS->operator<<(CSSForCoverage); - // Emit a file index along with some coverage statistics. - auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true); - if (Error E = OSOrErr.takeError()) + // Traverse the directory tree and emit the index files + CoverageTreeNode *IndexTree = + buildCoverageTree(SourceFiles, Coverage, Filters); + if (Error E = createIndexFile(IndexTree, Filters)) 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("")); - - // Emit some basic information about the coverage report. - if (Opts.hasProjectTitle()) - OSRef << tag(ProjectTitleTag, escape(Opts.ProjectTitle, Opts)); - OSRef << tag(ReportTitleTag, "Coverage Report"); - if (Opts.hasCreatedTime()) - OSRef << tag(CreatedTimeTag, escape(Opts.CreatedTimeStr, Opts)); - - // Emit a link to some documentation. - OSRef << tag("p", "Click " + - a("http://clang.llvm.org/docs/" - "SourceBasedCodeCoverage.html#interpreting-reports", - "here") + - " for information about interpreting this report."); - - // 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); - 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; - } - emitFileSummary(OSRef, "Totals", Totals, /*IsTotals=*/true); - OSRef << EndTable << EndCenteredDiv; - - // Emit links to files which don't contain any functions. These are normally - // not very useful, but could be relevant for code which abuses the - // preprocessor. - if (EmptyFiles && Filters.empty()) { - OSRef << tag("p", "Files which contain no functions. (These " - "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]); - OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n'; - } - OSRef << EndTable << EndCenteredDiv; - } - - OSRef << tag("h5", escape(Opts.getLLVMVersionString(), Opts)); - emitEpilog(OSRef); - return Error::success(); } @@ -593,8 +684,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)