Index: test/tools/llvm-cov/showProjectSummary.cpp =================================================================== --- test/tools/llvm-cov/showProjectSummary.cpp +++ test/tools/llvm-cov/showProjectSummary.cpp @@ -24,7 +24,7 @@ // Test html output. // RUN: llvm-cov show %S/Inputs/showProjectSummary.covmapping -format=html -o %t.dir -instr-profile %t.profdata -filename-equivalence %s -// RUN: FileCheck -check-prefixes=HTML,HTML-FILE,HTML-HEADER,HTML-UNCOVEREDLINE -input-file %t.dir/coverage/tmp/showProjectSummary.cpp.html %s +// RUN: FileCheck -check-prefixes=HTML,HTML-FILE,HTML-HEADER,HTML-UNCOVEREDLINE,HTML_NAVIGATION -input-file %t.dir/coverage/tmp/showProjectSummary.cpp.html %s // RUN: llvm-cov show %S/Inputs/showProjectSummary.covmapping -format=html -o %t.dir -instr-profile %t.profdata -project-title "Test Suite" -filename-equivalence %s // RUN: FileCheck -check-prefixes=HTML-TITLE,HTML,HTML-FILE,HTML-HEADER,HTML-UNCOVEREDLINE -input-file %t.dir/coverage/tmp/showProjectSummary.cpp.html %s // RUN: FileCheck -check-prefixes=HTML-TITLE,HTML -input-file %t.dir/index.html %s @@ -36,6 +36,7 @@ // HTML: Code Coverage Report // HTML:
// HTML: Created: +// HTML_NAVIGATION: Index | Prev | Next
// HTML-FILE:
Source: {{.*}}showProjectSummary.cpp (Binary: showProjectSummary.covmapping)
// HTML-FUNCTION:
main
// HTML-UNCOVEREDLINE: Go to first unexecuted line Index: tools/llvm-cov/CodeCoverage.cpp =================================================================== --- tools/llvm-cov/CodeCoverage.cpp +++ tools/llvm-cov/CodeCoverage.cpp @@ -707,7 +707,8 @@ auto OS = std::move(OSOrErr.get()); View->print(*OS.get(), /*Wholefile=*/true, - /*ShowSourceName=*/ShowFilenames); + /*ShowSourceName=*/ShowFilenames, /*ViewDepth=*/0, + SourceFiles); Printer->closeViewFile(std::move(OS)); }); } Index: tools/llvm-cov/CoverageViewOptions.h =================================================================== --- tools/llvm-cov/CoverageViewOptions.h +++ tools/llvm-cov/CoverageViewOptions.h @@ -56,6 +56,9 @@ /// \brief Check if the created time of the profile data file is available. bool hasCreatedTime() const { return !CreatedTimeStr.empty(); } + + /// \brief Return the sub-directory name for file coverage reports. + static StringRef getCoverageDir() { return "coverage"; } }; } Index: tools/llvm-cov/SourceCoverageView.h =================================================================== --- tools/llvm-cov/SourceCoverageView.h +++ tools/llvm-cov/SourceCoverageView.h @@ -122,9 +122,6 @@ Expected createOutputStream(StringRef Path, StringRef Extension, bool InToplevel); - /// \brief Return the sub-directory name for file coverage reports. - static StringRef getCoverageDir() { return "coverage"; } - public: static std::unique_ptr create(const CoverageViewOptions &Opts); @@ -242,7 +239,8 @@ /// \brief Render the project title, the report title \p CellText and the /// created time for the view. - virtual void renderCellInTitle(raw_ostream &OS, StringRef CellText) = 0; + virtual void renderCellInTitle(raw_ostream &OS, StringRef CellText, + ArrayRef SourceFiles) = 0; /// \brief Render the table header for a given source file virtual void renderTableHeader(raw_ostream &OS, unsigned IndentLevel = 0) = 0; @@ -293,7 +291,8 @@ /// \brief Print the code coverage information for a specific portion of a /// source file to the output stream. void print(raw_ostream &OS, bool WholeFile, bool ShowSourceName, - unsigned ViewDepth = 0); + unsigned ViewDepth = 0, + ArrayRef SourceFiles = ArrayRef()); }; } // namespace llvm Index: tools/llvm-cov/SourceCoverageView.cpp =================================================================== --- tools/llvm-cov/SourceCoverageView.cpp +++ tools/llvm-cov/SourceCoverageView.cpp @@ -38,7 +38,7 @@ FullPath.append(Opts.ShowOutputDirectory); if (!InToplevel) - sys::path::append(FullPath, getCoverageDir()); + sys::path::append(FullPath, Opts.getCoverageDir()); SmallString<256> ParentPath = sys::path::parent_path(Path); sys::path::remove_dots(ParentPath, /*remove_dot_dots=*/true); @@ -166,9 +166,10 @@ } void SourceCoverageView::print(raw_ostream &OS, bool WholeFile, - bool ShowSourceName, unsigned ViewDepth) { + bool ShowSourceName, unsigned ViewDepth, + ArrayRef SourceFiles) { if (WholeFile) - renderCellInTitle(OS, "Code Coverage Report"); + renderCellInTitle(OS, "Code Coverage Report", SourceFiles); renderViewHeader(OS); Index: tools/llvm-cov/SourceCoverageViewHTML.h =================================================================== --- tools/llvm-cov/SourceCoverageViewHTML.h +++ tools/llvm-cov/SourceCoverageViewHTML.h @@ -71,10 +71,40 @@ void renderRegionMarkers(raw_ostream &OS, CoverageSegmentArray Segments, unsigned ViewDepth) override; - void renderCellInTitle(raw_ostream &OS, StringRef CellText) override; + void renderCellInTitle(raw_ostream &OS, StringRef CellText, + ArrayRef SourceFiles) override; void renderTableHeader(raw_ostream &OS, unsigned IndentLevel) override; + /// \brief Return a relative filename from \p FirstFilename to \p + /// SecondFileName. + /// + /// Typical usage: + /// \code + /// case 1: + /// first input file1: /coverage/dir1/test.cpp + /// seconds input file2: /coverage/dir1/dir2/test1.cpp + /// return string: ../../../coverage/dir1/dir2/test1.cpp.html + /// case 2: + /// first input file1: /output/dir1/test.cpp + /// seconds input file2: /output/dir1/test1.cpp + /// return string: ../../../output/dir1/test1.cpp.html + /// case 3: + /// first input file1: /output/dir1/dir2/test.cpp + /// seconds input file2: /output/dir1/test1.cpp + /// return string: ../../../../output/dir1/test1.cpp.html + /// case 4: + /// first input file1: /coverage/dir1/test.cpp + /// seconds input file2: index.html + /// return string: ../../../index.html + /// \endcode + std::string getRelativePath(const StringRef FirstFilename, + const StringRef SecondFileName); + + /// \brief Render the page navigation in the project summary. + void renderPageNavigation(raw_ostream &OS, StringRef SourceFileName, + ArrayRef SourceFiles); + public: SourceCoverageViewHTML(StringRef SourceName, const MemoryBuffer &File, const CoverageViewOptions &Options, Index: tools/llvm-cov/SourceCoverageViewHTML.cpp =================================================================== --- tools/llvm-cov/SourceCoverageViewHTML.cpp +++ tools/llvm-cov/SourceCoverageViewHTML.cpp @@ -544,8 +544,8 @@ OS << EndExpansionDiv; } -void SourceCoverageViewHTML::renderCellInTitle(raw_ostream &OS, - StringRef CellText) { +void SourceCoverageViewHTML::renderCellInTitle( + raw_ostream &OS, StringRef CellText, ArrayRef SourceFiles) { if (getOptions().hasProjectTitle()) OS << BeginProjectTitleDiv << tag("span", escape(getOptions().ProjectTitle, getOptions())) @@ -558,6 +558,9 @@ OS << BeginCreatedTimeDiv << tag("span", escape(getOptions().CreatedTimeStr, getOptions())) << EndCreatedTimeDiv; + + if (getOptions().hasOutputDirectory()) + renderPageNavigation(OS, getSourceName(), SourceFiles); } void SourceCoverageViewHTML::renderTableHeader(raw_ostream &OS, @@ -568,3 +571,62 @@ << tag("td", tag("span", tag("pre", escape("Source", getOptions())))); renderLineSuffix(OS, ViewDepth); } + +std::string +SourceCoverageViewHTML::getRelativePath(const StringRef FirstFilename, + const StringRef SecondFileName) { + // Resolve the dots in the file paths. + SmallString<256> FileOne(FirstFilename); + sys::path::remove_dots(FileOne, /* remove_dot_dot */ true); + SmallString<256> FileTwo(SecondFileName); + sys::path::remove_dots(FileTwo, /* remove_dot_dot */ true); + // HTML uses '/' as the separator +#ifdef LLVM_ON_WIN32 + std::replace(FileTwo.begin(), FileTwo.end(), '\\', '/'); +#endif + std::string RelativePath; + for (auto PathChar : FileOne.str()) + if (sys::path::is_separator(PathChar)) + RelativePath += "../"; + if (SecondFileName == "index.html") + RelativePath += SecondFileName.str(); + else { + RelativePath += getOptions().getCoverageDir().str() + '/'; + RelativePath += sys::path::relative_path(FileTwo).str() + ".html"; + } + return RelativePath; +} + +void SourceCoverageViewHTML::renderPageNavigation( + raw_ostream &OS, StringRef SourceFileName, + ArrayRef SourceFiles) { + std::string RelativePathToIndex = + getRelativePath(SourceFileName, "index.html"); + StringRef PrevSourceFile; + StringRef NextSourceFile; + for (ArrayRef::iterator I = SourceFiles.begin(), + E = SourceFiles.end(); + I != E; ++I) { + if (SourceFileName == (*I)) { + if (I != SourceFiles.begin()) + PrevSourceFile = (*(I - 1)); + if ((I + 1) != E) + NextSourceFile = (*(I + 1)); + break; + } + if ((I + 1) == E) + assert("Source file doesn't find"); + } + + std::string RelativePathToPrev; + if (!PrevSourceFile.empty()) + RelativePathToPrev = getRelativePath(SourceFileName, PrevSourceFile); + std::string RelativePathToNext; + if (!NextSourceFile.empty()) + RelativePathToNext = getRelativePath(SourceFileName, NextSourceFile); + OS << a(RelativePathToIndex, "Index") << " | "; + OS << (RelativePathToPrev.empty() ? "Prev" : a(RelativePathToPrev, "Prev")) + << " | "; + OS << (RelativePathToNext.empty() ? "Next" : a(RelativePathToNext, "Next")) + << LineBreak; +} Index: tools/llvm-cov/SourceCoverageViewText.h =================================================================== --- tools/llvm-cov/SourceCoverageViewText.h +++ tools/llvm-cov/SourceCoverageViewText.h @@ -71,7 +71,8 @@ void renderRegionMarkers(raw_ostream &OS, CoverageSegmentArray Segments, unsigned ViewDepth) override; - void renderCellInTitle(raw_ostream &OS, StringRef CellText) override; + void renderCellInTitle(raw_ostream &OS, StringRef CellText, + ArrayRef SourceFiles) override; void renderTableHeader(raw_ostream &OS, unsigned IndentLevel) override; Index: tools/llvm-cov/SourceCoverageViewText.cpp =================================================================== --- tools/llvm-cov/SourceCoverageViewText.cpp +++ tools/llvm-cov/SourceCoverageViewText.cpp @@ -213,8 +213,8 @@ ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, ViewDepth); } -void SourceCoverageViewText::renderCellInTitle(raw_ostream &OS, - StringRef CellText) { +void SourceCoverageViewText::renderCellInTitle( + raw_ostream &OS, StringRef CellText, ArrayRef SourceFiles) { if (getOptions().hasProjectTitle()) getOptions().colored_ostream(OS, raw_ostream::CYAN) << getOptions().ProjectTitle << "\n";