Index: test/tools/llvm-cov/f1.c =================================================================== --- /dev/null +++ test/tools/llvm-cov/f1.c @@ -0,0 +1,8 @@ +int f1() {} + +// RUN: llvm-profdata merge %S/Inputs/multiple-files.proftext -o %t.profdata +// RUN: llvm-cov show %S/Inputs/multiple-files.covmapping -format=html -o %t.dir -instr-profile %t.profdata -filename-equivalence %S/f1.c %S/f2.c %S/f3.c +// RUN: FileCheck -check-prefixes=HTML -input-file %t.dir/coverage/tmp/coverage/f1.c.html %s +// HTML: Index +// HTML-NOT: >Prev< +// HTML: Next Index: test/tools/llvm-cov/f2.c =================================================================== --- /dev/null +++ test/tools/llvm-cov/f2.c @@ -0,0 +1,8 @@ +int f2() {} + +// RUN: llvm-profdata merge %S/Inputs/multiple-files.proftext -o %t.profdata +// RUN: llvm-cov show %S/Inputs/multiple-files.covmapping -format=html -o %t.dir -instr-profile %t.profdata -filename-equivalence %S/f1.c %S/f2.c %S/f3.c +// RUN: FileCheck -check-prefixes=HTML -input-file %t.dir/coverage/tmp/coverage/a/f2.c.html %s +// HTML: Index +// HTML: Prev +// HTML: Next Index: test/tools/llvm-cov/f3.c =================================================================== --- /dev/null +++ test/tools/llvm-cov/f3.c @@ -0,0 +1,8 @@ +int f3() {} + +// RUN: llvm-profdata merge %S/Inputs/multiple-files.proftext -o %t.profdata +// RUN: llvm-cov show %S/Inputs/multiple-files.covmapping -format=html -o %t.dir -instr-profile %t.profdata -filename-equivalence %S/f1.c %S/f2.c %S/f3.c +// RUN: FileCheck -check-prefixes=HTML -input-file %t.dir/coverage/tmp/coverage/b/f3.c.html %s +// HTML: Index +// HTML: Prev +// HTML-NOT: >Next< Index: test/tools/llvm-cov/showProjectSummary.cpp =================================================================== --- test/tools/llvm-cov/showProjectSummary.cpp +++ test/tools/llvm-cov/showProjectSummary.cpp @@ -27,7 +27,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-FOOTER -input-file %t.dir/coverage/tmp/showProjectSummary.cpp.html %s +// RUN: FileCheck -check-prefixes=HTML,HTML-FILE,HTML-HEADER,HTML-FOOTER,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-FOOTER -input-file %t.dir/coverage/tmp/showProjectSummary.cpp.html %s // RUN: FileCheck -check-prefixes=HTML-TITLE,HTML,HTML-FOOTER -input-file %t.dir/index.html %s @@ -36,6 +36,7 @@ // HTML-TITLE:

Test Suite

// HTML:

Coverage Report

// HTML:

Created:{{.*}}

+// HTML-NAVIGATION: Index // HTML-FILE:
{{.*}}showProjectSummary.cpp (Binary: showProjectSummary.covmapping)
// HTML-FUNCTION:
main
// HTML-HEADER:
Line No.
Index: tools/llvm-cov/CodeCoverage.cpp =================================================================== --- tools/llvm-cov/CodeCoverage.cpp +++ tools/llvm-cov/CodeCoverage.cpp @@ -691,8 +691,9 @@ ThreadCount = std::thread::hardware_concurrency(); ThreadPool Pool(ThreadCount); - for (StringRef SourceFile : SourceFiles) { - Pool.async([this, SourceFile, &Coverage, &Printer, ShowFilenames] { + for (size_t I = 0, E = SourceFiles.size(); I < E; ++I) { + StringRef SourceFile = SourceFiles[I]; + Pool.async([this, SourceFile, &Coverage, &Printer, ShowFilenames, I] { auto View = createSourceFileView(SourceFile, *Coverage); if (!View) { warning("The file '" + SourceFile.str() + "' isn't covered."); @@ -707,7 +708,8 @@ auto OS = std::move(OSOrErr.get()); View->print(*OS.get(), /*Wholefile=*/true, - /*ShowSourceName=*/ShowFilenames); + /*ShowSourceName=*/ShowFilenames, /*ViewDepth=*/0, + SourceFiles, I); Printer->closeViewFile(std::move(OS)); }); } Index: tools/llvm-cov/CoverageViewOptions.h =================================================================== --- tools/llvm-cov/CoverageViewOptions.h +++ tools/llvm-cov/CoverageViewOptions.h @@ -63,6 +63,9 @@ VersionString += LLVM_VERSION_STRING; return VersionString; } + + /// \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) const; - /// \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,9 @@ /// \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, + size_t Index) = 0; /// \brief Render the table header for a given source file. virtual void renderTableHeader(raw_ostream &OS, unsigned FirstUncoveredLineNo, @@ -294,7 +293,9 @@ /// \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(), + size_t Index = 0); }; } // namespace llvm Index: tools/llvm-cov/SourceCoverageView.cpp =================================================================== --- tools/llvm-cov/SourceCoverageView.cpp +++ tools/llvm-cov/SourceCoverageView.cpp @@ -39,7 +39,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); @@ -167,9 +167,10 @@ } void SourceCoverageView::print(raw_ostream &OS, bool WholeFile, - bool ShowSourceName, unsigned ViewDepth) { + bool ShowSourceName, unsigned ViewDepth, + ArrayRef SourceFiles, size_t Index) { if (WholeFile) - renderCellInTitle(OS, "Coverage Report"); + renderCellInTitle(OS, "Coverage Report", SourceFiles, Index); renderViewHeader(OS); Index: tools/llvm-cov/SourceCoverageViewHTML.h =================================================================== --- tools/llvm-cov/SourceCoverageViewHTML.h +++ tools/llvm-cov/SourceCoverageViewHTML.h @@ -78,11 +78,42 @@ 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, + size_t Index) override; void renderTableHeader(raw_ostream &OS, unsigned FirstUncoveredLineNo, 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, ArrayRef SourceFiles, + unsigned Index); + 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 @@ -590,13 +590,18 @@ } void SourceCoverageViewHTML::renderCellInTitle(raw_ostream &OS, - StringRef CellText) { + StringRef CellText, + ArrayRef SourceFiles, + size_t Index) { if (getOptions().hasProjectTitle()) OS << tag(ProjectTitleTag, escape(getOptions().ProjectTitle, getOptions())); OS << tag(ReportTitleTag, escape(CellText, getOptions())); if (getOptions().hasCreatedTime()) OS << tag(CreatedTimeTag, escape(getOptions().CreatedTimeStr, getOptions())); + + if (getOptions().hasOutputDirectory()) + renderPageNavigation(OS, SourceFiles, Index); } void SourceCoverageViewHTML::renderTableHeader(raw_ostream &OS, @@ -618,3 +623,56 @@ << SourceLabel; 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, ArrayRef SourceFiles, + unsigned Index) { + assert(((Index >= 0) && (Index < SourceFiles.size())) && + "Source file doesn't find"); + StringRef SourceFileName = SourceFiles[Index]; + std::string RelativePathToIndex = + getRelativePath(SourceFileName, "index.html"); + StringRef PrevSourceFile; + if (Index > 0) + PrevSourceFile = SourceFiles[Index - 1]; + StringRef NextSourceFile; + if (Index < (SourceFiles.size() - 1)) + NextSourceFile = SourceFiles[Index + 1]; + + std::string RelativePathToPrev; + if (!PrevSourceFile.empty()) + RelativePathToPrev = getRelativePath(SourceFileName, PrevSourceFile); + std::string RelativePathToNext; + if (!NextSourceFile.empty()) + RelativePathToNext = getRelativePath(SourceFileName, NextSourceFile); + OS << a(RelativePathToIndex, "Index"); + if (!RelativePathToPrev.empty()) + OS << " | " << a(RelativePathToPrev, "Prev"); + if (!RelativePathToNext.empty()) + OS << " | " << a(RelativePathToNext, "Next"); +} Index: tools/llvm-cov/SourceCoverageViewText.h =================================================================== --- tools/llvm-cov/SourceCoverageViewText.h +++ tools/llvm-cov/SourceCoverageViewText.h @@ -71,7 +71,9 @@ 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, + size_t Index) override; void renderTableHeader(raw_ostream &OS, unsigned FirstUncoveredLineNo, unsigned IndentLevel) override; Index: tools/llvm-cov/SourceCoverageViewText.cpp =================================================================== --- tools/llvm-cov/SourceCoverageViewText.cpp +++ tools/llvm-cov/SourceCoverageViewText.cpp @@ -222,7 +222,9 @@ } void SourceCoverageViewText::renderCellInTitle(raw_ostream &OS, - StringRef CellText) { + StringRef CellText, + ArrayRef SourceFiles, + size_t Index) { if (getOptions().hasProjectTitle()) getOptions().colored_ostream(OS, raw_ostream::CYAN) << getOptions().ProjectTitle << "\n";