diff options
author | Hana Dusíková <hanicka@hanicka.net> | 2024-06-17 21:30:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-17 21:30:50 +0200 |
commit | 06aa078d68380bc775f0a903204fe330d50f4f1f (patch) | |
tree | 2f4fe6d8219559162866e22a4a2833de8a0556bd /llvm/tools | |
parent | c22d3917b93a6d54613d2e5b2ea4c97546144c46 (diff) | |
download | llvm-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.cpp | 3 | ||||
-rw-r--r-- | llvm/tools/llvm-cov/SourceCoverageView.h | 3 | ||||
-rw-r--r-- | llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp | 220 | ||||
-rw-r--r-- | llvm/tools/llvm-cov/SourceCoverageViewHTML.h | 4 | ||||
-rw-r--r-- | llvm/tools/llvm-cov/SourceCoverageViewText.cpp | 3 | ||||
-rw-r--r-- | llvm/tools/llvm-cov/SourceCoverageViewText.h | 3 |
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, |