aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--llvm/docs/CommandGuide/llvm-cov.rst5
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/a0/a1/a2.cc7
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1.h3
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1_1.cc5
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1_2.cc5
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2.h9
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2_1.cc3
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2_2.cc2
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/header.h5
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.cc9
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.covmappingbin0 -> 669 bytes
-rw-r--r--llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.profdatabin0 -> 1032 bytes
-rw-r--r--llvm/test/tools/llvm-cov/directory_coverage.linux.test53
-rw-r--r--llvm/test/tools/llvm-cov/directory_coverage.win.test44
-rw-r--r--llvm/tools/llvm-cov/CodeCoverage.cpp5
-rw-r--r--llvm/tools/llvm-cov/CoverageReport.cpp145
-rw-r--r--llvm/tools/llvm-cov/CoverageReport.h70
-rw-r--r--llvm/tools/llvm-cov/CoverageSummaryInfo.h1
-rw-r--r--llvm/tools/llvm-cov/CoverageViewOptions.h1
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageView.cpp4
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageView.h2
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp391
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewHTML.h20
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewText.cpp65
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewText.h15
-rw-r--r--llvm/tools/llvm-cov/TestingSupport.cpp5
26 files changed, 786 insertions, 88 deletions
diff --git a/llvm/docs/CommandGuide/llvm-cov.rst b/llvm/docs/CommandGuide/llvm-cov.rst
index 792f962..ea70e17 100644
--- a/llvm/docs/CommandGuide/llvm-cov.rst
+++ b/llvm/docs/CommandGuide/llvm-cov.rst
@@ -249,6 +249,11 @@ OPTIONS
line, but show the individual regions if there are multiple on the line.
Defaults to false.
+.. option:: -show-directory-coverage
+
+ Generate an index file in each directory that contains at least one source
+ file with a top level index showing aggregates. Defaults to false.
+
.. option:: -use-color
Enable or disable color output. By default this is autodetected.
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/a0/a1/a2.cc b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/a0/a1/a2.cc
new file mode 100644
index 0000000..8ff5d21
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/a0/a1/a2.cc
@@ -0,0 +1,7 @@
+#include "../../header.h"
+
+template<>
+bool equal<int>(int a, int b)
+{
+ return a == b;
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1.h b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1.h
new file mode 100644
index 0000000..32b872a
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1.h
@@ -0,0 +1,3 @@
+int add(int a, int b);
+
+int sub(int a, int b);
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1_1.cc b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1_1.cc
new file mode 100644
index 0000000..b824723
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1_1.cc
@@ -0,0 +1,5 @@
+#include "b1.h"
+
+int add(int a, int b) {
+ return a + b;
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1_2.cc b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1_2.cc
new file mode 100644
index 0000000..5038ded
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/b0/b1_2.cc
@@ -0,0 +1,5 @@
+#include "b1.h"
+
+int sub(int a, int b) {
+ return a - b;
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2.h b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2.h
new file mode 100644
index 0000000..4837051
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2.h
@@ -0,0 +1,9 @@
+int mul(int a, int b);
+
+int div(int a, int b);
+
+#ifdef DEF
+int div(int a, int b) {
+ return a / b;
+}
+#endif
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2_1.cc b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2_1.cc
new file mode 100644
index 0000000..5557720
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2_1.cc
@@ -0,0 +1,3 @@
+int mul(int a, int b) {
+ return a * b;
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2_2.cc b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2_2.cc
new file mode 100644
index 0000000..82ba2cc
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/c0/c1/c2_2.cc
@@ -0,0 +1,2 @@
+#define DEF
+#include "c2.h"
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/header.h b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/header.h
new file mode 100644
index 0000000..6a4eb2d
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/header.h
@@ -0,0 +1,5 @@
+#include "b0/b1.h"
+#include "c0/c1/c2.h"
+
+template<typename T>
+bool equal(T a, T b);
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.cc b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.cc
new file mode 100644
index 0000000..10d403f
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.cc
@@ -0,0 +1,9 @@
+#include "header.h"
+
+int main() {
+ int a = 3;
+ int b = 4;
+ int c = c = add(a, b);
+ int d = mul(a, b);
+ return equal(a, sub(c, b)) - equal(a, div(d, b));
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.covmapping b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.covmapping
new file mode 100644
index 0000000..58f7aad
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.covmapping
Binary files differ
diff --git a/llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.profdata b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.profdata
new file mode 100644
index 0000000..decd124
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/directory_coverage/main.profdata
Binary files differ
diff --git a/llvm/test/tools/llvm-cov/directory_coverage.linux.test b/llvm/test/tools/llvm-cov/directory_coverage.linux.test
new file mode 100644
index 0000000..5db76c5
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/directory_coverage.linux.test
@@ -0,0 +1,53 @@
+# REQUIRES: system-linux
+# RUN: mkdir -p %t
+
+# RUN: llvm-cov show %S/Inputs/directory_coverage/main.covmapping \
+# RUN: --instr-profile %S/Inputs/directory_coverage/main.profdata \
+# RUN: --path-equivalence=/tmp/directory_coverage,%S/Inputs/directory_coverage \
+# RUN: --format=text --show-directory-coverage -o %t/report-text
+
+# RUN: llvm-cov show %S/Inputs/directory_coverage/main.covmapping \
+# RUN: --instr-profile %S/Inputs/directory_coverage/main.profdata \
+# RUN: --path-equivalence=/tmp/directory_coverage,%S/Inputs/directory_coverage \
+# RUN: --format=html --show-directory-coverage -o %t/report-html
+
+# RUN: FileCheck --input-file %t/report-text/index.txt %s --check-prefix=ROOT
+# RUN: FileCheck --input-file %t/report-text/coverage/tmp/directory_coverage/index.txt %s --check-prefix=ROOT
+# RUN: FileCheck --input-file %t/report-text/coverage/tmp/directory_coverage/b0/index.txt %s --check-prefix=B0
+# RUN: FileCheck --input-file %t/report-text/coverage/tmp/directory_coverage/c0/c1/index.txt %s --check-prefix=C1
+
+# RUN: FileCheck --input-file %t/report-html/index.html %s --check-prefix=HTML-TOP --allow-empty
+# RUN: FileCheck --input-file %t/report-html/coverage/tmp/directory_coverage/index.html %s --check-prefix=ROOT
+# RUN: FileCheck --input-file %t/report-html/coverage/tmp/directory_coverage/b0/index.html %s --check-prefix=B0
+# RUN: FileCheck --input-file %t/report-html/coverage/tmp/directory_coverage/c0/c1/index.html %s --check-prefix=C1
+
+
+
+# HTML-TOP: coverage/index.html
+
+# ROOT: a0/a1/a2.cc
+# ROOT: b0/
+# ROOT-NOT: b1_1.cc
+# ROOT-NOT: b1_2.cc
+# ROOT: c0/c1/
+# ROOT-NOT: c2_1.cc
+# ROOT-NOT: b2_2.cc
+# ROOT: main.cc
+
+# B0: b1_1.cc
+# B0: b1_2.cc
+
+# C1: c2.h
+# C1: c2_1.cc
+
+
+For regenerating the test:
+
+cp -r %S/Inputs/directory_coverage /tmp
+cd /tmp/directory_coverage
+clang -fprofile-instr-generate -fcoverage-mapping -mllvm -enable-name-compression=false \
+ -o main main.cc a0/a1/a2.cc b0/b1_1.cc b0/b1_2.cc c0/c1/c2_1.cc c0/c1/c2_2.cc
+./main
+llvm-cov convert-for-testing main -o main.covmapping
+llvm-profdata merge default.profraw -o main.profdata
+rm main default.profraw
diff --git a/llvm/test/tools/llvm-cov/directory_coverage.win.test b/llvm/test/tools/llvm-cov/directory_coverage.win.test
new file mode 100644
index 0000000..f948bdc
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/directory_coverage.win.test
@@ -0,0 +1,44 @@
+# REQUIRES: system-windows
+# RUN: mkdir -p %t
+
+# RUN: llvm-cov show %S/Inputs/directory_coverage/main.covmapping \
+# RUN: --instr-profile %S/Inputs/directory_coverage/main.profdata \
+# RUN: --path-equivalence=/tmp/directory_coverage,%S/Inputs/directory_coverage \
+# RUN: --format=text --show-directory-coverage -o %t/report-text
+
+# RUN: llvm-cov show %S/Inputs/directory_coverage/main.covmapping \
+# RUN: --instr-profile %S/Inputs/directory_coverage/main.profdata \
+# RUN: --path-equivalence=/tmp/directory_coverage,%S/Inputs/directory_coverage \
+# RUN: --format=html --show-directory-coverage -o %t/report-html
+
+# RUN: FileCheck --input-file %t/report-text/index.txt %s --check-prefix=ROOT
+# RUN: FileCheck --input-file %t/report-text/coverage/tmp/directory_coverage/index.txt %s --check-prefix=ROOT
+# RUN: FileCheck --input-file %t/report-text/coverage/tmp/directory_coverage/b0/index.txt %s --check-prefix=B0
+# RUN: FileCheck --input-file %t/report-text/coverage/tmp/directory_coverage/c0/c1/index.txt %s --check-prefix=C1
+
+# RUN: FileCheck --input-file %t/report-html/index.html %s --check-prefix=HTML-TOP --allow-empty
+# RUN: FileCheck --input-file %t/report-html/coverage/tmp/directory_coverage/index.html %s --check-prefix=ROOT
+# RUN: FileCheck --input-file %t/report-html/coverage/tmp/directory_coverage/b0/index.html %s --check-prefix=B0
+# RUN: FileCheck --input-file %t/report-html/coverage/tmp/directory_coverage/c0/c1/index.html %s --check-prefix=C1
+
+
+
+# HTML-TOP: coverage\index.html
+
+# ROOT: a0\a1\a2.cc
+# ROOT: b0\
+# ROOT-NOT: b1_1.cc
+# ROOT-NOT: b1_2.cc
+# ROOT: c0\c1\
+# ROOT-NOT: c2_1.cc
+# ROOT-NOT: b2_2.cc
+# ROOT: main.cc
+
+# B0: b1_1.cc
+# B0: b1_2.cc
+
+# C1: c2.h
+# C1: c2_1.cc
+
+
+The input of this test is generated on Linux. See 'directory_coverage.linux.test'.
diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp
index 3992dfa..f26db15 100644
--- a/llvm/tools/llvm-cov/CodeCoverage.cpp
+++ b/llvm/tools/llvm-cov/CodeCoverage.cpp
@@ -982,6 +982,10 @@ int CodeCoverageTool::doShow(int argc, const char **argv,
cl::desc("Show function instantiations"),
cl::init(true), cl::cat(ViewCategory));
+ cl::opt<bool> ShowDirectoryCoverage("show-directory-coverage", cl::Optional,
+ cl::desc("Show directory coverage"),
+ cl::cat(ViewCategory));
+
cl::opt<std::string> ShowOutputDirectory(
"output-dir", cl::init(""),
cl::desc("Directory in which coverage information is written out"));
@@ -1062,6 +1066,7 @@ int CodeCoverageTool::doShow(int argc, const char **argv,
ViewOpts.ShowBranchPercents =
ShowBranches == CoverageViewOptions::BranchOutputType::Percent;
ViewOpts.ShowFunctionInstantiations = ShowInstantiations;
+ ViewOpts.ShowDirectoryCoverage = ShowDirectoryCoverage;
ViewOpts.ShowOutputDirectory = ShowOutputDirectory;
ViewOpts.TabSize = TabSize;
ViewOpts.ProjectTitle = ProjectTitle;
diff --git a/llvm/tools/llvm-cov/CoverageReport.cpp b/llvm/tools/llvm-cov/CoverageReport.cpp
index cb0b184e..518f6cd 100644
--- a/llvm/tools/llvm-cov/CoverageReport.cpp
+++ b/llvm/tools/llvm-cov/CoverageReport.cpp
@@ -166,6 +166,35 @@ unsigned getRedundantPrefixLen(ArrayRef<std::string> Paths) {
return PrefixLen;
}
+/// Determine the length of the longest redundant prefix of the substrs starts
+/// from \p LCP in \p Paths. \p Paths can't be empty. If there's only one
+/// element in \p Paths, the length of the substr is returned. Note this is
+/// differnet from the behavior of the function above.
+unsigned getRedundantPrefixLen(ArrayRef<StringRef> Paths, unsigned LCP) {
+ assert(!Paths.empty() && "Paths must have at least one element");
+
+ auto Iter = Paths.begin();
+ auto IterE = Paths.end();
+ auto Prefix = Iter->substr(LCP);
+ while (++Iter != IterE) {
+ auto Other = Iter->substr(LCP);
+ auto Len = std::min(Prefix.size(), Other.size());
+ for (std::size_t I = 0; I < Len; ++I) {
+ if (Prefix[I] != Other[I]) {
+ Prefix = Prefix.substr(0, I);
+ break;
+ }
+ }
+ }
+
+ for (auto I = Prefix.size(); --I != SIZE_MAX;) {
+ if (Prefix[I] == '/' || Prefix[I] == '\\')
+ return I + 1;
+ }
+
+ return Prefix.size();
+}
+
} // end anonymous namespace
namespace llvm {
@@ -180,8 +209,14 @@ void CoverageReport::render(const FileCoverageSummary &File,
determineCoveragePercentageColor(File.InstantiationCoverage);
auto LineCoverageColor = determineCoveragePercentageColor(File.LineCoverage);
SmallString<256> FileName = File.Name;
- sys::path::remove_dots(FileName, /*remove_dot_dot=*/true);
sys::path::native(FileName);
+
+ // remove_dots will remove trailing slash, so we need to check before it.
+ auto IsDir = FileName.endswith(sys::path::get_separator());
+ sys::path::remove_dots(FileName, /*remove_dot_dot=*/true);
+ if (IsDir)
+ FileName += sys::path::get_separator();
+
OS << column(FileName, FileReportColumns[0], Column::NoTrim);
if (Options.ShowRegionSummary) {
@@ -436,7 +471,12 @@ void CoverageReport::renderFileReports(
FileCoverageSummary Totals("TOTAL");
auto FileReports =
prepareFileReports(Coverage, Totals, Files, Options, Filters);
+ renderFileReports(OS, FileReports, Totals, Filters.empty());
+}
+void CoverageReport::renderFileReports(
+ raw_ostream &OS, const std::vector<FileCoverageSummary> &FileReports,
+ const FileCoverageSummary &Totals, bool ShowEmptyFiles) const {
std::vector<StringRef> Filenames;
Filenames.reserve(FileReports.size());
for (const FileCoverageSummary &FCS : FileReports)
@@ -467,21 +507,20 @@ void CoverageReport::renderFileReports(
renderDivider(FileReportColumns, OS);
OS << "\n";
- bool EmptyFiles = false;
+ std::vector<const FileCoverageSummary *> EmptyFiles;
for (const FileCoverageSummary &FCS : FileReports) {
if (FCS.FunctionCoverage.getNumFunctions())
render(FCS, OS);
else
- EmptyFiles = true;
+ EmptyFiles.push_back(&FCS);
}
- if (EmptyFiles && Filters.empty()) {
+ if (!EmptyFiles.empty() && ShowEmptyFiles) {
OS << "\n"
<< "Files which contain no functions:\n";
- for (const FileCoverageSummary &FCS : FileReports)
- if (!FCS.FunctionCoverage.getNumFunctions())
- render(FCS, OS);
+ for (auto FCS : EmptyFiles)
+ render(*FCS, OS);
}
renderDivider(FileReportColumns, OS);
@@ -489,4 +528,96 @@ void CoverageReport::renderFileReports(
render(Totals, OS);
}
+Expected<FileCoverageSummary> DirectoryCoverageReport::prepareDirectoryReports(
+ ArrayRef<std::string> SourceFiles) {
+ std::vector<StringRef> Files(SourceFiles.begin(), SourceFiles.end());
+
+ unsigned RootLCP = getRedundantPrefixLen(Files, 0);
+ auto LCPath = Files.front().substr(0, RootLCP);
+
+ ThreadPoolStrategy PoolS = hardware_concurrency(Options.NumThreads);
+ if (Options.NumThreads == 0) {
+ PoolS = heavyweight_hardware_concurrency(Files.size());
+ PoolS.Limit = true;
+ }
+ ThreadPool Pool(PoolS);
+
+ TPool = &Pool;
+ LCPStack = {RootLCP};
+ FileCoverageSummary RootTotals(LCPath);
+ if (auto E = prepareSubDirectoryReports(Files, &RootTotals))
+ return {std::move(E)};
+ return {std::move(RootTotals)};
+}
+
+/// Filter out files in LCPStack.back(), group others by subdirectory name
+/// and recurse on them. After returning from all subdirectories, call
+/// generateSubDirectoryReport(). \p Files must be non-empty. The
+/// FileCoverageSummary of this directory will be added to \p Totals.
+Error DirectoryCoverageReport::prepareSubDirectoryReports(
+ const ArrayRef<StringRef> &Files, FileCoverageSummary *Totals) {
+ assert(!Files.empty() && "Files must have at least one element");
+
+ auto LCP = LCPStack.back();
+ auto LCPath = Files.front().substr(0, LCP).str();
+
+ // Use ordered map to keep entries in order.
+ SubFileReports SubFiles;
+ SubDirReports SubDirs;
+ for (auto &&File : Files) {
+ auto SubPath = File.substr(LCPath.size());
+ SmallVector<char, 128> NativeSubPath;
+ sys::path::native(SubPath, NativeSubPath);
+ StringRef NativeSubPathRef(NativeSubPath.data(), NativeSubPath.size());
+
+ auto I = sys::path::begin(NativeSubPathRef);
+ auto E = sys::path::end(NativeSubPathRef);
+ assert(I != E && "Such case should have been filtered out in the caller");
+
+ auto Name = SubPath.substr(0, I->size());
+ if (++I == E) {
+ auto Iter = SubFiles.insert_or_assign(Name, SubPath).first;
+ // Makes files reporting overlap with subdir reporting.
+ TPool->async(&CoverageReport::prepareSingleFileReport, File, &Coverage,
+ Options, LCP, &Iter->second, &Filters);
+ } else {
+ SubDirs[Name].second.push_back(File);
+ }
+ }
+
+ // Call recursively on subdirectories.
+ for (auto &&KV : SubDirs) {
+ auto &V = KV.second;
+ if (V.second.size() == 1) {
+ // If there's only one file in that subdirectory, we don't bother to
+ // recurse on it further.
+ V.first.Name = V.second.front().substr(LCP);
+ TPool->async(&CoverageReport::prepareSingleFileReport, V.second.front(),
+ &Coverage, Options, LCP, &V.first, &Filters);
+ } else {
+ auto SubDirLCP = getRedundantPrefixLen(V.second, LCP);
+ V.first.Name = V.second.front().substr(LCP, SubDirLCP);
+ LCPStack.push_back(LCP + SubDirLCP);
+ if (auto E = prepareSubDirectoryReports(V.second, &V.first))
+ return E;
+ }
+ }
+
+ TPool->wait();
+
+ FileCoverageSummary CurrentTotals(LCPath);
+ for (auto &&KV : SubFiles)
+ CurrentTotals += KV.second;
+ for (auto &&KV : SubDirs)
+ CurrentTotals += KV.second.first;
+ *Totals += CurrentTotals;
+
+ if (auto E = generateSubDirectoryReport(
+ std::move(SubFiles), std::move(SubDirs), std::move(CurrentTotals)))
+ return E;
+
+ LCPStack.pop_back();
+ return Error::success();
+}
+
} // end namespace llvm
diff --git a/llvm/tools/llvm-cov/CoverageReport.h b/llvm/tools/llvm-cov/CoverageReport.h
index f9a092f..60f751c 100644
--- a/llvm/tools/llvm-cov/CoverageReport.h
+++ b/llvm/tools/llvm-cov/CoverageReport.h
@@ -16,9 +16,12 @@
#include "CoverageFilters.h"
#include "CoverageSummaryInfo.h"
#include "CoverageViewOptions.h"
+#include <map>
namespace llvm {
+class ThreadPool;
+
/// Displays the code coverage report.
class CoverageReport {
const CoverageViewOptions &Options;
@@ -62,6 +65,73 @@ public:
/// in \p Filters.
void renderFileReports(raw_ostream &OS, ArrayRef<std::string> Files,
const CoverageFiltersMatchAll &Filters) const;
+
+ /// Render file reports with given data.
+ void renderFileReports(raw_ostream &OS,
+ const std::vector<FileCoverageSummary> &FileReports,
+ const FileCoverageSummary &Totals,
+ bool ShowEmptyFiles) const;
+};
+
+/// Prepare reports for every non-trivial directories (which have more than 1
+/// source files) of the source files. This class uses template method pattern.
+class DirectoryCoverageReport {
+public:
+ DirectoryCoverageReport(
+ const CoverageViewOptions &Options,
+ const coverage::CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters = CoverageFiltersMatchAll())
+ : Options(Options), Coverage(Coverage), Filters(Filters) {}
+
+ virtual ~DirectoryCoverageReport() = default;
+
+ /// Prepare file reports for each directory in \p SourceFiles. The total
+ /// report for all files is returned and its Name is set to the LCP of all
+ /// files. The size of \p SourceFiles must be greater than 1 or else the
+ /// behavior is undefined, in which case you should use
+ /// CoverageReport::prepareSingleFileReport instead. If an error occurs,
+ /// the recursion will stop immediately.
+ Expected<FileCoverageSummary>
+ prepareDirectoryReports(ArrayRef<std::string> SourceFiles);
+
+protected:
+ // These member variables below are used for avoiding being passed
+ // repeatedly in recursion.
+ const CoverageViewOptions &Options;
+ const coverage::CoverageMapping &Coverage;
+ const CoverageFiltersMatchAll &Filters;
+
+ /// For calling CoverageReport::prepareSingleFileReport asynchronously
+ /// in prepareSubDirectoryReports(). It's not intended to be modified by
+ /// generateSubDirectoryReport().
+ ThreadPool *TPool;
+
+ /// One report level may correspond to multiple directory levels as we omit
+ /// directories which have only one subentry. So we use this Stack to track
+ /// each report level's corresponding drectory level.
+ /// Each value in the stack is the LCP prefix length length of that report
+ /// level. LCPStack.front() is the root LCP. Current LCP is LCPStack.back().
+ SmallVector<unsigned, 32> LCPStack;
+
+ // Use std::map to sort table rows in order.
+ using SubFileReports = std::map<StringRef, FileCoverageSummary>;
+ using SubDirReports =
+ std::map<StringRef,
+ std::pair<FileCoverageSummary, SmallVector<StringRef, 0>>>;
+
+ /// This method is called when a report level is prepared during the
+ /// recursion. \p SubFiles are the reports for those files directly in the
+ /// current directory. \p SubDirs are the reports for subdirectories in
+ /// current directory. \p SubTotals is the sum of all, and its name is the
+ /// current LCP. Note that this method won't be called for trivial
+ /// directories.
+ virtual Error generateSubDirectoryReport(SubFileReports &&SubFiles,
+ SubDirReports &&SubDirs,
+ FileCoverageSummary &&SubTotals) = 0;
+
+private:
+ Error prepareSubDirectoryReports(const ArrayRef<StringRef> &Files,
+ FileCoverageSummary *Totals);
};
} // end namespace llvm
diff --git a/llvm/tools/llvm-cov/CoverageSummaryInfo.h b/llvm/tools/llvm-cov/CoverageSummaryInfo.h
index 84a3228..46510dc 100644
--- a/llvm/tools/llvm-cov/CoverageSummaryInfo.h
+++ b/llvm/tools/llvm-cov/CoverageSummaryInfo.h
@@ -222,6 +222,7 @@ struct FileCoverageSummary {
FunctionCoverageInfo FunctionCoverage;
FunctionCoverageInfo InstantiationCoverage;
+ FileCoverageSummary() = default;
FileCoverageSummary(StringRef Name) : Name(Name) {}
FileCoverageSummary &operator+=(const FileCoverageSummary &RHS) {
diff --git a/llvm/tools/llvm-cov/CoverageViewOptions.h b/llvm/tools/llvm-cov/CoverageViewOptions.h
index fedf2df..eb852859 100644
--- a/llvm/tools/llvm-cov/CoverageViewOptions.h
+++ b/llvm/tools/llvm-cov/CoverageViewOptions.h
@@ -38,6 +38,7 @@ struct CoverageViewOptions {
bool ShowBranchSummary;
bool ShowRegionSummary;
bool ShowInstantiationSummary;
+ bool ShowDirectoryCoverage;
bool ExportSummaryOnly;
bool SkipExpansions;
bool SkipFunctions;
diff --git a/llvm/tools/llvm-cov/SourceCoverageView.cpp b/llvm/tools/llvm-cov/SourceCoverageView.cpp
index ea86aca..7480b7f 100644
--- a/llvm/tools/llvm-cov/SourceCoverageView.cpp
+++ b/llvm/tools/llvm-cov/SourceCoverageView.cpp
@@ -76,8 +76,12 @@ std::unique_ptr<CoveragePrinter>
CoveragePrinter::create(const CoverageViewOptions &Opts) {
switch (Opts.Format) {
case CoverageViewOptions::OutputFormat::Text:
+ if (Opts.ShowDirectoryCoverage)
+ return std::make_unique<CoveragePrinterTextDirectory>(Opts);
return std::make_unique<CoveragePrinterText>(Opts);
case CoverageViewOptions::OutputFormat::HTML:
+ if (Opts.ShowDirectoryCoverage)
+ return std::make_unique<CoveragePrinterHTMLDirectory>(Opts);
return std::make_unique<CoveragePrinterHTML>(Opts);
case CoverageViewOptions::OutputFormat::Lcov:
// Unreachable because CodeCoverage.cpp should terminate with an error
diff --git a/llvm/tools/llvm-cov/SourceCoverageView.h b/llvm/tools/llvm-cov/SourceCoverageView.h
index 5a9fcdd..c07595f 100644
--- a/llvm/tools/llvm-cov/SourceCoverageView.h
+++ b/llvm/tools/llvm-cov/SourceCoverageView.h
@@ -99,7 +99,7 @@ protected:
CoveragePrinter(const CoverageViewOptions &Opts) : Opts(Opts) {}
/// Return `OutputDir/ToplevelDir/Path.Extension`. If \p InToplevel is
- /// false, skip the ToplevelDir component. If \p Relative is false, skip the
+ /// true, skip the ToplevelDir component. If \p Relative is true, skip the
/// OutputDir component.
std::string getOutputPath(StringRef Path, StringRef Extension,
bool InToplevel, bool Relative = true) const;
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp
index 49f2035..79a0494 100644
--- a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp
+++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp
@@ -10,12 +10,13 @@
///
//===----------------------------------------------------------------------===//
-#include "CoverageReport.h"
#include "SourceCoverageViewHTML.h"
+#include "CoverageReport.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/Path.h"
+#include "llvm/Support/ThreadPool.h"
#include <optional>
using namespace llvm;
@@ -49,23 +50,41 @@ std::string escape(StringRef Str, const CoverageViewOptions &Opts) {
}
// Create a \p Name tag around \p Str, and optionally set its \p ClassName.
-std::string tag(const std::string &Name, const std::string &Str,
- const std::string &ClassName = "") {
- std::string Tag = "<" + Name;
- if (!ClassName.empty())
- Tag += " class='" + ClassName + "'";
- return Tag + ">" + Str + "</" + Name + ">";
+std::string tag(StringRef Name, StringRef Str, StringRef ClassName = "") {
+ std::string Tag = "<";
+ Tag += Name;
+ if (!ClassName.empty()) {
+ Tag += " class='";
+ Tag += ClassName;
+ Tag += "'";
+ }
+ Tag += ">";
+ Tag += Str;
+ Tag += "</";
+ Tag += Name;
+ Tag += ">";
+ return Tag;
}
// Create an anchor to \p Link with the label \p Str.
-std::string a(const std::string &Link, const std::string &Str,
- const std::string &TargetName = "") {
- std::string Name = TargetName.empty() ? "" : ("name='" + TargetName + "' ");
- return "<a " + Name + "href='" + Link + "'>" + Str + "</a>";
+std::string a(StringRef Link, StringRef Str, StringRef TargetName = "") {
+ std::string Tag;
+ Tag += "<a ";
+ if (!TargetName.empty()) {
+ Tag += "name='";
+ Tag += TargetName;
+ Tag += "' ";
+ }
+ Tag += "href='";
+ Tag += Link;
+ Tag += "'>";
+ Tag += Str;
+ Tag += "</a>";
+ return Tag;
}
const char *BeginHeader =
- "<head>"
+ "<head>"
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
"<meta charset='UTF-8'>";
@@ -272,6 +291,57 @@ void emitPrelude(raw_ostream &OS, const CoverageViewOptions &Opts,
OS << EndHeader << "<body>";
}
+void emitTableRow(raw_ostream &OS, const CoverageViewOptions &Opts,
+ const std::string &FirstCol, const FileCoverageSummary &FCS,
+ bool IsTotals) {
+ SmallVector<std::string, 8> Columns;
+
+ // Format a coverage triple and add the result to the list of columns.
+ auto AddCoverageTripleToColumn =
+ [&Columns, &Opts](unsigned Hit, unsigned Total, float Pctg) {
+ std::string S;
+ {
+ raw_string_ostream RSO{S};
+ if (Total)
+ RSO << format("%*.2f", 7, Pctg) << "% ";
+ else
+ RSO << "- ";
+ RSO << '(' << Hit << '/' << Total << ')';
+ }
+ const char *CellClass = "column-entry-yellow";
+ if (Pctg >= Opts.HighCovWatermark)
+ CellClass = "column-entry-green";
+ else if (Pctg < Opts.LowCovWatermark)
+ CellClass = "column-entry-red";
+ Columns.emplace_back(tag("td", tag("pre", S), CellClass));
+ };
+
+ Columns.emplace_back(tag("td", tag("pre", FirstCol)));
+ AddCoverageTripleToColumn(FCS.FunctionCoverage.getExecuted(),
+ FCS.FunctionCoverage.getNumFunctions(),
+ FCS.FunctionCoverage.getPercentCovered());
+ if (Opts.ShowInstantiationSummary)
+ AddCoverageTripleToColumn(FCS.InstantiationCoverage.getExecuted(),
+ FCS.InstantiationCoverage.getNumFunctions(),
+ FCS.InstantiationCoverage.getPercentCovered());
+ AddCoverageTripleToColumn(FCS.LineCoverage.getCovered(),
+ FCS.LineCoverage.getNumLines(),
+ FCS.LineCoverage.getPercentCovered());
+ if (Opts.ShowRegionSummary)
+ AddCoverageTripleToColumn(FCS.RegionCoverage.getCovered(),
+ FCS.RegionCoverage.getNumRegions(),
+ FCS.RegionCoverage.getPercentCovered());
+ if (Opts.ShowBranchSummary)
+ AddCoverageTripleToColumn(FCS.BranchCoverage.getCovered(),
+ FCS.BranchCoverage.getNumBranches(),
+ FCS.BranchCoverage.getPercentCovered());
+
+ if (IsTotals)
+ OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row-bold");
+ else
+ OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row");
+}
+
void emitEpilog(raw_ostream &OS) {
OS << "</body>"
<< "</html>";
@@ -330,33 +400,44 @@ CoveragePrinterHTML::buildLinkToFile(StringRef SF,
return a(LinkTarget, LinkText);
}
+Error CoveragePrinterHTML::emitStyleSheet() {
+ auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true);
+ if (Error E = CSSOrErr.takeError())
+ return E;
+
+ OwnedStream CSS = std::move(CSSOrErr.get());
+ CSS->operator<<(CSSForCoverage);
+
+ return Error::success();
+}
+
+void CoveragePrinterHTML::emitReportHeader(raw_ostream &OSRef,
+ const std::string &Title) {
+ // Emit some basic information about the coverage report.
+ if (Opts.hasProjectTitle())
+ OSRef << tag(ProjectTitleTag, escape(Opts.ProjectTitle, Opts));
+ OSRef << tag(ReportTitleTag, Title);
+ 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);
+}
+
/// Render a file coverage summary (\p FCS) in a table row. If \p IsTotals is
/// false, link the summary to \p SF.
void CoveragePrinterHTML::emitFileSummary(raw_ostream &OS, StringRef SF,
const FileCoverageSummary &FCS,
bool IsTotals) const {
- SmallVector<std::string, 8> Columns;
-
- // Format a coverage triple and add the result to the list of columns.
- auto AddCoverageTripleToColumn =
- [&Columns, this](unsigned Hit, unsigned Total, float Pctg) {
- std::string S;
- {
- raw_string_ostream RSO{S};
- if (Total)
- RSO << format("%*.2f", 7, Pctg) << "% ";
- else
- RSO << "- ";
- RSO << '(' << Hit << '/' << Total << ')';
- }
- const char *CellClass = "column-entry-yellow";
- if (Pctg >= Opts.HighCovWatermark)
- CellClass = "column-entry-green";
- else if (Pctg < Opts.LowCovWatermark)
- CellClass = "column-entry-red";
- Columns.emplace_back(tag("td", tag("pre", S), CellClass));
- };
-
// Simplify the display file path, and wrap it in a link if requested.
std::string Filename;
if (IsTotals) {
@@ -365,43 +446,16 @@ void CoveragePrinterHTML::emitFileSummary(raw_ostream &OS, StringRef SF,
Filename = buildLinkToFile(SF, FCS);
}
- Columns.emplace_back(tag("td", tag("pre", Filename)));
- AddCoverageTripleToColumn(FCS.FunctionCoverage.getExecuted(),
- FCS.FunctionCoverage.getNumFunctions(),
- FCS.FunctionCoverage.getPercentCovered());
- if (Opts.ShowInstantiationSummary)
- AddCoverageTripleToColumn(FCS.InstantiationCoverage.getExecuted(),
- FCS.InstantiationCoverage.getNumFunctions(),
- FCS.InstantiationCoverage.getPercentCovered());
- AddCoverageTripleToColumn(FCS.LineCoverage.getCovered(),
- FCS.LineCoverage.getNumLines(),
- FCS.LineCoverage.getPercentCovered());
- if (Opts.ShowRegionSummary)
- AddCoverageTripleToColumn(FCS.RegionCoverage.getCovered(),
- FCS.RegionCoverage.getNumRegions(),
- FCS.RegionCoverage.getPercentCovered());
- if (Opts.ShowBranchSummary)
- AddCoverageTripleToColumn(FCS.BranchCoverage.getCovered(),
- FCS.BranchCoverage.getNumBranches(),
- FCS.BranchCoverage.getPercentCovered());
-
- if (IsTotals)
- OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row-bold");
- else
- OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row");
+ emitTableRow(OS, Opts, Filename, FCS, IsTotals);
}
Error CoveragePrinterHTML::createIndexFile(
ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
const CoverageFiltersMatchAll &Filters) {
// Emit the default stylesheet.
- auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true);
- if (Error E = CSSOrErr.takeError())
+ if (Error E = emitStyleSheet())
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())
@@ -412,24 +466,8 @@ Error CoveragePrinterHTML::createIndexFile(
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.");
+ emitReportHeader(OSRef, "Coverage 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);
@@ -465,6 +503,199 @@ Error CoveragePrinterHTML::createIndexFile(
return Error::success();
}
+struct CoveragePrinterHTMLDirectory::Reporter : public DirectoryCoverageReport {
+ CoveragePrinterHTMLDirectory &Printer;
+
+ Reporter(CoveragePrinterHTMLDirectory &Printer,
+ const coverage::CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters)
+ : DirectoryCoverageReport(Printer.Opts, Coverage, Filters),
+ Printer(Printer) {}
+
+ Error generateSubDirectoryReport(SubFileReports &&SubFiles,
+ SubDirReports &&SubDirs,
+ FileCoverageSummary &&SubTotals) override {
+ auto &LCPath = SubTotals.Name;
+ assert(Options.hasOutputDirectory() &&
+ "No output directory for index file");
+
+ SmallString<128> OSPath = LCPath;
+ sys::path::append(OSPath, "index");
+ auto OSOrErr = Printer.createOutputStream(OSPath, "html",
+ /*InToplevel=*/false);
+ if (auto E = OSOrErr.takeError())
+ return E;
+ auto OS = std::move(OSOrErr.get());
+ raw_ostream &OSRef = *OS.get();
+
+ auto IndexHtmlPath = Printer.getOutputPath((LCPath + "index").str(), "html",
+ /*InToplevel=*/false);
+ emitPrelude(OSRef, Options, getPathToStyle(IndexHtmlPath));
+
+ auto NavLink = buildTitleLinks(LCPath);
+ Printer.emitReportHeader(OSRef, "Coverage Report (" + NavLink + ")");
+
+ std::vector<const FileCoverageSummary *> EmptyFiles;
+
+ // Make directories at the top of the table.
+ for (auto &&SubDir : SubDirs) {
+ auto &Report = SubDir.second.first;
+ if (!Report.FunctionCoverage.getNumFunctions())
+ EmptyFiles.push_back(&Report);
+ else
+ emitTableRow(OSRef, Options, buildRelLinkToFile(Report.Name), Report,
+ /*IsTotals=*/false);
+ }
+
+ for (auto &&SubFile : SubFiles) {
+ auto &Report = SubFile.second;
+ if (!Report.FunctionCoverage.getNumFunctions())
+ EmptyFiles.push_back(&Report);
+ else
+ emitTableRow(OSRef, Options, buildRelLinkToFile(Report.Name), Report,
+ /*IsTotals=*/false);
+ }
+
+ // Emit the totals row.
+ emitTableRow(OSRef, Options, "Totals", SubTotals, /*IsTotals=*/false);
+ 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.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 FCS : EmptyFiles) {
+ auto Link = buildRelLinkToFile(FCS->Name);
+ OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n';
+ }
+ OSRef << EndTable << EndCenteredDiv;
+ }
+
+ // Emit epilog.
+ OSRef << tag("h5", escape(Options.getLLVMVersionString(), Options));
+ emitEpilog(OSRef);
+
+ return Error::success();
+ }
+
+ /// Make a title with hyperlinks to the index.html files of each hierarchy
+ /// of the report.
+ std::string buildTitleLinks(StringRef LCPath) const {
+ // For each report level in LCPStack, extract the path component and
+ // calculate the number of "../" relative to current LCPath.
+ SmallVector<std::pair<SmallString<128>, unsigned>, 16> Components;
+
+ auto Iter = LCPStack.begin(), IterE = LCPStack.end();
+ SmallString<128> RootPath;
+ if (*Iter == 0) {
+ // If llvm-cov works on relative coverage mapping data, the LCP of
+ // all source file paths can be 0, which makes the title path empty.
+ // As we like adding a slash at the back of the path to indicate a
+ // directory, in this case, we use "." as the root path to make it
+ // not be confused with the root path "/".
+ RootPath = ".";
+ } else {
+ RootPath = LCPath.substr(0, *Iter);
+ sys::path::native(RootPath);
+ sys::path::remove_dots(RootPath, /*remove_dot_dot=*/true);
+ }
+ Components.emplace_back(std::move(RootPath), 0);
+
+ for (auto Last = *Iter; ++Iter != IterE; Last = *Iter) {
+ SmallString<128> SubPath = LCPath.substr(Last, *Iter - Last);
+ sys::path::native(SubPath);
+ sys::path::remove_dots(SubPath, /*remove_dot_dot=*/true);
+ auto Level = unsigned(SubPath.count(sys::path::get_separator())) + 1;
+ Components.back().second += Level;
+ Components.emplace_back(std::move(SubPath), Level);
+ }
+
+ // Then we make the title accroding to Components.
+ std::string S;
+ for (auto I = Components.begin(), E = Components.end();;) {
+ auto &Name = I->first;
+ if (++I == E) {
+ S += a("./index.html", Name);
+ S += sys::path::get_separator();
+ break;
+ }
+
+ SmallString<128> Link;
+ for (unsigned J = I->second; J > 0; --J)
+ Link += "../";
+ Link += "index.html";
+ S += a(Link, Name);
+ S += sys::path::get_separator();
+ }
+ return S;
+ }
+
+ std::string buildRelLinkToFile(StringRef RelPath) const {
+ SmallString<128> LinkTextStr(RelPath);
+ sys::path::native(LinkTextStr);
+
+ // remove_dots will remove trailing slash, so we need to check before it.
+ auto IsDir = LinkTextStr.endswith(sys::path::get_separator());
+ sys::path::remove_dots(LinkTextStr, /*remove_dot_dot=*/true);
+
+ SmallString<128> LinkTargetStr(LinkTextStr);
+ if (IsDir) {
+ LinkTextStr += sys::path::get_separator();
+ sys::path::append(LinkTargetStr, "index.html");
+ } else {
+ LinkTargetStr += ".html";
+ }
+
+ auto LinkText = escape(LinkTextStr, Options);
+ auto LinkTarget = escape(LinkTargetStr, Options);
+ return a(LinkTarget, LinkText);
+ }
+};
+
+Error CoveragePrinterHTMLDirectory::createIndexFile(
+ ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters) {
+ // The createSubIndexFile function only works when SourceFiles is
+ // more than one. So we fallback to CoveragePrinterHTML when it is.
+ if (SourceFiles.size() <= 1)
+ return CoveragePrinterHTML::createIndexFile(SourceFiles, Coverage, Filters);
+
+ // Emit the default stylesheet.
+ if (Error E = emitStyleSheet())
+ return E;
+
+ // Emit index files in every subdirectory.
+ Reporter Report(*this, Coverage, Filters);
+ auto TotalsOrErr = Report.prepareDirectoryReports(SourceFiles);
+ if (auto E = TotalsOrErr.takeError())
+ return E;
+ auto &LCPath = TotalsOrErr->Name;
+
+ // Emit the top level index file. Top level index file is just a redirection
+ // to the index file in the LCP directory.
+ auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true);
+ if (auto E = OSOrErr.takeError())
+ return E;
+ auto OS = std::move(OSOrErr.get());
+ auto LCPIndexFilePath =
+ getOutputPath((LCPath + "index").str(), "html", /*InToplevel=*/false);
+ *OS.get() << R"(<!DOCTYPE html>
+ <html>
+ <head>
+ <meta http-equiv="Refresh" content="0; url=')"
+ << LCPIndexFilePath << R"('" />
+ </head>
+ <body></body>
+ </html>
+ )";
+
+ return Error::success();
+}
+
void SourceCoverageViewHTML::renderViewHeader(raw_ostream &OS) {
OS << BeginCenteredDiv << BeginTable;
}
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.h b/llvm/tools/llvm-cov/SourceCoverageViewHTML.h
index 7d94675..c846379 100644
--- a/llvm/tools/llvm-cov/SourceCoverageViewHTML.h
+++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.h
@@ -19,6 +19,8 @@ namespace llvm {
using namespace coverage;
+class ThreadPool;
+
struct FileCoverageSummary;
/// A coverage printer for html output.
@@ -36,6 +38,10 @@ public:
CoveragePrinterHTML(const CoverageViewOptions &Opts)
: CoveragePrinter(Opts) {}
+protected:
+ Error emitStyleSheet();
+ void emitReportHeader(raw_ostream &OSRef, const std::string &Title);
+
private:
void emitFileSummary(raw_ostream &OS, StringRef SF,
const FileCoverageSummary &FCS,
@@ -44,6 +50,20 @@ private:
const FileCoverageSummary &FCS) const;
};
+/// A coverage printer for html output, but generates index files in every
+/// subdirectory to show a hierarchical view.
+class CoveragePrinterHTMLDirectory : public CoveragePrinterHTML {
+public:
+ using CoveragePrinterHTML::CoveragePrinterHTML;
+
+ Error createIndexFile(ArrayRef<std::string> SourceFiles,
+ const coverage::CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters) override;
+
+private:
+ struct Reporter;
+};
+
/// A code coverage view which supports html-based rendering.
class SourceCoverageViewHTML : public SourceCoverageView {
void renderViewHeader(raw_ostream &OS) override;
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewText.cpp b/llvm/tools/llvm-cov/SourceCoverageViewText.cpp
index 6e0db09..44694a0 100644
--- a/llvm/tools/llvm-cov/SourceCoverageViewText.cpp
+++ b/llvm/tools/llvm-cov/SourceCoverageViewText.cpp
@@ -14,7 +14,9 @@
#include "CoverageReport.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Format.h"
+#include "llvm/Support/Path.h"
#include <optional>
using namespace llvm;
@@ -46,6 +48,69 @@ Error CoveragePrinterText::createIndexFile(
return Error::success();
}
+struct CoveragePrinterTextDirectory::Reporter : public DirectoryCoverageReport {
+ CoveragePrinterTextDirectory &Printer;
+
+ Reporter(CoveragePrinterTextDirectory &Printer,
+ const coverage::CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters)
+ : DirectoryCoverageReport(Printer.Opts, Coverage, Filters),
+ Printer(Printer) {}
+
+ Error generateSubDirectoryReport(SubFileReports &&SubFiles,
+ SubDirReports &&SubDirs,
+ FileCoverageSummary &&SubTotals) override {
+ auto &LCPath = SubTotals.Name;
+ assert(Options.hasOutputDirectory() &&
+ "No output directory for index file");
+
+ SmallString<128> OSPath = LCPath;
+ sys::path::append(OSPath, "index");
+ auto OSOrErr = Printer.createOutputStream(OSPath, "txt",
+ /*InToplevel=*/false);
+ if (auto E = OSOrErr.takeError())
+ return E;
+ auto OS = std::move(OSOrErr.get());
+ raw_ostream &OSRef = *OS.get();
+
+ std::vector<FileCoverageSummary> Reports;
+ for (auto &&SubDir : SubDirs)
+ Reports.push_back(std::move(SubDir.second.first));
+ for (auto &&SubFile : SubFiles)
+ Reports.push_back(std::move(SubFile.second));
+
+ CoverageReport Report(Options, Coverage);
+ Report.renderFileReports(OSRef, Reports, SubTotals, Filters.empty());
+
+ Options.colored_ostream(OSRef, raw_ostream::CYAN)
+ << "\n"
+ << Options.getLLVMVersionString();
+
+ return Error::success();
+ }
+};
+
+Error CoveragePrinterTextDirectory::createIndexFile(
+ ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters) {
+ if (SourceFiles.size() <= 1)
+ return CoveragePrinterText::createIndexFile(SourceFiles, Coverage, Filters);
+
+ Reporter Report(*this, Coverage, Filters);
+ auto TotalsOrErr = Report.prepareDirectoryReports(SourceFiles);
+ if (auto E = TotalsOrErr.takeError())
+ return E;
+ auto &LCPath = TotalsOrErr->Name;
+
+ auto TopIndexFilePath =
+ getOutputPath("index", "txt", /*InToplevel=*/true, /*Relative=*/false);
+ auto LCPIndexFilePath =
+ getOutputPath((LCPath + "index").str(), "txt", /*InToplevel=*/false,
+ /*Relative=*/false);
+ return errorCodeToError(
+ sys::fs::copy_file(LCPIndexFilePath, TopIndexFilePath));
+}
+
namespace {
static const unsigned LineCoverageColumnWidth = 7;
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewText.h b/llvm/tools/llvm-cov/SourceCoverageViewText.h
index b2be060..ade47ed 100644
--- a/llvm/tools/llvm-cov/SourceCoverageViewText.h
+++ b/llvm/tools/llvm-cov/SourceCoverageViewText.h
@@ -35,6 +35,21 @@ public:
: CoveragePrinter(Opts) {}
};
+/// A coverage printer for text output, but generates index files in every
+/// subdirectory to show a hierarchical view. The implementation is similar
+/// to CoveragePrinterHTMLDirectory. So please refer to that for more comments.
+class CoveragePrinterTextDirectory : public CoveragePrinterText {
+public:
+ using CoveragePrinterText::CoveragePrinterText;
+
+ Error createIndexFile(ArrayRef<std::string> SourceFiles,
+ const CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters) override;
+
+private:
+ struct Reporter;
+};
+
/// A code coverage view which supports text-based rendering.
class SourceCoverageViewText : public SourceCoverageView {
void renderViewHeader(raw_ostream &OS) override;
diff --git a/llvm/tools/llvm-cov/TestingSupport.cpp b/llvm/tools/llvm-cov/TestingSupport.cpp
index 59e9ee4..6ad8c35 100644
--- a/llvm/tools/llvm-cov/TestingSupport.cpp
+++ b/llvm/tools/llvm-cov/TestingSupport.cpp
@@ -121,6 +121,11 @@ int convertForTestingMain(int argc, const char *argv[]) {
return 1;
}
+ // If this is a linked PE/COFF file, then we have to skip over the null byte
+ // that is allocated in the .lprfn$A section in the LLVM profiling runtime.
+ if (isa<COFFObjectFile>(OF) && !OF->isRelocatableObject())
+ ProfileNamesData = ProfileNamesData.drop_front(1);
+
int FD;
if (auto Err = sys::fs::openFileForWrite(OutputFilename, FD)) {
errs() << "error: " << Err.message() << "\n";