[PATCH][RFC] llvm-cov HTML generation
Justin Bogner via llvm-commits
llvm-commits at lists.llvm.org
Thu Mar 10 14:57:06 PST 2016
Harlan Haskins <hhaskins at apple.com> writes:
> On Mar 10, 2016, at 1:30 PM, Ying Yi <maggieyi666 at gmail.com> wrote:
>
> Thanks for your reply and the updated patch.
>
> 1) When I use the release build of llvm-cov to generate HTML files, all
> the HTML files are named “html”. This issue also exists on UNIX.
>
> I haven’t seen this happen. Can you provide some steps to reproduce? Thanks!
>
> 2) The following warning is given when building llvm-cov using Visual
> Studio 2013.
>
> “sourcecoverageview.cpp (108): warning C4715:
> 'llvm::SourceCoverageView::create' : not all control paths return a value”
>
> That warning is incorrect. It’s a switch statement over both cases of an enum
> — I want this because if we add another format, say, OFJSON or something, I
> want a compiler warning telling me the switch case isn’t exhaustive.
The usual way to appease this warning is to throw an llvm_unreachable
after the switch.
> Here’s an updated patch with some small sanity tests added:
>
> Index: test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.covmapping
> ===================================================================
> Cannot display: file marked as a binary type.
> svn:mime-type = application/octet-stream
> Index: test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.covmapping
> ===================================================================
> --- test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.covmapping (revision 262364)
> +++ test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.covmapping (working copy)
>
> Property changes on: test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.covmapping
> ___________________________________________________________________
> Added: svn:mime-type
> ## -0,0 +1 ##
> +application/octet-stream
> \ No newline at end of property
> Index: test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.profdata
> ===================================================================
> Cannot display: file marked as a binary type.
> svn:mime-type = application/octet-stream
> Index: test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.profdata
> ===================================================================
> --- test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.profdata (revision 262364)
> +++ test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.profdata (working copy)
>
> Property changes on: test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.profdata
> ___________________________________________________________________
> Added: svn:mime-type
> ## -0,0 +1 ##
> +application/octet-stream
> \ No newline at end of property
> Index: test/tools/llvm-cov/Inputs/templateInstantiationsHTML.covmapping
> ===================================================================
> Cannot display: file marked as a binary type.
> svn:mime-type = application/octet-stream
> Index: test/tools/llvm-cov/Inputs/templateInstantiationsHTML.covmapping
> ===================================================================
> --- test/tools/llvm-cov/Inputs/templateInstantiationsHTML.covmapping (revision 262364)
> +++ test/tools/llvm-cov/Inputs/templateInstantiationsHTML.covmapping (working copy)
>
> Property changes on: test/tools/llvm-cov/Inputs/templateInstantiationsHTML.covmapping
> ___________________________________________________________________
> Added: svn:mime-type
> ## -0,0 +1 ##
> +application/octet-stream
> \ No newline at end of property
> Index: test/tools/llvm-cov/Inputs/templateInstantiationsHTML.profdata
> ===================================================================
> Cannot display: file marked as a binary type.
> svn:mime-type = application/octet-stream
> Index: test/tools/llvm-cov/Inputs/templateInstantiationsHTML.profdata
> ===================================================================
> --- test/tools/llvm-cov/Inputs/templateInstantiationsHTML.profdata (revision 262364)
> +++ test/tools/llvm-cov/Inputs/templateInstantiationsHTML.profdata (working copy)
>
> Property changes on: test/tools/llvm-cov/Inputs/templateInstantiationsHTML.profdata
> ___________________________________________________________________
> Added: svn:mime-type
> ## -0,0 +1 ##
> +application/octet-stream
> \ No newline at end of property
> Index: test/tools/llvm-cov/showLineExecutionCountsHTML.cpp
> ===================================================================
> --- test/tools/llvm-cov/showLineExecutionCountsHTML.cpp (revision 0)
> +++ test/tools/llvm-cov/showLineExecutionCountsHTML.cpp (working copy)
> @@ -0,0 +1,40 @@
> +// RUN: llvm-cov show %S/Inputs/lineExecutionCountsHTML.covmapping -format html -instr-profile %S/Inputs/lineExecutionCountsHTML.profdata -filename-equivalence %s | FileCheck -check-prefix=CHECK -check-prefix=WHOLE-FILE %s
> +// RUN: llvm-cov show %S/Inputs/lineExecutionCountsHTML.covmapping -format html -instr-profile %S/Inputs/lineExecutionCountsHTML.profdata -filename-equivalence -name=main %s | FileCheck -check-prefix=CHECK %s
> + // WHOLE-FILE: <div class='function-title'>
> + // WHOLE-FILE: showLineExecutionCounts.cpp
> + // CHECK: <td class='numeric'><pre>1</pre></td>
> + // CHECK: <td class='numeric'><pre>[[@LINE+1]]</pre></td>
> +int main() { // CHECK: <td class='code'><pre>
> + // CHECK: int main() {
> + // CHECK: </pre></td>
> + // CHECK: <td class='numeric'><pre>1</pre></td>
> + // CHECK: <td class='numeric'><pre>[[@LINE+1]]</pre></td>
> + int x = 0; // CHECK: <td class='code'><pre>
> + // CHECK: int x = 0;
> + // CHECK: </pre></td>
> + // CHECK: <td class='numeric'><pre>101</pre></td>
> + // CHECK: <td class='numeric'><pre>[[@LINE+1]]</pre></td>
> + for (int i = 0; i < 100; i++) { // CHECK: <td class='code'><pre>
> + // CHECK: for (int i = 0;
> + // CHECK: </pre></td>
> + // CHECK: <td class='numeric'><pre>100</pre></td>
> + // CHECK: <td class='numeric'><pre>[[@LINE+1]]</pre></td>
> + x++; // CHECK: <td class='code'><pre>
> + // CHECK: x++;
> + // CHECK: </pre></td>
> + }
> +
> + // CHECK: <td class='numeric'><pre>1</pre></td>
> + // CHECK: <td class='numeric'><pre>[[@LINE+1]]</pre></td>
> + if (0) { // CHECK: <td class='code'><pre>
> + // CHECK: if (0)
> + // CHECK: <span class='red'>{
> + // CHECK: }
> + // CHECK: </span>
> + // CHECK: <td class='numeric'><pre>0</pre></td>
> + // CHECK: <td class='numeric'><pre>[[@LINE+1]]</pre></td>
> + x += 10; // CHECK: <td class='code'><pre>
> + // CHECK: x += 10;
> + }
> + return 0;
> +}
> Index: test/tools/llvm-cov/showTemplateInstantiationsHTML.cpp
> ===================================================================
> --- test/tools/llvm-cov/showTemplateInstantiationsHTML.cpp (revision 0)
> +++ test/tools/llvm-cov/showTemplateInstantiationsHTML.cpp (working copy)
> @@ -0,0 +1,25 @@
> +// RUN: llvm-cov show %S/Inputs/templateInstantiationsHTML.covmapping -format html -instr-profile %S/Inputs/templateInstantiationsHTML.profdata -filename-equivalence %s | FileCheck -check-prefix=CHECK %s
> +// RUN: llvm-cov show %S/Inputs/templateInstantiationsHTML.covmapping -format html -instr-profile %S/Inputs/templateInstantiationsHTML.profdata -filename-equivalence -name=_Z4funcIbEiT_ %s | FileCheck -check-prefix=CHECK %s
> +
> +// before coverage
> + // WHOLE-FILE: // before
> + // FILTER-NOT: // before
> + // CHECK: <!doctype html>
> +template<typename T> // WHOLE-FILE: <div class='expansion-view'>
> +int func(T x) { // CHECK: <span class='red'>
> + if(x) // CHECK: return 0
> + return 0; // WHOLE-FILE: </div>
> + else
> + return 1;
> + int j = 1;
> +}
> +
> +int main() {
> + func<int>(0);
> + func<bool>(true);
> + return 0;
> +}
> +// after coverage
> +
> + // WHOLE-FILE: // after
> + // FILTER-NOT: // after
> Index: tools/llvm-cov/CodeCoverage.cpp
> ===================================================================
> --- tools/llvm-cov/CodeCoverage.cpp (revision 262364)
> +++ tools/llvm-cov/CodeCoverage.cpp (working copy)
> @@ -51,6 +51,10 @@
> /// \brief Print the error message to the error output stream.
> void error(const Twine &Message, StringRef Whence = "");
>
> + /// \brief Return a pointer to a stream for the given file.
> + /// Passing "" yields stdout.
> + std::unique_ptr<raw_ostream> getStreamForFile(StringRef Filename);
> +
> /// \brief Return a memory buffer for the given source file.
> ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile);
>
> @@ -100,6 +104,36 @@
> errs() << Message << "\n";
> }
>
> +std::unique_ptr<raw_ostream>
> +CodeCoverageTool::getStreamForFile(StringRef Filename) {
> + if (Filename == "") {
> + return llvm::make_unique<raw_fd_ostream>(fileno(stdout), false);
> + }
> +
> + std::error_code Error;
> + auto OS = llvm::make_unique<raw_fd_ostream>(Filename, Error, sys::fs::F_RW);
> + if (Error) {
> + error(Error.message(), "could not get stream for " + std::string(Filename));
> + return nullptr;
> + }
> +
> + return std::move(OS);
> +}
> +
> +llvm::SmallString<128> getOutputPathForFile(StringRef OutputDir,
> + StringRef SourceFile,
> + StringRef FileExt) {
> + auto Basename = sys::path::relative_path(sys::path::parent_path(SourceFile));
> + auto Filename = sys::path::filename(SourceFile) + "." + FileExt;
> + llvm::SmallString<128> Path(OutputDir);
> + if (Path != "") {
> + sys::path::append(Path, Basename);
> + sys::fs::create_directories(Path);
> + sys::path::append(Path, Filename);
> + }
> + return Path;
> +}
> +
> ErrorOr<const MemoryBuffer &>
> CodeCoverageTool::getSourceFile(StringRef SourceFile) {
> // If we've remapped filenames, look up the real location for this file.
> @@ -135,8 +169,9 @@
> continue;
>
> auto SubViewExpansions = ExpansionCoverage.getExpansions();
> - auto SubView = llvm::make_unique<SourceCoverageView>(
> - SourceBuffer.get(), ViewOpts, std::move(ExpansionCoverage));
> + auto SubView = SourceCoverageView::create(SourceBuffer.get(), ViewOpts,
> + Expansion.Function.Name,
> + std::move(ExpansionCoverage));
> attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
> View.addExpansion(Expansion.Region, std::move(SubView));
> }
> @@ -153,8 +188,8 @@
> return nullptr;
>
> auto Expansions = FunctionCoverage.getExpansions();
> - auto View = llvm::make_unique<SourceCoverageView>(
> - SourceBuffer.get(), ViewOpts, std::move(FunctionCoverage));
> + auto View = SourceCoverageView::create(
> + SourceBuffer.get(), ViewOpts, Function.Name, std::move(FunctionCoverage));
> attachExpansionSubViews(*View, Expansions, Coverage);
>
> return View;
> @@ -171,15 +206,16 @@
> return nullptr;
>
> auto Expansions = FileCoverage.getExpansions();
> - auto View = llvm::make_unique<SourceCoverageView>(
> - SourceBuffer.get(), ViewOpts, std::move(FileCoverage));
> + auto View = SourceCoverageView::create(SourceBuffer.get(), ViewOpts,
> + SourceFile, std::move(FileCoverage));
> attachExpansionSubViews(*View, Expansions, Coverage);
>
> for (auto Function : Coverage.getInstantiations(SourceFile)) {
> auto SubViewCoverage = Coverage.getCoverageForFunction(*Function);
> auto SubViewExpansions = SubViewCoverage.getExpansions();
> - auto SubView = llvm::make_unique<SourceCoverageView>(
> - SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage));
> + auto SubView =
> + SourceCoverageView::create(SourceBuffer.get(), ViewOpts, Function->Name,
> + std::move(SubViewCoverage));
> attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
>
> if (SubView) {
> @@ -315,9 +351,16 @@
> ViewOpts.Debug = DebugDump;
> CompareFilenamesOnly = FilenameEquivalence;
>
> - ViewOpts.Colors = UseColor == cl::BOU_UNSET
> - ? sys::Process::StandardOutHasColors()
> - : UseColor == cl::BOU_TRUE;
> + switch (ViewOpts.Format) {
> + case CoverageViewOptions::OFText:
> + ViewOpts.Colors = UseColor == cl::BOU_UNSET
> + ? sys::Process::StandardOutHasColors()
> + : UseColor == cl::BOU_TRUE;
> + break;
> + case CoverageViewOptions::OFHTML:
> + ViewOpts.Colors = true;
> + break;
> + }
>
> // Create the function filters
> if (!NameFilters.empty() || !NameRegexFilters.empty()) {
> @@ -406,6 +449,18 @@
> cl::desc("Show function instantiations"),
> cl::cat(ViewCategory));
>
> + cl::opt<std::string> OutputDirectory(
> + "output-dir", cl::Optional,
> + cl::desc("Directory to output individual files"));
> +
> + cl::opt<CoverageViewOptions::OutputFormat> Format(
> + "format", cl::desc("Format to output coverage"),
> + cl::values(clEnumValN(CoverageViewOptions::OFHTML, "html", "HTML output"),
> + clEnumValN(CoverageViewOptions::OFText, "text",
> + "Textual table output"),
> + clEnumValEnd),
> + cl::init(CoverageViewOptions::OFText));
> +
> auto Err = commandLineParser(argc, argv);
> if (Err)
> return Err;
> @@ -417,12 +472,25 @@
> ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts;
> ViewOpts.ShowExpandedRegions = ShowExpansions;
> ViewOpts.ShowFunctionInstantiations = ShowInstantiations;
> + ViewOpts.Format = Format;
>
> auto Coverage = load();
> if (!Coverage)
> return 1;
>
> + bool isHTML = Format == CoverageViewOptions::OFHTML;
> + StringRef FileExt = isHTML ? "html" : "txt";
> +
> if (!Filters.empty()) {
> + auto Path = getOutputPathForFile(OutputDirectory, "functions", FileExt);
> + auto OS = getStreamForFile(Path);
> + if (!OS)
> + return 1;
> +
> + if (isHTML) {
> + SourceCoverageViewHTML::renderFileHeader(*OS);
> + }
> +
> // Show functions
> for (const auto &Function : Coverage->getCoveredFunctions()) {
> if (!Filters.matches(Function))
> @@ -435,39 +503,48 @@
> outs() << "\n";
> continue;
> }
> - ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << Function.Name
> - << ":";
> - outs() << "\n";
> - mainView->render(outs(), /*WholeFile=*/false);
> - outs() << "\n";
> + mainView->render(*OS, /*WholeFile=*/false, /*RenderTitle=*/true);
> + *OS << "\n";
> }
> +
> + if (isHTML) {
> + SourceCoverageViewHTML::renderFileFooter(*OS);
> + }
> return 0;
> }
>
> // Show files
> - bool ShowFilenames = SourceFiles.size() != 1;
> + bool ShowFilenames = isHTML || SourceFiles.size() != 1;
>
> if (SourceFiles.empty())
> // Get the source files from the function coverage mapping
> for (StringRef Filename : Coverage->getUniqueSourceFiles())
> SourceFiles.push_back(Filename);
>
> + if (isHTML && OutputDirectory != "") {
> + // Render an index with a list of files.
> + auto Path = getOutputPathForFile(OutputDirectory, "index", FileExt);
> + auto OS = getStreamForFile(Path);
> + if (!OS)
> + return 1;
> + SourceCoverageViewHTML::renderIndex(*OS, SourceFiles);
> + }
> +
> for (const auto &SourceFile : SourceFiles) {
> + auto Path = getOutputPathForFile(OutputDirectory, SourceFile, FileExt);
> + auto OS = getStreamForFile(Path);
> + if (!OS)
> + return 1;
> auto mainView = createSourceFileView(SourceFile, *Coverage);
> if (!mainView) {
> - ViewOpts.colored_ostream(outs(), raw_ostream::RED)
> + ViewOpts.colored_ostream(errs(), raw_ostream::RED)
> << "warning: The file '" << SourceFile << "' isn't covered.";
> - outs() << "\n";
> + errs() << "\n";
> continue;
> }
> -
> - if (ShowFilenames) {
> - ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":";
> - outs() << "\n";
> - }
> - mainView->render(outs(), /*Wholefile=*/true);
> + mainView->render(*OS, /*Wholefile=*/true, /*RenderTitle=*/ShowFilenames);
> if (SourceFiles.size() > 1)
> - outs() << "\n";
> + *OS << "\n";
> }
>
> return 0;
> Index: tools/llvm-cov/CoverageCSS.inc
> ===================================================================
> --- tools/llvm-cov/CoverageCSS.inc (revision 0)
> +++ tools/llvm-cov/CoverageCSS.inc (working copy)
> @@ -0,0 +1,121 @@
> +//===-------- CoverageCSS.inc - css file for HTML coverage reports --------===//
> +//
> +// The LLVM Compiler Infrastructure
> +//
> +// This file is distributed under the University of Illinois Open Source
> +// License. See LICENSE.TXT for details.
> +//
> +//===----------------------------------------------------------------------===//
> +//
> +// This file has a raw string literal that is inserted as a CSS style into
> +// HTML documents generated by llvm-cov.
> +//
> +// This file includes the necessary styling for centered tables of code and
> +// tooltip generation.
> +//
> +//===----------------------------------------------------------------------===//
> +
> +StringRef CoverageCSS = R"(.red {
> + background-color: #FFD0D0;
> +}
> +.cyan {
> + background-color: cyan;
> +}
> +.black {
> + background-color: black;
> + color: white;
> +}
> +.green {
> + background-color: #98FFA6;
> + color: white;
> +}
> +.magenta {
> + background-color: #F998FF;
> + color: white;
> +}
> +body {
> + font-family: -apple-system, sans-serif;
> +}
> +pre {
> + margin-top: 0px !important;
> + margin-bottom: 0px !important;
> +}
> +.function-title {
> + padding: 5px 10px;
> + border-bottom: 1px solid #dbdbdb;
> + background-color: #eee;
> +}
> +.centered {
> + display: table;
> + margin-left: auto;
> + margin-right: auto;
> + border: 1px solid #dbdbdb;
> + border-radius: 3px;
> +}
> +.expansion-view {
> + background-color: rgba(0, 0, 0, 0);
> + margin-left: 0px;
> + margin-top: 5px;
> + margin-right: 5px;
> + margin-bottom: 5px;
> + border: 1px solid #dbdbdb;
> + border-radius: 3px;
> +}
> +table {
> + border-collapse: collapse;
> +}
> +.numeric {
> + text-align: right;
> + color: #aaa;
> +}
> +.tooltips {
> + position: relative;
> + display: inline;
> + background-color: #FFFBD2;
> + text-decoration: none;
> +}
> +.tooltips span.tooltip-content {
> + position: absolute;
> + width:140px;
> + color: #FFFFFF;
> + background: #000000;
> + height: 30px;
> + line-height: 30px;
> + text-align: center;
> + visibility: hidden;
> + border-radius: 6px;
> +}
> +.tooltips span.tooltip-content:after {
> + content: '';
> + position: absolute;
> + top: 100%;
> + left: 50%;
> + margin-left: -8px;
> + width: 0; height: 0;
> + border-top: 8px solid #000000;
> + border-right: 8px solid transparent;
> + border-left: 8px solid transparent;
> +}
> +:hover.tooltips span.tooltip-content {
> + visibility: visible;
> + opacity: 0.8;
> + bottom: 30px;
> + left: 50%;
> + margin-left: -76px;
> + z-index: 999;
> +}
> +th, td {
> + vertical-align: top;
> + padding: 2px 5px;
> + border-collapse: collapse;
> + border-right: solid 1px #eee;
> + border-left: solid 1px #eee;
> +}
> +
> +td:first-child {
> + border-left: none;
> +}
> +td:last-child {
> + border-right: none;
> +}
> +})";
> Index: tools/llvm-cov/CoverageViewOptions.h
> ===================================================================
> --- tools/llvm-cov/CoverageViewOptions.h (revision 262364)
> +++ tools/llvm-cov/CoverageViewOptions.h (working copy)
> @@ -16,6 +16,12 @@
>
> /// \brief The options for displaying the code coverage information.
> struct CoverageViewOptions {
> + enum OutputFormat {
> + /// \brief ASCII table output.
> + OFText,
> + /// \brief An HTML directory.
> + OFHTML
> + };
> bool Debug;
> bool Colors;
> bool ShowLineNumbers;
> @@ -25,6 +31,7 @@
> bool ShowExpandedRegions;
> bool ShowFunctionInstantiations;
> bool ShowFullFilenames;
> + OutputFormat Format;
>
> /// \brief Change the output's stream color if the colors are enabled.
> ColoredRawOstream colored_ostream(raw_ostream &OS,
> Index: tools/llvm-cov/SourceCoverageView.cpp
> ===================================================================
> --- tools/llvm-cov/SourceCoverageView.cpp (revision 262364)
> +++ tools/llvm-cov/SourceCoverageView.cpp (working copy)
> @@ -12,13 +12,123 @@
> //===----------------------------------------------------------------------===//
>
> #include "SourceCoverageView.h"
> -#include "llvm/ADT/Optional.h"
> #include "llvm/ADT/SmallString.h"
> #include "llvm/ADT/StringExtras.h"
> -#include "llvm/Support/LineIterator.h"
> +#include "llvm/Support/Path.h"
>
> using namespace llvm;
>
> +namespace html {
> +/// An HTML-escaped string.
> +std::string escape(std::string Str) {
> + std::string Result;
> + for (size_t i = 0; i < Str.size(); ++i) {
> + std::string Token = Str.substr(i, 1);
> + if (Token == "&")
> + Token = "&";
> + else if (Token == "<")
> + Token = "<";
> + else if (Token == "\"")
> + Token = """;
> + else if (Token == ">")
> + Token = ">";
> + Result += Token;
> + }
> + return Result;
> +}
> +
> +/// Creates a tag around the provided text
> +/// (expects the value to be properly escaped).
> +std::string tag(std::string Name, std::string Text,
> + std::string ClassName = "") {
> + std::string Tag = "<" + Name;
> + if (ClassName != "") {
> + Tag += " class='" + ClassName + "'";
> + }
> + return Tag + ">" + Text + "</" + Name + ">";
> +}
> +
> +std::string a(std::string Link, std::string Text) {
> + return "<a href='" + Link + "'>" + Text + "</a>";
> +}
> +} // end namespace html
> +
> +std::string classForColor(raw_ostream::Colors Color) {
> + switch (Color) {
> + case raw_ostream::RED:
> + return "red";
> + case raw_ostream::CYAN:
> + return "cyan";
> + case raw_ostream::BLUE:
> + return "blue";
> + case raw_ostream::BLACK:
> + return "black";
> + case raw_ostream::GREEN:
> + return "green";
> + case raw_ostream::MAGENTA:
> + return "magenta";
> + default:
> + return "clear";
> + }
> +}
> +
> +/// Format a count using engineering notation with 3 significant digits.
> +std::string formatCount(uint64_t N) {
> + std::string Number = utostr(N);
> + int Len = Number.size();
> + if (Len <= 3)
> + return Number;
> + int IntLen = Len % 3 == 0 ? 3 : Len % 3;
> + std::string Result(Number.data(), IntLen);
> + if (IntLen != 3) {
> + Result.push_back('.');
> + Result += Number.substr(IntLen, 3 - IntLen);
> + }
> + Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
> + return Result;
> +}
> +
> +//===----------------------------------------------------------------------===//
> +//
> +// SourceCoverageView
> +//
> +//===----------------------------------------------------------------------===//
> +
> +std::unique_ptr<SourceCoverageView> SourceCoverageView::create(
> + const MemoryBuffer &File, const CoverageViewOptions &Options,
> + StringRef SourceName, coverage::CoverageData &&CoverageInfo) {
> + switch (Options.Format) {
> + case CoverageViewOptions::OFHTML:
> + return llvm::make_unique<SourceCoverageViewHTML>(File, Options, SourceName,
> + std::move(CoverageInfo));
> + case CoverageViewOptions::OFText:
> + return llvm::make_unique<SourceCoverageViewText>(File, Options, SourceName,
> + std::move(CoverageInfo));
> + }
> +}
> +
> +void SourceCoverageView::renderExpansionView(
> + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> + bool RenderedSubview, unsigned IndentLevel, unsigned NestedIndent,
> + unsigned CombinedColumnWidth, unsigned DividerWidth) {
> +
> + if (Options.Debug)
> + errs() << "Expansion at line " << ExpansionView.getLine() << ", "
> + << ExpansionView.getStartCol() << " -> " << ExpansionView.getEndCol()
> + << "\n";
> + ExpansionView.View->render(OS, /*WholeFile=*/false,
> + /*RenderTitle*/ false, NestedIndent);
> +}
> +
> +void SourceCoverageView::renderInstantiationView(
> + raw_ostream &OS, InstantiationView &InstantiationView, unsigned IndentLevel,
> + unsigned NestedIndent, unsigned DividerWidth) {
> + InstantiationView.View->render(OS, /*WholeFile=*/false,
> + /*RenderTitle=*/true, NestedIndent);
> +}
> +
> void SourceCoverageView::renderLine(
> raw_ostream &OS, StringRef Line, int64_t LineNumber,
> const coverage::CoverageSegment *WrappedSegment,
> @@ -35,9 +145,8 @@
> unsigned Col = 1;
> for (const auto *S : Segments) {
> unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1);
> - colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
> - Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
> - << Line.substr(Col - 1, End - Col);
> + auto Text = Line.substr(Col - 1, End - Col);
> + output(OS, Text, Highlight, S);
> if (Options.Debug && Highlight)
> HighlightedRanges.push_back(std::make_pair(Col, End));
> Col = End;
> @@ -49,11 +158,8 @@
> Highlight = None;
> }
>
> - // Show the rest of the line
> - colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
> - Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
> - << Line.substr(Col - 1, Line.size() - Col + 1);
> - OS << "\n";
> + auto Rest = Line.substr(Col - 1, Line.size() - Col + 1);
> + output(OS, Rest, Highlight, nullptr);
>
> if (Options.Debug) {
> for (const auto &Range : HighlightedRanges)
> @@ -64,94 +170,12 @@
> }
> }
>
> -void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) {
> - for (unsigned I = 0; I < Level; ++I)
> - OS << " |";
> -}
> -
> -void SourceCoverageView::renderViewDivider(unsigned Level, unsigned Length,
> - raw_ostream &OS) {
> - assert(Level != 0 && "Cannot render divider at top level");
> - renderIndent(OS, Level - 1);
> - OS.indent(2);
> - for (unsigned I = 0; I < Length; ++I)
> - OS << "-";
> -}
> -
> -/// Format a count using engineering notation with 3 significant digits.
> -static std::string formatCount(uint64_t N) {
> - std::string Number = utostr(N);
> - int Len = Number.size();
> - if (Len <= 3)
> - return Number;
> - int IntLen = Len % 3 == 0 ? 3 : Len % 3;
> - std::string Result(Number.data(), IntLen);
> - if (IntLen != 3) {
> - Result.push_back('.');
> - Result += Number.substr(IntLen, 3 - IntLen);
> - }
> - Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
> - return Result;
> -}
> -
> -void
> -SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS,
> - const LineCoverageInfo &Line) {
> - if (!Line.isMapped()) {
> - OS.indent(LineCoverageColumnWidth) << '|';
> - return;
> - }
> - std::string C = formatCount(Line.ExecutionCount);
> - OS.indent(LineCoverageColumnWidth - C.size());
> - colored_ostream(OS, raw_ostream::MAGENTA,
> - Line.hasMultipleRegions() && Options.Colors)
> - << C;
> - OS << '|';
> -}
> -
> -void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS,
> - unsigned LineNo) {
> - SmallString<32> Buffer;
> - raw_svector_ostream BufferOS(Buffer);
> - BufferOS << LineNo;
> - auto Str = BufferOS.str();
> - // Trim and align to the right
> - Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
> - OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
> -}
> -
> -void SourceCoverageView::renderRegionMarkers(
> - raw_ostream &OS, ArrayRef<const coverage::CoverageSegment *> Segments) {
> - unsigned PrevColumn = 1;
> - for (const auto *S : Segments) {
> - if (!S->IsRegionEntry)
> - continue;
> - // Skip to the new region
> - if (S->Col > PrevColumn)
> - OS.indent(S->Col - PrevColumn);
> - PrevColumn = S->Col + 1;
> - std::string C = formatCount(S->Count);
> - PrevColumn += C.size();
> - OS << '^' << C;
> - }
> - OS << "\n";
> -
> - if (Options.Debug)
> - for (const auto *S : Segments)
> - errs() << "Marker at " << S->Line << ":" << S->Col << " = "
> - << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n");
> -}
> -
> void SourceCoverageView::render(raw_ostream &OS, bool WholeFile,
> - unsigned IndentLevel) {
> + bool RenderTitle, unsigned IndentLevel) {
> // The width of the leading columns
> unsigned CombinedColumnWidth =
> (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
> (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
> - // The width of the line that is used to divide between the view and the
> - // subviews.
> - unsigned DividerWidth = CombinedColumnWidth + 4;
> -
> // We need the expansions and instantiations sorted so we can go through them
> // while we iterate lines.
> std::sort(ExpansionSubViews.begin(), ExpansionSubViews.end());
> @@ -191,10 +215,10 @@
> LineCount.addRegionCount(WrappedSegment->Count);
> for (const auto *S : LineSegments)
> if (S->HasCount && S->IsRegionEntry)
> - LineCount.addRegionStartCount(S->Count);
> + LineCount.addRegionStartCount(S->Count);
>
> - // Render the line prefix.
> - renderIndent(OS, IndentLevel);
> + startLine(OS, IndentLevel);
> +
> if (Options.ShowLineStats)
> renderLineCoverageColumn(OS, LineCount);
> if (Options.ShowLineNumbers)
> @@ -202,8 +226,7 @@
>
> // If there are expansion subviews, we want to highlight the first one.
> unsigned ExpansionColumn = 0;
> - if (NextESV != EndESV && NextESV->getLine() == LI.line_number() &&
> - Options.Colors)
> + if (NextESV != EndESV && NextESV->getLine() == LI.line_number())
> ExpansionColumn = NextESV->getStartCol();
>
> // Display the source code for the current line.
> @@ -214,51 +237,357 @@
> if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers ||
> LineCount.hasMultipleRegions()) &&
> !LineSegments.empty()) {
> - renderIndent(OS, IndentLevel);
> - OS.indent(CombinedColumnWidth);
> - renderRegionMarkers(OS, LineSegments);
> + renderRegionMarkers(OS, LineSegments, IndentLevel, CombinedColumnWidth);
> +
> + if (Options.Debug)
> + for (const auto *S : LineSegments)
> + errs() << "Marker at " << S->Line << ":" << S->Col << " = "
> + << formatCount(S->Count)
> + << (S->IsRegionEntry ? "\n" : " (pop)\n");
> }
> + // The width of the line that is used to divide between the view and the
> + // subviews.
> + unsigned DividerWidth = CombinedColumnWidth + 4;
>
> // Show the expansions and instantiations for this line.
> unsigned NestedIndent = IndentLevel + 1;
> bool RenderedSubView = false;
> for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
> ++NextESV) {
> - renderViewDivider(NestedIndent, DividerWidth, OS);
> - OS << "\n";
> - if (RenderedSubView) {
> - // Re-render the current line and highlight the expansion range for
> - // this subview.
> - ExpansionColumn = NextESV->getStartCol();
> - renderIndent(OS, IndentLevel);
> - OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
> - renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
> - ExpansionColumn);
> - renderViewDivider(NestedIndent, DividerWidth, OS);
> - OS << "\n";
> - }
> - // Render the child subview
> - if (Options.Debug)
> - errs() << "Expansion at line " << NextESV->getLine() << ", "
> - << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
> - << "\n";
> - NextESV->View->render(OS, false, NestedIndent);
> + renderExpansionView(OS, LI, *NextESV, WrappedSegment, LineSegments,
> + RenderedSubView, IndentLevel, NestedIndent,
> + CombinedColumnWidth, DividerWidth);
> RenderedSubView = true;
> }
> for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
> - renderViewDivider(NestedIndent, DividerWidth, OS);
> - OS << "\n";
> - renderIndent(OS, NestedIndent);
> - OS << ' ';
> - Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName
> - << ":";
> - OS << "\n";
> - NextISV->View->render(OS, false, NestedIndent);
> + renderInstantiationView(OS, *NextISV, IndentLevel, NestedIndent,
> + DividerWidth);
> RenderedSubView = true;
> }
> if (RenderedSubView) {
> - renderViewDivider(NestedIndent, DividerWidth, OS);
> - OS << "\n";
> + finishRenderingSubviews(OS, NestedIndent, DividerWidth);
> }
> +
> + endLine(OS);
> }
> }
> +
> +void SourceCoverageView::renderRegionMarkers(
> + raw_ostream &OS, ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned IndentLevel, unsigned CombinedColumnWidth) {
> + OS.indent(CombinedColumnWidth);
> + unsigned PrevColumn = 1;
> + for (const auto *S : Segments) {
> + if (!S->IsRegionEntry)
> + continue;
> + // Skip to the new region
> + if (S->Col > PrevColumn)
> + OS.indent(S->Col - PrevColumn);
> + PrevColumn = S->Col + 1;
> + std::string C = formatCount(S->Count);
> + PrevColumn += C.size();
> + OS << '^' << C;
> + }
> + OS << "\n";
> +}
> +
> +//===----------------------------------------------------------------------===//
> +//
> +// SourceCoverageViewHTML
> +//
> +//===----------------------------------------------------------------------===//
> +
> +void SourceCoverageViewHTML::output(raw_ostream &OS, StringRef Text,
> + Optional<raw_ostream::Colors> Highlight,
> + const coverage::CoverageSegment *Segment) {
> + auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR;
> + std::string EscapedText = html::escape(Text);
> + if (Highlight) {
> + EscapedText = html::tag("span", EscapedText, classForColor(Color));
> + }
> + if (Options.ShowRegionMarkers && Segment && Segment->IsRegionEntry) {
> + std::string C = formatCount(Segment->Count);
> + auto CountTag = html::tag("span", html::escape(C), "tooltip-content");
> + EscapedText = html::tag("div", EscapedText + CountTag, "tooltips");
> + }
> + OS << EscapedText;
> +}
> +
> +void SourceCoverageViewHTML::renderLine(
> + raw_ostream &OS, StringRef Line, int64_t LineNumber,
> + const coverage::CoverageSegment *WrappedSegment,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned int ExpansionCol) {
> + OS << "<td class='code'><pre>\n";
> + SourceCoverageView::renderLine(OS, Line, LineNumber, WrappedSegment, Segments,
> + ExpansionCol);
> + OS << "</pre>\n";
> + if (ExpansionSubViews.empty() && InstantiationSubViews.empty()) {
> + // don't end the source line if we're going to embed sub-tables.
> + OS << "</td>\n";
> + }
> +}
> +
> +void SourceCoverageViewHTML::renderTitle(raw_ostream &OS) {
> + OS << "<div class='function-title'>\n"
> + << " <pre>" << std::string(SourceName) << ":</pre>"
> + << "</div>\n";
> +}
> +
> +void SourceCoverageViewHTML::renderLineCoverageColumn(
> + raw_ostream &OS, const LineCoverageInfo &Line) {
> + if (Line.isMapped()) {
> + std::string C = formatCount(Line.ExecutionCount);
> + OS << html::tag("td", html::tag("pre", html::escape(C)), "numeric") << "\n";
> + } else {
> + OS << html::tag("td", "", "numeric") << "\n";
> + }
> +}
> +
> +void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
> + unsigned int LineNo) {
> + SmallString<32> Buffer;
> + raw_svector_ostream BufferOS(Buffer);
> + BufferOS << LineNo;
> + auto Str = BufferOS.str();
> + OS << html::tag("td", html::tag("pre", Str), "numeric") << "\n";
> +}
> +
> +void SourceCoverageViewHTML::renderExpansionView(
> + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> + bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent,
> + unsigned CombinedColumnWidth, unsigned DividerWidth) {
> + // Render the child subview
> +
> + if (RenderedSubView) {
> + // Re-render the current line and highlight the expansion range for
> + // this subview.
> + unsigned ExpansionColumn = ExpansionView.getStartCol();
> + OS << "<pre>";
> + SourceCoverageView::renderLine(OS, *LI, LI.line_number(), WrappedSegment,
> + LineSegments, ExpansionColumn);
> + OS << "</pre>\n";
> + }
> +
> + OS << "<div class='expansion-view'>\n";
> + SourceCoverageView::renderExpansionView(
> + OS, LI, ExpansionView, WrappedSegment, LineSegments, RenderedSubView,
> + IndentLevel, NestedIndent, CombinedColumnWidth, DividerWidth);
> + OS << "</div>\n";
> +}
> +
> +void SourceCoverageViewHTML::renderInstantiationView(
> + llvm::raw_ostream &OS, llvm::InstantiationView &InstantiationView,
> + unsigned IndentLevel, unsigned NestedIndent, unsigned DividerWidth) {
> + OS << "<div class='expansion-view'>\n";
> + SourceCoverageView::renderInstantiationView(
> + OS, InstantiationView, IndentLevel, NestedIndent, DividerWidth);
> + OS << "</div>\n";
> +}
> +
> +void SourceCoverageViewHTML::finishRenderingSubviews(raw_ostream &OS,
> + unsigned IndentLevel,
> + unsigned DividerWidth) {
> + OS << "</td>\n";
> +}
> +
> +void SourceCoverageViewHTML::renderFileHeader(raw_ostream &OS) {
> +// bring in the `CoverageCSS` string declared in CoverageCSS.inc
> +#include "CoverageCSS.inc"
> +
> + OS << "<!doctype html>\n"
> + "<html>\n"
> + " <head>\n"
> + " <meta name='viewport'"
> + "content='width=device-width,initial-scale=1'>\n"
> + " <meta charset='UTF-8'>"
> + " <style>\n"
> + << CoverageCSS << " </style>\n"
> + " </head>\n"
> + " <body>\n";
> +}
> +
> +void SourceCoverageViewHTML::renderFileFooter(raw_ostream &OS) {
> + OS << " </body>\n"
> + "</html>\n";
> +}
> +
> +void SourceCoverageViewHTML::render(raw_ostream &OS, bool WholeFile,
> + bool RenderTitle, unsigned IndentLevel) {
> + if (WholeFile) {
> + renderFileHeader(OS);
> + OS << "<div class='centered'>\n";
> + }
> +
> + if (RenderTitle) {
> + renderTitle(OS);
> + }
> +
> + OS << "<table>";
> + SourceCoverageView::render(OS, WholeFile, IndentLevel);
> + OS << "</table>\n";
> + if (WholeFile) {
> + OS << "</div>\n";
> + }
> + if (WholeFile) {
> + renderFileFooter(OS);
> + }
> +}
> +
> +void SourceCoverageViewHTML::startLine(raw_ostream &OS, unsigned IndentLevel) {
> + OS << "<tr>\n";
> +}
> +
> +void SourceCoverageViewHTML::endLine(raw_ostream &OS) { OS << "</tr>\n"; }
> +
> +void SourceCoverageViewHTML::renderIndex(
> + raw_ostream &OS, std::vector<std::string> &SourceFiles) {
> + SourceCoverageViewHTML::renderFileHeader(OS);
> + OS << "<div class='centered'>\n"
> + "<div class='function-title'>Index</div>\n"
> + "<table>\n";
> + for (auto &SourceFile : SourceFiles) {
> + std::string Relative = sys::path::relative_path(SourceFile);
> + std::string Link = Relative + ".html";
> + OS << html::tag(
> + "tr",
> + html::tag("td", html::tag("pre", html::a(Link, Relative)), "code"));
> + OS << "\n";
> + }
> + OS << "</table>\n"
> + "</div>\n";
> + SourceCoverageViewHTML::renderFileFooter(OS);
> +}
> +
> +//===----------------------------------------------------------------------===//
> +//
> +// SourceCoverageViewText
> +//
> +//===----------------------------------------------------------------------===//
> +
> +void SourceCoverageViewText::output(raw_ostream &OS, StringRef Text,
> + Optional<raw_ostream::Colors> Highlight,
> + const coverage::CoverageSegment *Segment) {
> + auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR;
> + colored_ostream(OS, Color, Options.Colors && Highlight, /*Bold=*/false,
> + /*BG=*/true)
> + << Text;
> +}
> +
> +void SourceCoverageViewText::render(raw_ostream &OS, bool WholeFile,
> + bool RenderTitle, unsigned IndentLevel) {
> + if (RenderTitle)
> + renderTitle(OS);
> + SourceCoverageView::render(OS, WholeFile, RenderTitle, IndentLevel);
> +}
> +
> +void SourceCoverageViewText::renderTitle(raw_ostream &OS) {
> + Options.colored_ostream(OS, raw_ostream::CYAN) << SourceName << ":\n";
> +}
> +
> +void SourceCoverageViewText::renderLine(
> + raw_ostream &OS, StringRef Line, int64_t LineNumber,
> + const coverage::CoverageSegment *WrappedSegment,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned int ExpansionCol) {
> + SourceCoverageView::renderLine(OS, Line, LineNumber, WrappedSegment, Segments,
> + ExpansionCol);
> + OS << "\n";
> +}
> +
> +void SourceCoverageViewText::renderIndent(raw_ostream &OS, unsigned Level) {
> + for (unsigned I = 0; I < Level; ++I)
> + OS << " |";
> +}
> +
> +void SourceCoverageViewText::renderViewDivider(unsigned Level, unsigned Length,
> + raw_ostream &OS) {
> + assert(Level != 0 && "Cannot render divider at top level");
> + renderIndent(OS, Level - 1);
> + OS.indent(2);
> + for (unsigned I = 0; I < Length; ++I)
> + OS << "-";
> +}
> +
> +void SourceCoverageViewText::renderLineCoverageColumn(
> + raw_ostream &OS, const LineCoverageInfo &Line) {
> +
> + std::string C = formatCount(Line.ExecutionCount);
> + if (!Line.isMapped()) {
> + OS.indent(LineCoverageColumnWidth) << '|';
> + return;
> + }
> + OS.indent(LineCoverageColumnWidth - C.size());
> + colored_ostream(OS, raw_ostream::MAGENTA,
> + Line.hasMultipleRegions() && Options.Colors)
> + << C;
> + OS << '|';
> +}
> +
> +void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS,
> + unsigned LineNo) {
> + SmallString<32> Buffer;
> + raw_svector_ostream BufferOS(Buffer);
> + BufferOS << LineNo;
> + auto Str = BufferOS.str();
> + // Trim and align to the right
> + Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
> + OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
> +}
> +
> +void SourceCoverageViewText::renderRegionMarkers(
> + raw_ostream &OS, ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned IndentLevel, unsigned CombinedColumnWidth) {
> + renderIndent(OS, IndentLevel);
> + SourceCoverageView::renderRegionMarkers(OS, Segments, IndentLevel,
> + CombinedColumnWidth);
> +}
> +
> +void SourceCoverageViewText::startLine(raw_ostream &OS, unsigned IndentLevel) {
> + renderIndent(OS, IndentLevel);
> +}
> +
> +void SourceCoverageViewText::renderInstantiationView(
> + raw_ostream &OS, InstantiationView &InstantiationView, unsigned IndentLevel,
> + unsigned NestedIndent, unsigned DividerWidth) {
> + renderViewDivider(NestedIndent, DividerWidth, OS);
> + OS << "\n";
> + renderIndent(OS, NestedIndent);
> + OS << ' ';
> + SourceCoverageView::renderInstantiationView(
> + OS, InstantiationView, IndentLevel, NestedIndent, DividerWidth);
> +}
> +
> +void SourceCoverageViewText::finishRenderingSubviews(raw_ostream &OS,
> + unsigned IndentLevel,
> + unsigned DividerWidth) {
> + renderViewDivider(IndentLevel, DividerWidth, OS);
> + OS << "\n";
> +}
> +
> +void SourceCoverageViewText::renderExpansionView(
> + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> + bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent,
> + unsigned CombinedColumnWidth, unsigned DividerWidth) {
> +
> + renderViewDivider(NestedIndent, DividerWidth, OS);
> + OS << "\n";
> + if (RenderedSubView) {
> + // Re-render the current line and highlight the expansion range for
> + // this subview.
> + unsigned ExpansionColumn = ExpansionView.getStartCol();
> + renderIndent(OS, IndentLevel);
> + OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
> + renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
> + ExpansionColumn);
> + renderViewDivider(NestedIndent, DividerWidth, OS);
> + OS << "\n";
> + }
> + SourceCoverageView::renderExpansionView(
> + OS, LI, ExpansionView, WrappedSegment, LineSegments, RenderedSubView,
> + IndentLevel, NestedIndent, CombinedColumnWidth, DividerWidth);
> +}
> Index: tools/llvm-cov/SourceCoverageView.h
> ===================================================================
> --- tools/llvm-cov/SourceCoverageView.h (revision 262364)
> +++ tools/llvm-cov/SourceCoverageView.h (working copy)
> @@ -17,6 +17,9 @@
> #include "CoverageViewOptions.h"
> #include "llvm/ProfileData/CoverageMapping.h"
> #include "llvm/Support/MemoryBuffer.h"
> +#include "llvm/Support/LineIterator.h"
> +#include "llvm/ADT/Optional.h"
> +#include "llvm/ADT/STLExtras.h"
> #include <vector>
>
> namespace llvm {
> @@ -76,7 +79,7 @@
> /// \brief A code coverage view of a specific source file.
> /// It can have embedded coverage views.
> class SourceCoverageView {
> -private:
> +protected:
> /// \brief Coverage information for a single line.
> struct LineCoverageInfo {
> uint64_t ExecutionCount;
> @@ -103,40 +106,70 @@
>
> const MemoryBuffer &File;
> const CoverageViewOptions &Options;
> + StringRef SourceName;
> coverage::CoverageData CoverageInfo;
> std::vector<ExpansionView> ExpansionSubViews;
> std::vector<InstantiationView> InstantiationSubViews;
>
> + virtual void output(llvm::raw_ostream &OS, StringRef Text,
> + llvm::Optional<llvm::raw_ostream::Colors> Highlight,
> + const coverage::CoverageSegment *Segment = nullptr) = 0;
> +
> /// \brief Render a source line with highlighting.
> - void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> - const coverage::CoverageSegment *WrappedSegment,
> - ArrayRef<const coverage::CoverageSegment *> Segments,
> - unsigned ExpansionCol);
> + virtual void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> + const coverage::CoverageSegment *WrappedSegment,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned ExpansionCol);
>
> - void renderIndent(raw_ostream &OS, unsigned Level);
> -
> - void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
> -
> /// \brief Render the line's execution count column.
> - void renderLineCoverageColumn(raw_ostream &OS, const LineCoverageInfo &Line);
> + virtual void renderLineCoverageColumn(raw_ostream &OS,
> + const LineCoverageInfo &Line) = 0;
>
> /// \brief Render the line number column.
> - void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo);
> + virtual void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) = 0;
>
> /// \brief Render all the region's execution counts on a line.
> - void
> + virtual void
> renderRegionMarkers(raw_ostream &OS,
> - ArrayRef<const coverage::CoverageSegment *> Segments);
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned IndentLevel, unsigned CombinedColumnWidth);
>
> + /// \brief Render an expansion view inline, using format-specific logic.
> + virtual void renderExpansionView(
> + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> + bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent,
> + unsigned CombinedColumnWidth, unsigned DividerWidth);
> +
> + /// \brief Render an instantiation view inline, using format-specific logic.
> + virtual void renderInstantiationView(raw_ostream &OS,
> + InstantiationView &InstantiationView,
> + unsigned IndentLevel,
> + unsigned NestedIndent,
> + unsigned DividerWidth);
> +
> + /// \brief Render all the region's execution counts on a line.
> + virtual void finishRenderingSubviews(raw_ostream &OS, unsigned IndentLevel,
> + unsigned DividerWidth) = 0;
> +
> + virtual void startLine(raw_ostream &OS, unsigned IndentLevel) {}
> + virtual void endLine(raw_ostream &OS) {}
> +
> static const unsigned LineCoverageColumnWidth = 7;
> static const unsigned LineNumberColumnWidth = 5;
>
> public:
> SourceCoverageView(const MemoryBuffer &File,
> - const CoverageViewOptions &Options,
> + const CoverageViewOptions &Options, StringRef SourceName,
> coverage::CoverageData &&CoverageInfo)
> - : File(File), Options(Options), CoverageInfo(std::move(CoverageInfo)) {}
> + : File(File), Options(Options), SourceName(SourceName),
> + CoverageInfo(std::move(CoverageInfo)) {}
>
> + static std::unique_ptr<SourceCoverageView>
> + create(const MemoryBuffer &File, const CoverageViewOptions &Options,
> + StringRef SourceName, coverage::CoverageData &&CoverageInfo);
> +
> const CoverageViewOptions &getOptions() const { return Options; }
>
> /// \brief Add an expansion subview to this view.
> @@ -153,9 +186,147 @@
>
> /// \brief Print the code coverage information for a specific
> /// portion of a source file to the output stream.
> - void render(raw_ostream &OS, bool WholeFile, unsigned IndentLevel = 0);
> + virtual void render(raw_ostream &OS, bool WholeFile, bool RenderTitle = false,
> + unsigned IndentLevel = 0);
> +
> + /// \brief Render the title for a given source region (function or file name)
> + virtual void renderTitle(raw_ostream &OS) = 0;
> +
> + virtual ~SourceCoverageView() {}
> };
>
> +class SourceCoverageViewHTML : public SourceCoverageView {
> +
> + /// \brief Render a source line with highlighting.
> + void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> + const coverage::CoverageSegment *WrappedSegment,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned ExpansionCol) override;
> +
> + void startLine(raw_ostream &OS, unsigned IndentLevel) override;
> + void endLine(raw_ostream &OS) override;
> +
> + /// \brief Render the line's execution count column.
> + void renderLineCoverageColumn(raw_ostream &OS,
> + const LineCoverageInfo &Line) override;
> +
> + void output(llvm::raw_ostream &OS, StringRef Text,
> + llvm::Optional<llvm::raw_ostream::Colors> Highlight,
> + const coverage::CoverageSegment *Segment = nullptr) override;
> +
> + /// \brief Render the line number column.
> + void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) override;
> +
> + /// \brief Render all the region's execution counts on a line.
> + void renderRegionMarkers(raw_ostream &OS,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned IndentLevel,
> + unsigned CombinedColumnWidth) override {
> + // empty implementation (region markers are tooltips).
> + }
> +
> + /// \brief Print the code coverage information for a specific
> + /// portion of a source file to the output stream.
> + void render(raw_ostream &OS, bool WholeFile, bool RenderTitle = false,
> + unsigned IndentLevel = 0) override;
> +
> + void finishRenderingSubviews(raw_ostream &OS, unsigned IndentLevel,
> + unsigned DividerWidth) override;
> + void renderExpansionView(
> + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> + bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent,
> + unsigned CombinedColumnWidth, unsigned DividerWidth) override;
> +
> + void renderInstantiationView(raw_ostream &OS,
> + InstantiationView &InstantiationView,
> + unsigned IndentLevel, unsigned NestedIndent,
> + unsigned DividerWidth) override;
> +
> +public:
> + SourceCoverageViewHTML(const MemoryBuffer &File,
> + const CoverageViewOptions &Options,
> + StringRef SourceName,
> + coverage::CoverageData &&CoverageInfo)
> + : SourceCoverageView(File, Options, SourceName, std::move(CoverageInfo)) {
> + }
> +
> + static void renderIndex(raw_ostream &OS,
> + std::vector<std::string> &SourceFiles);
> +
> + // \brief Render the standard boilerplate HTML header.
> + static void renderFileHeader(raw_ostream &OS);
> +
> + // \brief Render the standard boilerplate HTML footer.
> + static void renderFileFooter(raw_ostream &OS);
> +
> + /// \brief Render the title for a given source region (function or file name)
> + void renderTitle(raw_ostream &OS) override;
> +};
> +
> +class SourceCoverageViewText : public SourceCoverageView {
> +
> + /// \brief Render a source line with highlighting.
> + void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> + const coverage::CoverageSegment *WrappedSegment,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned ExpansionCol) override;
> +
> + void startLine(raw_ostream &OS, unsigned IndentLevel) override;
> +
> + /// \brief Render the line's execution count column.
> + void renderLineCoverageColumn(raw_ostream &OS,
> + const LineCoverageInfo &Line) override;
> +
> + void output(raw_ostream &OS, StringRef Text,
> + Optional<raw_ostream::Colors> Highlight,
> + const coverage::CoverageSegment *Segment = nullptr) override;
> +
> + /// \brief Render the line number column.
> + void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) override;
> +
> + /// \brief Render all the region's execution counts on a line.
> + void renderRegionMarkers(raw_ostream &OS,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned IndentLevel,
> + unsigned CombinedColumnWidth) override;
> +
> + void renderIndent(raw_ostream &OS, unsigned Level);
> +
> + void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
> +
> + void finishRenderingSubviews(raw_ostream &OS, unsigned IndentLevel,
> + unsigned DividerWidth) override;
> + void renderExpansionView(
> + raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> + bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent,
> + unsigned CombinedColumnWidth, unsigned DividerWidth) override;
> +
> + void renderInstantiationView(raw_ostream &OS,
> + InstantiationView &InstantiationView,
> + unsigned IndentLevel, unsigned NestedIndent,
> + unsigned DividerWidth) override;
> +
> + /// \brief Print the code coverage information for a specific
> + /// portion of a source file to the output stream.
> + void render(raw_ostream &OS, bool WholeFile, bool RenderTitle = false,
> + unsigned IndentLevel = 0) override;
> +
> +public:
> + SourceCoverageViewText(const MemoryBuffer &File,
> + const CoverageViewOptions &Options,
> + StringRef SourceName,
> + coverage::CoverageData &&CoverageInfo)
> + : SourceCoverageView(File, Options, SourceName, std::move(CoverageInfo)) {
> + }
> +
> + /// \brief Render the title for a given source region (function or file name)
> + void renderTitle(raw_ostream &OS) override;
> +};
> +
> } // namespace llvm
>
> #endif // LLVM_COV_SOURCECOVERAGEVIEW_H
> Best,
> Harlan
>
> On Wed, Mar 9, 2016 at 12:50 AM, Harlan Haskins <hhaskins at apple.com>
> wrote:
>
> Updated patch with unistd removed, utf-8, and `console` replaced with
> `text`.
>
> Thanks!
> Harlan
>
> On Mar 8, 2016, at 10:55 AM, Justin Bogner <mail at justinbogner.com>
> wrote:
>
> Harlan Haskins <hhaskins at apple.com> writes:
>
> Thanks again! Responses inline.
>
> On Mar 8, 2016, at 5:08 AM, Ying Yi <maggieyi666 at gmail.com
> > wrote:
>
> Thanks for adding the index page. I have some comments:
>
> 1) The ‘unistd.h’ header won’t build on windows.
>
> Thanks for catching that — I was using it to debug, but don’t
> need
> it. I also don’t have a Windows machine to debug, so thank you
> for
> investigating portability!
>
> 2) The help text for ‘-format’ is different from
> llvm-profdata.exe
> tool, which uses the following style:
> -format=<html|console> - Format to
> output comparison ….
> Is there a standard way to do this?
>
> LLVM’s CommandLine library renders the help text. Also, I
> noticed I
> used ‘comparison’ instead of ‘coverage’. Thanks!
>
> 3) Should ‘console’ be changed to ‘text’, because it
> doesn’t show in
> the console when you give an output directory?
>
> Yeah, I think it could. I’ve cc’d Justin for his input — he
> originally
> suggested Console.
>
> My only problem with "text" is that this includes ANSI escape
> sequences
> when it uses colour, so text seems a bit misleading. That said, it
> doesn't matter much - I'm fine with either.
>
> 4) Could you please use an html charset that supports
> UTF-8?
>
> Yep. Thanks for catching that!
>
> 5) The HTML report can look quite different to the source
> file if it
> includes tabs. Could you specify a smaller tab-size in the
> CSS file?
>
> That’s a good question, and I don’t quite know the correct
> answer to
> it. GitHub has a complicated set of rules for tab sizing that
> makes a
> ‘best guess’ based on the source file type. I wonder if it’s a
> good
> candidate for a command line switch?
>
> In the future (not for this patch):
>
> 6) Could we support a user CSS file?
> Currently, the CSS rules are embedded into the html page.
> Could
> llvm-cov provide an option to use an external CSS file?
> This would
> give the user more flexibility.
>
> I agree that it’d give the user more flexibility, but it’d be
> difficult to do given the nature of the directory structure.
> Maybe, if
> you know you’re gonna be running it on a server where the
> ‘root’ is
> well-defined, we could just dump the css file to disk and put
> a
> relative <link> tag in. But if it’s gonna be ten directories
> down,
> we’ll need to have some logic in that replaces all the parent
> paths in
> the link with /../..’s to make the file resolve. I think it
> can be
> done, just with more input from the user about their intention
> with
> hosting.
>
> 7) Could we put the functions/lines summary information
> into the
> HTML files? As a rough example:
>
> Lines: 9/10 (90%)
> Functions: 1/1 (100%)
>
> I think this totally can be done — it’s out of scope for this
> patch
> (it’s already a pretty big patch as it is.)
> Some kind of merging of ‘show’ and ‘report’ would be ideal.
> Maybe the
> index page has at-a-glance information about each file?
>
> Yep, this sounds like a great idea, but it's probably more suited
> to a
> follow up patch.
>
> --
> Ying Yi
> SN Systems Ltd - Sony Computer Entertainment Group.
More information about the llvm-commits
mailing list