aboutsummaryrefslogtreecommitdiff
path: root/llvm/tools
diff options
context:
space:
mode:
authorHana Dusíková <hanicka@hanicka.net>2024-06-17 21:30:50 +0200
committerGitHub <noreply@github.com>2024-06-17 21:30:50 +0200
commit06aa078d68380bc775f0a903204fe330d50f4f1f (patch)
tree2f4fe6d8219559162866e22a4a2833de8a0556bd /llvm/tools
parentc22d3917b93a6d54613d2e5b2ea4c97546144c46 (diff)
downloadllvm-06aa078d68380bc775f0a903204fe330d50f4f1f.zip
llvm-06aa078d68380bc775f0a903204fe330d50f4f1f.tar.gz
llvm-06aa078d68380bc775f0a903204fe330d50f4f1f.tar.bz2
[llvm-cov] Coverage report HTML UI to jump between uncovered parts of code (#95662)
I replaced "jump to first uncovered line" with UI buttons and keyboard shortcut to jump between uncovered parts of code: lines (key L), branchs (key B), regions (key R). User can also jump in reverse direction with shift+key.
Diffstat (limited to 'llvm/tools')
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageView.cpp3
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageView.h3
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp220
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewHTML.h4
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewText.cpp3
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewText.h3
6 files changed, 203 insertions, 33 deletions
diff --git a/llvm/tools/llvm-cov/SourceCoverageView.cpp b/llvm/tools/llvm-cov/SourceCoverageView.cpp
index 45bddd7..ce55e3a 100644
--- a/llvm/tools/llvm-cov/SourceCoverageView.cpp
+++ b/llvm/tools/llvm-cov/SourceCoverageView.cpp
@@ -203,8 +203,7 @@ void SourceCoverageView::print(raw_ostream &OS, bool WholeFile,
if (ShowSourceName)
renderSourceName(OS, WholeFile);
- renderTableHeader(OS, (ViewDepth > 0) ? 0 : getFirstUncoveredLineNo(),
- ViewDepth);
+ renderTableHeader(OS, ViewDepth);
// We need the expansions, instantiations, and branches sorted so we can go
// through them while we iterate lines.
diff --git a/llvm/tools/llvm-cov/SourceCoverageView.h b/llvm/tools/llvm-cov/SourceCoverageView.h
index a874f7c..d255f8c 100644
--- a/llvm/tools/llvm-cov/SourceCoverageView.h
+++ b/llvm/tools/llvm-cov/SourceCoverageView.h
@@ -262,8 +262,7 @@ protected:
virtual void renderTitle(raw_ostream &OS, StringRef CellText) = 0;
/// Render the table header for a given source file.
- virtual void renderTableHeader(raw_ostream &OS, unsigned FirstUncoveredLineNo,
- unsigned IndentLevel) = 0;
+ virtual void renderTableHeader(raw_ostream &OS, unsigned IndentLevel) = 0;
/// @}
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp
index d4b2ea3..6f4d327 100644
--- a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp
+++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp
@@ -88,6 +88,113 @@ const char *BeginHeader =
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
"<meta charset='UTF-8'>";
+const char *JSForCoverage =
+ R"javascript(
+
+function next_uncovered(selector, reverse, scroll_selector) {
+ function visit_element(element) {
+ element.classList.add("seen");
+ element.classList.add("selected");
+
+ if (!scroll_selector) {
+ scroll_selector = "tr:has(.selected) td.line-number"
+ }
+
+ const scroll_to = document.querySelector(scroll_selector);
+ if (scroll_to) {
+ scroll_to.scrollIntoView({behavior: "smooth", block: "center", inline: "end"});
+ }
+
+ }
+
+ function select_one() {
+ if (!reverse) {
+ const previously_selected = document.querySelector(".selected");
+
+ if (previously_selected) {
+ previously_selected.classList.remove("selected");
+ }
+
+ return document.querySelector(selector + ":not(.seen)");
+ } else {
+ const previously_selected = document.querySelector(".selected");
+
+ if (previously_selected) {
+ previously_selected.classList.remove("selected");
+ previously_selected.classList.remove("seen");
+ }
+
+ const nodes = document.querySelectorAll(selector + ".seen");
+ if (nodes) {
+ const last = nodes[nodes.length - 1]; // last
+ return last;
+ } else {
+ return undefined;
+ }
+ }
+ }
+
+ function reset_all() {
+ if (!reverse) {
+ const all_seen = document.querySelectorAll(selector + ".seen");
+
+ if (all_seen) {
+ all_seen.forEach(e => e.classList.remove("seen"));
+ }
+ } else {
+ const all_seen = document.querySelectorAll(selector + ":not(.seen)");
+
+ if (all_seen) {
+ all_seen.forEach(e => e.classList.add("seen"));
+ }
+ }
+
+ }
+
+ const uncovered = select_one();
+
+ if (uncovered) {
+ visit_element(uncovered);
+ } else {
+ reset_all();
+
+
+ const uncovered = select_one();
+
+ if (uncovered) {
+ visit_element(uncovered);
+ }
+ }
+}
+
+function next_line(reverse) {
+ next_uncovered("td.uncovered-line", reverse)
+}
+
+function next_region(reverse) {
+ next_uncovered("span.red.region", reverse);
+}
+
+function next_branch(reverse) {
+ next_uncovered("span.red.branch", reverse);
+}
+
+document.addEventListener("keypress", function(event) {
+ console.log(event);
+ const reverse = event.shiftKey;
+ if (event.code == "KeyL") {
+ next_line(reverse);
+ }
+ if (event.code == "KeyB") {
+ next_branch(reverse);
+ }
+ if (event.code == "KeyR") {
+ next_region(reverse);
+ }
+
+});
+)javascript";
+
const char *CSSForCoverage =
R"(.red {
background-color: #f004;
@@ -95,6 +202,9 @@ const char *CSSForCoverage =
.cyan {
background-color: cyan;
}
+html {
+ scroll-behavior: smooth;
+}
body {
font-family: -apple-system, sans-serif;
}
@@ -171,6 +281,18 @@ table {
text-align: right;
color: #d00;
}
+.uncovered-line.selected {
+ color: #f00;
+ font-weight: bold;
+}
+.region.red.selected {
+ background-color: #f008;
+ font-weight: bold;
+}
+.branch.red.selected {
+ background-color: #f008;
+ font-weight: bold;
+}
.tooltip {
position: relative;
display: inline;
@@ -231,12 +353,19 @@ tr:hover {
tr:last-child {
border-bottom: none;
}
-tr:has(> td >a:target) {
- background-color: #50f6;
+tr:has(> td >a:target), tr:has(> td.uncovered-line.selected) {
+ background-color: #8884;
}
a {
color: inherit;
}
+.control {
+ position: fixed;
+ top: 0em;
+ right: 0em;
+ padding: 1em;
+ background: #FFF8;
+}
@media (prefers-color-scheme: dark) {
body {
background-color: #222;
@@ -254,6 +383,12 @@ a {
.tooltip {
background-color: #068;
}
+ .control {
+ background: #2228;
+ }
+ tr:has(> td >a:target), tr:has(> td.uncovered-line.selected) {
+ background-color: #8884;
+ }
}
)";
@@ -298,8 +433,18 @@ std::string getPathToStyle(StringRef ViewPath) {
return PathToStyle + "style.css";
}
+std::string getPathToJavaScript(StringRef ViewPath) {
+ std::string PathToJavaScript;
+ std::string PathSep = std::string(sys::path::get_separator());
+ unsigned NumSeps = ViewPath.count(PathSep);
+ for (unsigned I = 0, E = NumSeps; I < E; ++I)
+ PathToJavaScript += ".." + PathSep;
+ return PathToJavaScript + "control.js";
+}
+
void emitPrelude(raw_ostream &OS, const CoverageViewOptions &Opts,
- const std::string &PathToStyle = "") {
+ const std::string &PathToStyle = "",
+ const std::string &PathToJavaScript = "") {
OS << "<!doctype html>"
"<html>"
<< BeginHeader;
@@ -311,6 +456,12 @@ void emitPrelude(raw_ostream &OS, const CoverageViewOptions &Opts,
OS << "<link rel='stylesheet' type='text/css' href='"
<< escape(PathToStyle, Opts) << "'>";
+ // Link to a JavaScript if one is available
+ if (PathToJavaScript.empty())
+ OS << "<script>" << JSForCoverage << "</script>";
+ else
+ OS << "<script src='" << escape(PathToJavaScript, Opts) << "'></script>";
+
OS << EndHeader << "<body>";
}
@@ -390,7 +541,8 @@ CoveragePrinterHTML::createViewFile(StringRef Path, bool InToplevel) {
emitPrelude(*OS.get(), Opts);
} else {
std::string ViewPath = getOutputPath(Path, "html", InToplevel);
- emitPrelude(*OS.get(), Opts, getPathToStyle(ViewPath));
+ emitPrelude(*OS.get(), Opts, getPathToStyle(ViewPath),
+ getPathToJavaScript(ViewPath));
}
return std::move(OS);
@@ -442,6 +594,17 @@ Error CoveragePrinterHTML::emitStyleSheet() {
return Error::success();
}
+Error CoveragePrinterHTML::emitJavaScript() {
+ auto JSOrErr = createOutputStream("control", "js", /*InToplevel=*/true);
+ if (Error E = JSOrErr.takeError())
+ return E;
+
+ OwnedStream JS = std::move(JSOrErr.get());
+ JS->operator<<(JSForCoverage);
+
+ return Error::success();
+}
+
void CoveragePrinterHTML::emitReportHeader(raw_ostream &OSRef,
const std::string &Title) {
// Emit some basic information about the coverage report.
@@ -487,6 +650,10 @@ Error CoveragePrinterHTML::createIndexFile(
if (Error E = emitStyleSheet())
return E;
+ // Emit the JavaScript UI implementation
+ if (Error E = emitJavaScript())
+ return E;
+
// Emit a file index along with some coverage statistics.
auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true);
if (Error E = OSOrErr.takeError())
@@ -495,7 +662,7 @@ Error CoveragePrinterHTML::createIndexFile(
raw_ostream &OSRef = *OS.get();
assert(Opts.hasOutputDirectory() && "No output directory for index file");
- emitPrelude(OSRef, Opts, getPathToStyle(""));
+ emitPrelude(OSRef, Opts, getPathToStyle(""), getPathToJavaScript(""));
emitReportHeader(OSRef, "Coverage Report");
@@ -561,7 +728,8 @@ struct CoveragePrinterHTMLDirectory::Reporter : public DirectoryCoverageReport {
auto IndexHtmlPath = Printer.getOutputPath((LCPath + "index").str(), "html",
/*InToplevel=*/false);
- emitPrelude(OSRef, Options, getPathToStyle(IndexHtmlPath));
+ emitPrelude(OSRef, Options, getPathToStyle(IndexHtmlPath),
+ getPathToJavaScript(IndexHtmlPath));
auto NavLink = buildTitleLinks(LCPath);
Printer.emitReportHeader(OSRef, "Coverage Report (" + NavLink + ")");
@@ -699,6 +867,10 @@ Error CoveragePrinterHTMLDirectory::createIndexFile(
if (Error E = emitStyleSheet())
return E;
+ // Emit the JavaScript UI implementation
+ if (Error E = emitJavaScript())
+ return E;
+
// Emit index files in every subdirectory.
Reporter Report(*this, Coverage, Filters);
auto TotalsOrErr = Report.prepareDirectoryReports(SourceFiles);
@@ -800,7 +972,10 @@ void SourceCoverageViewHTML::renderLine(raw_ostream &OS, LineRef L,
auto Highlight = [&](const std::string &Snippet, unsigned LC, unsigned RC) {
if (getOptions().Debug)
HighlightedRanges.emplace_back(LC, RC);
- return tag("span", Snippet, std::string(*Color));
+ if (Snippet.empty())
+ return tag("span", Snippet, std::string(*Color));
+ else
+ return tag("span", Snippet, "region " + std::string(*Color));
};
auto CheckIfUncovered = [&](const CoverageSegment *S) {
@@ -883,7 +1058,9 @@ void SourceCoverageViewHTML::renderLineCoverageColumn(
if (Line.isMapped())
Count = tag("pre", formatCount(Line.getExecutionCount()));
std::string CoverageClass =
- (Line.getExecutionCount() > 0) ? "covered-line" : "uncovered-line";
+ (Line.getExecutionCount() > 0)
+ ? "covered-line"
+ : (Line.isMapped() ? "uncovered-line" : "skipped-line");
OS << tag("td", Count, CoverageClass);
}
@@ -957,7 +1134,7 @@ void SourceCoverageViewHTML::renderBranchView(raw_ostream &OS, BranchView &BRV,
}
// Display TrueCount or TruePercent.
- std::string TrueColor = R.ExecutionCount ? "None" : "red";
+ std::string TrueColor = R.ExecutionCount ? "None" : "red branch";
std::string TrueCovClass =
(R.ExecutionCount > 0) ? "covered-line" : "uncovered-line";
@@ -969,7 +1146,7 @@ void SourceCoverageViewHTML::renderBranchView(raw_ostream &OS, BranchView &BRV,
OS << format("%0.2f", TruePercent) << "%, ";
// Display FalseCount or FalsePercent.
- std::string FalseColor = R.FalseExecutionCount ? "None" : "red";
+ std::string FalseColor = R.FalseExecutionCount ? "None" : "red branch";
std::string FalseCovClass =
(R.FalseExecutionCount > 0) ? "covered-line" : "uncovered-line";
@@ -1053,24 +1230,21 @@ void SourceCoverageViewHTML::renderTitle(raw_ostream &OS, StringRef Title) {
if (getOptions().hasCreatedTime())
OS << tag(CreatedTimeTag,
escape(getOptions().CreatedTimeStr, getOptions()));
+
+ OS << tag("span",
+ a("javascript:next_line()", "next uncovered line (L)") + ", " +
+ a("javascript:next_region()", "next uncovered region (R)") +
+ ", " +
+ a("javascript:next_branch()", "next uncovered branch (B)"),
+ "control");
}
void SourceCoverageViewHTML::renderTableHeader(raw_ostream &OS,
- unsigned FirstUncoveredLineNo,
unsigned ViewDepth) {
- std::string SourceLabel;
- if (FirstUncoveredLineNo == 0) {
- SourceLabel = tag("td", tag("pre", "Source"));
- } else {
- std::string LinkTarget = "#L" + utostr(uint64_t(FirstUncoveredLineNo));
- SourceLabel =
- tag("td", tag("pre", "Source (" +
- a(LinkTarget, "jump to first uncovered line") +
- ")"));
- }
+ std::string Links;
renderLinePrefix(OS, ViewDepth);
- OS << tag("td", tag("pre", "Line")) << tag("td", tag("pre", "Count"))
- << SourceLabel;
+ OS << tag("td", tag("pre", "Line")) << tag("td", tag("pre", "Count"));
+ OS << tag("td", tag("pre", "Source" + Links));
renderLineSuffix(OS, ViewDepth);
}
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.h b/llvm/tools/llvm-cov/SourceCoverageViewHTML.h
index 32313a3..9b7391d 100644
--- a/llvm/tools/llvm-cov/SourceCoverageViewHTML.h
+++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.h
@@ -38,6 +38,7 @@ public:
protected:
Error emitStyleSheet();
+ Error emitJavaScript();
void emitReportHeader(raw_ostream &OSRef, const std::string &Title);
private:
@@ -105,8 +106,7 @@ class SourceCoverageViewHTML : public SourceCoverageView {
void renderTitle(raw_ostream &OS, StringRef Title) override;
- void renderTableHeader(raw_ostream &OS, unsigned FirstUncoveredLineNo,
- unsigned IndentLevel) override;
+ void renderTableHeader(raw_ostream &OS, unsigned IndentLevel) override;
public:
SourceCoverageViewHTML(StringRef SourceName, const MemoryBuffer &File,
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewText.cpp b/llvm/tools/llvm-cov/SourceCoverageViewText.cpp
index 580da45..cab60c2 100644
--- a/llvm/tools/llvm-cov/SourceCoverageViewText.cpp
+++ b/llvm/tools/llvm-cov/SourceCoverageViewText.cpp
@@ -414,5 +414,4 @@ void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) {
<< getOptions().CreatedTimeStr << "\n";
}
-void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned,
- unsigned) {}
+void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned) {}
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewText.h b/llvm/tools/llvm-cov/SourceCoverageViewText.h
index 7cb47fc..25a161b 100644
--- a/llvm/tools/llvm-cov/SourceCoverageViewText.h
+++ b/llvm/tools/llvm-cov/SourceCoverageViewText.h
@@ -93,8 +93,7 @@ class SourceCoverageViewText : public SourceCoverageView {
void renderTitle(raw_ostream &OS, StringRef Title) override;
- void renderTableHeader(raw_ostream &OS, unsigned FirstUncoveredLineNo,
- unsigned IndentLevel) override;
+ void renderTableHeader(raw_ostream &OS, unsigned IndentLevel) override;
public:
SourceCoverageViewText(StringRef SourceName, const MemoryBuffer &File,