[PATCH][RFC] llvm-cov HTML generation

Justin Bogner via llvm-commits llvm-commits at lists.llvm.org
Tue Mar 1 23:18:51 PST 2016


Harlan Haskins via llvm-commits <llvm-commits at lists.llvm.org> writes:
> I’ve got a preliminary implementation of HTML generation for llvm-cov’s
> coverage reports.
>
> The patch adds 2 flags to llvm-cov show:
>
>   * -format [html | console]
>   * -output-dir <dirname>

This looks really useful. I've cc'd a couple of people I think will be
interested.

Commments inline, mostly nitpicks.

> Specifying -format=console will perform the current behavior (now the
> default), and -format=html will generate an HTML report.
> If -output-dir is specified, then the output is split into one html or txt
> file per source file, named <source-name>.<ext>, with a directory structure
> that mimics the file system structure.
>
> If neither are provided, the behavior remains the same.
>
> I’m hoping to add an index with a browsable list of files within their
> directories, but for now I’ve attached the patch and a sample HTML file (In
> this case, AliasAnalysis.h, as included by swiftc).

Something as simple as a list of files with clickable links would
probably be good enough for a start. You could also go further and do
something like the output of llvm-cov report with clickable links, but
that's probably better as a followup.

Harlan Haskins via llvm-commits <llvm-commits at lists.llvm.org> writes:
> Oops, forgot to add a file to the patch.
>
> New patch attached.
>
> Index: CodeCoverage.cpp

Better to generate diffs from the top level llvm source directory, for
future reference. Then people don't need to figure out where to cd first
(tools/llvm-cov) to apply the patch.

> ===================================================================
> --- CodeCoverage.cpp	(revision 262364)
> +++ CodeCoverage.cpp	(working copy)
> @@ -33,6 +33,7 @@
>  #include "llvm/Support/Signals.h"
>  #include <functional>
>  #include <system_error>
> +#include <unistd.h>

We should probably avoid unistd here, it's not portable.

>  
>  using namespace llvm;
>  using namespace coverage;
> @@ -47,10 +48,17 @@
>      /// \brief The report command.
>      Report
>    };
> -
> +  
> +  /// \brief Print an error message and exit.
> +  void exitWithErrorCode(std::error_code Error, StringRef Whence);
> +  
>    /// \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> streamForFile(std::string Filename);
> +  
>    /// \brief Return a memory buffer for the given source file.
>    ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile);
>  
> @@ -135,7 +143,7 @@
>        continue;
>  
>      auto SubViewExpansions = ExpansionCoverage.getExpansions();
> -    auto SubView = llvm::make_unique<SourceCoverageView>(
> +    auto SubView = SourceCoverageView::create(
>          SourceBuffer.get(), ViewOpts, std::move(ExpansionCoverage));
>      attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
>      View.addExpansion(Expansion.Region, std::move(SubView));
> @@ -153,7 +161,7 @@
>      return nullptr;
>  
>    auto Expansions = FunctionCoverage.getExpansions();
> -  auto View = llvm::make_unique<SourceCoverageView>(
> +  auto View = SourceCoverageView::create(
>        SourceBuffer.get(), ViewOpts, std::move(FunctionCoverage));
>    attachExpansionSubViews(*View, Expansions, Coverage);
>  
> @@ -171,14 +179,14 @@
>      return nullptr;
>  
>    auto Expansions = FileCoverage.getExpansions();
> -  auto View = llvm::make_unique<SourceCoverageView>(
> +  auto View = SourceCoverageView::create(
>        SourceBuffer.get(), ViewOpts, 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>(
> +    auto SubView = SourceCoverageView::create(
>          SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage));
>      attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
>  
> @@ -377,6 +385,26 @@
>    return 0;
>  }
>  
> +std::unique_ptr<raw_ostream>
> +CodeCoverageTool::streamForFile(std::string Filename) {

Functions should usually be verb phrases, like getStreamForFile.

> +  if (Filename == "") {
> +    return make_unique<raw_fd_ostream>(STDOUT_FILENO, false);
> +  } else {

The LLVM style avoids the "else" after an early return. Saves some
indentation.

This applies to a few places later in the patch too.

> +    std::error_code Error;
> +    auto OS = make_unique<raw_fd_ostream>(Filename,
> +                                          Error, sys::fs::F_RW);
> +    if (Error)
> +      exitWithErrorCode(Error, __PRETTY_FUNCTION__);

Why __PRETTY_FUNCTION__?

I'm not a big fan of calling exit here - I think it'd be better to just
call error(...) and return nullptr, then CodeCoverageTool::view can
just return 1 or whatever.

> +    return move(OS);

This is std::move, right? Are we "using namespace std" somewhere? We
shouldn't be.

> +  }
> +}
> +
> +void CodeCoverageTool::exitWithErrorCode(std::error_code Error,
> +                                         StringRef Whence = "") {
> +  error(Error.message(), Whence);
> +  exit(Error.value());
> +}
> +
>  int CodeCoverageTool::show(int argc, const char **argv,
>                             CommandLineParserType commandLineParser) {
>  
> @@ -405,6 +433,20 @@
>    cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional,
>                                     cl::desc("Show function instantiations"),
>                                     cl::cat(ViewCategory));
> +  
> +  cl::opt<std::string> OutputDirectory("o", "output-dir", cl::Optional,
> +      cl::desc("Directory to output individual files"));
> +  
> +  cl::opt<CoverageViewOptions::OutputFormat> Format("f", "format",
> +      cl::desc("Format to output comparison"),
> +      cl::values(clEnumValN(CoverageViewOptions::HTML,
> +                            "html",
> +                            "HTML output"),
> +                 clEnumValN(CoverageViewOptions::Console,
> +                            "console",
> +                            "Console table output"),
> +                 clEnumValEnd),
> +      cl::init(CoverageViewOptions::Console));
>  
>    auto Err = commandLineParser(argc, argv);
>    if (Err)
> @@ -417,12 +459,23 @@
>    ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts;
>    ViewOpts.ShowExpandedRegions = ShowExpansions;
>    ViewOpts.ShowFunctionInstantiations = ShowInstantiations;
> +  ViewOpts.Format = Format;
> +  ViewOpts.Colors = ViewOpts.Colors && Format == CoverageViewOptions::Console;
>  
>    auto Coverage = load();
>    if (!Coverage)
>      return 1;
> -
> +  
> +  
>    if (!Filters.empty()) {
> +    std::string outputPath = OutputDirectory;
> +    if (outputPath != "") {
> +      std::string ext = Format == CoverageViewOptions::HTML ? "html" : "txt";
> +      outputPath += "/functions." + ext;
> +    }
> +    auto os = streamForFile(outputPath);

Variables should start with capital letters. Ie, `OutputPath`, `Ext`,
and `OS`.

> +    SourceCoverageViewHTML::renderFileHeader(*os);
> +    
>      // Show functions
>      for (const auto &Function : Coverage->getCoveredFunctions()) {
>        if (!Filters.matches(Function))
> @@ -435,12 +488,12 @@
>          outs() << "\n";
>          continue;
>        }
> -      ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << Function.Name
> -                                                          << ":";
> -      outs() << "\n";
> -      mainView->render(outs(), /*WholeFile=*/false);
> -      outs() << "\n";
> +      mainView->renderTitle(*os, Function.Name);
> +      *os << "\n";
> +      mainView->render(*os, /*WholeFile=*/false);
> +      *os << "\n";
>      }
> +    SourceCoverageViewHTML::renderFileFooter(*os);
>      return 0;
>    }
>  
> @@ -453,21 +506,28 @@
>        SourceFiles.push_back(Filename);
>  
>    for (const auto &SourceFile : SourceFiles) {
> +    auto basename = sys::path::parent_path(SourceFile);
> +    sys::fs::create_directories(OutputDirectory + basename);
> +    auto ext = Format == CoverageViewOptions::HTML ? "html" : "txt";
> +    std::string outputPath = OutputDirectory;
> +    if (outputPath != "") {
> +      outputPath += "/" + std::string(SourceFile) + "." + ext;
> +    }
> +    std::unique_ptr<raw_ostream> os = streamForFile(outputPath);

The code duplication's kind of gross here. To be fair, that was true
before this patch, but we should probably try to clean that up in a
separate NFC patch either before or after this one.

>      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->renderTitle(*os, SourceFile);
>      }
> -    mainView->render(outs(), /*Wholefile=*/true);
> +    mainView->render(*os, /*Wholefile=*/true);
>      if (SourceFiles.size() > 1)
> -      outs() << "\n";
> +      *os << "\n";
>    }
>  
>    return 0;
> Index: CoverageViewOptions.h
> ===================================================================
> --- CoverageViewOptions.h	(revision 262364)
> +++ 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.
> +    Console,
> +    /// \brief An HTML directory.
> +    HTML

These should probably be named something like OutputFormatConsole or
OFConsole to avoid name collisions.

> +  };
>    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: SourceCoverageView.cpp
> ===================================================================
> --- SourceCoverageView.cpp	(revision 262364)
> +++ SourceCoverageView.cpp	(working copy)
> @@ -12,13 +12,99 @@
>  //===----------------------------------------------------------------------===//
>  
>  #include "SourceCoverageView.h"
> -#include "llvm/ADT/Optional.h"
>  #include "llvm/ADT/SmallString.h"
>  #include "llvm/ADT/StringExtras.h"
> -#include "llvm/Support/LineIterator.h"
>  
>  using namespace llvm;
>  
> +namespace html {
> +  using namespace std;

Please don't "using namespace std".

> +  /// \returns An HTML-escaped string.

I don't know what doxygen does with a \returns with no \brief. If you're
sure it's reasonable this is fine, but otherwise I'd just drop the
\returns and reword these like "Escapes the string for HTML".

> +  string escape(string s) {
> +    string result;
> +    for (size_t i = 0; i < s.size(); ++i) {
> +      string token = s.substr(i,1);
> +      if (token == "&")
> +        token = "&";
> +      else if (token == "<")
> +        token = "<";
> +      else if (token == "\"")
> +        token = """;
> +      else if (token == ">")
> +        token = ">";
> +      result += token;
> +    }
> +    return result;
> +  }
> +  
> +  /// \returns A tag around the provided text
> +  /// (expects the value to be properly escaped).
> +  string tag(string name, string text, string _class = "") {

More variables that should start with capital letters, but note that
_Class would be a reserved name (_Capital always is), so you'd have to
go with Class_.

> +    string tag = "<" + name;
> +    if (_class != "") {
> +     tag += " class='" + _class + "'";
> +    }
> +    return tag + ">" + text + "</" + name + ">";
> +  }
> +}
> +
> +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.
> +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 SourceCoverageViewHTML::output(raw_ostream &OS, std::string Text,
> +                                    Optional<raw_ostream::Colors> Highlight,
> +                                    const coverage::CoverageSegment
> +                                      *Segment) {

This is formatted funny. I recommend clang-format. You can use the
clang-format-diff or git-clang-format script in the clang repo to just
format your changes (and avoid arbitrary reformatting changes elsewhere).

> +  auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR;
> +  Text = html::escape(Text);
> +  if (Highlight) {
> +    Text = html::tag("span", Text, classForColor(Color));
> +  }
> +  if (Options.ShowRegionMarkers && Segment && Segment->IsRegionEntry) {
> +    std::string C = formatCount(Segment->Count);
> +    auto countTag = html::tag("span", html::escape(C));
> +    Text = html::tag("span", Text + countTag, "tooltips");
> +  }
> +  OS << Text;
> +}
> +
> +void SourceCoverageViewConsole::output(raw_ostream &OS,
> +                                       std::string 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 SourceCoverageView::renderLine(
>      raw_ostream &OS, StringRef Line, int64_t LineNumber,
>      const coverage::CoverageSegment *WrappedSegment,
> @@ -30,14 +116,13 @@
>    // The first segment overlaps from a previous line, so we treat it specially.
>    if (WrappedSegment && WrappedSegment->HasCount && WrappedSegment->Count == 0)
>      Highlight = raw_ostream::RED;
> -
> +  
>    // Output each segment of the line, possibly highlighted.
>    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;
> @@ -48,13 +133,10 @@
>      else
>        Highlight = None;
>    }
> +  
> +  auto Rest = Line.substr(Col - 1, Line.size() - Col + 1);
> +  output(OS, Rest, Highlight, nullptr);
>  
> -  // 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";
> -
>    if (Options.Debug) {
>      for (const auto &Range : HighlightedRanges)
>        errs() << "Highlighted line " << LineNumber << ", " << Range.first
> @@ -64,13 +146,60 @@
>    }
>  }
>  
> -void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) {
> +void SourceCoverageViewHTML::renderTitle(raw_ostream &OS,
> +                                         StringRef Name) {
> +  OS << "<div class='function-title'><pre>"
> +     << std::string(Name) << ":</pre></div>";
> +}
> +
> +void SourceCoverageViewConsole::renderTitle(raw_ostream &OS,
> +                                            StringRef Name) {
> +  Options.colored_ostream(OS, raw_ostream::CYAN) << Name << ":\n";
> +}
> +
> +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><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 SourceCoverageViewConsole::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 SourceCoverageViewConsole::renderIndent(raw_ostream &OS, unsigned Level) {
>    for (unsigned I = 0; I < Level; ++I)
>      OS << "  |";
>  }
>  
> -void SourceCoverageView::renderViewDivider(unsigned Level, unsigned Length,
> -                                           raw_ostream &OS) {
> +void SourceCoverageViewConsole::renderViewDivider(unsigned Level,
> +                                                  unsigned Length,
> +                                                  raw_ostream &OS) {
>    assert(Level != 0 && "Cannot render divider at top level");
>    renderIndent(OS, Level - 1);
>    OS.indent(2);
> @@ -78,50 +207,61 @@
>      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) {
> +void SourceCoverageViewConsole::renderLineCoverageColumn(raw_ostream &OS,
> +                                                         const LineCoverageInfo
> +                                                            &Line) {
> +  
> +  std::string C = formatCount(Line.ExecutionCount);
>    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;
> +  << C;
>    OS << '|';
>  }
>  
> -void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS,
> -                                                unsigned LineNo) {
> +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 SourceCoverageViewConsole::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) {
> +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)
> @@ -135,23 +275,158 @@
>      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 SourceCoverageViewConsole::renderRegionMarkers(raw_ostream &OS,
> +      ArrayRef<const coverage::CoverageSegment *> Segments,
> +      unsigned IndentLevel, unsigned CombinedColumnWidth) {
> +  renderIndent(OS, IndentLevel);
> +  SourceCoverageView::renderRegionMarkers(OS, Segments,
> +                                          IndentLevel, CombinedColumnWidth);
>  }
>  
> +void SourceCoverageViewConsole::startLine(raw_ostream &OS,
> +                                          unsigned IndentLevel) {
> +  renderIndent(OS, IndentLevel);
> +}
> +
> +void SourceCoverageViewHTML::renderFileHeader(raw_ostream &OS) {
> +#include "css.inc"

A more specific name than css.inc would probably be better. This
probably deserves a comment too.

> +  OS << "<!doctype html>\n"
> +        "<html>\n"
> +        "  <head>\n"
> +        "    <meta name='viewport'"
> +        "content='width=device-width,initial-scale=1'>\n"
> +        "    <style>\n" << css <<
> +        "    </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,
> +                                    unsigned IndentLevel) {
> +  if (WholeFile) {
> +    renderFileHeader(OS);
> +    OS << "    <table class='centered'>\n";
> +  } else {
> +    OS << "    <table>\n";
> +  }
> +  SourceCoverageView::render(OS, WholeFile, IndentLevel);
> +  OS << "      </table>\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 SourceCoverageViewConsole::renderSubviews(raw_ostream &OS,
> +                                               line_iterator &LI,
> +      std::vector<ExpansionView>::iterator &NextESV,
> +      std::vector<ExpansionView>::iterator &EndESV,
> +      std::vector<InstantiationView>::iterator &NextISV,
> +      std::vector<InstantiationView>::iterator &EndISV,
> +      const coverage::CoverageSegment *WrappedSegment,
> +      SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> +      unsigned IndentLevel, unsigned ExpansionColumn,
> +      unsigned CombinedColumnWidth) {
> +  
> +  // 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);
> +    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);
> +    RenderedSubView = true;
> +  }
> +  if (RenderedSubView) {
> +    renderViewDivider(NestedIndent, DividerWidth, OS);
> +    OS << "\n";
> +  }
> +}
> +
> +void SourceCoverageViewHTML::renderSubviews(raw_ostream &OS,
> +                                            line_iterator &LI,
> +                    std::vector<ExpansionView>::iterator &NextESV,
> +                    std::vector<ExpansionView>::iterator &EndESV,
> +                    std::vector<InstantiationView>::iterator &NextISV,
> +                    std::vector<InstantiationView>::iterator &EndISV,
> +                    const coverage::CoverageSegment *WrappedSegment,
> +                    SmallVector<const coverage::CoverageSegment *, 8>
> +                    LineSegments,  unsigned IndentLevel,
> +                    unsigned ExpansionColumn, unsigned CombinedColumnWidth) {
> +  for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
> +       ++NextESV) {
> +    OS << "<div class='expansion-view'>\n";
> +    // Render the child subview
> +    if (Options.Debug)
> +      errs() << "Expansion at line " << NextESV->getLine() << ", "
> +      << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
> +      << "\n";
> +    NextESV->View->render(OS, false);
> +    OS << "</div>\n";
> +  }
> +  
> +  for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
> +    OS << "<div class='expansion-view'>\n";
> +    NextISV->View->renderTitle(OS, NextISV->FunctionName);
> +    NextISV->View->render(OS, false);
> +    OS << "</div>\n";
> +  }
> +  OS << "</td>\n";
> +}
> +
>  void SourceCoverageView::render(raw_ostream &OS, bool WholeFile,
>                                  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());
> @@ -193,8 +468,8 @@
>        if (S->HasCount && S->IsRegionEntry)
>            LineCount.addRegionStartCount(S->Count);
>  
> -    // Render the line prefix.
> -    renderIndent(OS, IndentLevel);
> +    startLine(OS, IndentLevel);
> +
>      if (Options.ShowLineStats)
>        renderLineCoverageColumn(OS, LineCount);
>      if (Options.ShowLineNumbers)
> @@ -214,51 +489,30 @@
>      if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers ||
>                                        LineCount.hasMultipleRegions()) &&
>          !LineSegments.empty()) {
> -      renderIndent(OS, IndentLevel);
> -      OS.indent(CombinedColumnWidth);
> -      renderRegionMarkers(OS, LineSegments);
> -    }
> +      renderRegionMarkers(OS, LineSegments, IndentLevel, CombinedColumnWidth);
>  
> -    // 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);
> -      RenderedSubView = true;
> +        for (const auto *S : LineSegments)
> +          errs() << "Marker at " << S->Line << ":" << S->Col << " = "
> +          << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n");
>      }
> -    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);
> -      RenderedSubView = true;
> -    }
> -    if (RenderedSubView) {
> -      renderViewDivider(NestedIndent, DividerWidth, OS);
> -      OS << "\n";
> -    }
> +    renderSubviews(OS, LI, NextESV, EndESV, NextISV, EndISV,
> +                   WrappedSegment, LineSegments, IndentLevel,
> +                   ExpansionColumn, CombinedColumnWidth);
> +    
> +    endLine(OS);
>    }
>  }
> +
> +std::unique_ptr<SourceCoverageView>
> +SourceCoverageView::create(const MemoryBuffer &File,
> +                           const CoverageViewOptions &Options,
> +                           coverage::CoverageData &&CoverageInfo)  {
> +  if (Options.Format == CoverageViewOptions::HTML) {
> +    return make_unique<SourceCoverageViewHTML>(File, Options,
> +                                               std::move(CoverageInfo));
> +  } else {
> +    return make_unique<SourceCoverageViewConsole>(File, Options,
> +                                                  std::move(CoverageInfo));

Better to use a switch statement - it'll give a warning if we add a
value to the enum and forget to update here.

> +  }
> +}
> Index: SourceCoverageView.h
> ===================================================================
> --- SourceCoverageView.h	(revision 262364)
> +++ 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;
> @@ -108,25 +111,39 @@
>    std::vector<InstantiationView> InstantiationSubViews;
>  
>    /// \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
> -  renderRegionMarkers(raw_ostream &OS,
> -                      ArrayRef<const coverage::CoverageSegment *> Segments);
> +  virtual void renderRegionMarkers(raw_ostream &OS,
> +                 ArrayRef<const coverage::CoverageSegment *> Segments,
> +                                   unsigned IndentLevel,
> +                                   unsigned CombinedColumnWidth);
> +  
> +  virtual void renderSubviews(llvm::raw_ostream &OS,
> +                              line_iterator &LI,
> +                              std::vector<ExpansionView>::iterator &NextESV,
> +                              std::vector<ExpansionView>::iterator &EndESV,
> +                              std::vector<InstantiationView>::iterator &NextISV,
> +                              std::vector<InstantiationView>::iterator &EndISV,
> +                              const coverage::CoverageSegment *WrappedSegment,
> +                              SmallVector<const coverage::CoverageSegment *, 8>
> +                              LineSegments,
> +                              unsigned IndentLevel,
> +                              unsigned ExpansionColumn,
> +                              unsigned CombinedColumnWidth) = 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;
> @@ -136,6 +153,11 @@
>                       const CoverageViewOptions &Options,
>                       coverage::CoverageData &&CoverageInfo)
>        : File(File), Options(Options), CoverageInfo(std::move(CoverageInfo)) {}
> +  
> +  
> +  static std::unique_ptr<SourceCoverageView> create(const MemoryBuffer &File,
> +          const CoverageViewOptions &Options,
> +          coverage::CoverageData &&CoverageInfo);
>  
>    const CoverageViewOptions &getOptions() const { return Options; }
>  
> @@ -150,12 +172,139 @@
>                          std::unique_ptr<SourceCoverageView> View) {
>      InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View));
>    }
> +  
> +  virtual void output(llvm::raw_ostream &OS, std::string Text,
> +                      llvm::Optional<llvm::raw_ostream::Colors> Highlight,
> +                      const coverage::CoverageSegment *Segment = nullptr) = 0;
>  
>    /// \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,
> +                      unsigned IndentLevel = 0);
> +  
> +  /// \brief Render the title for a given source region (function or file name)
> +  virtual void renderTitle(raw_ostream &OS, StringRef Name) = 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, std::string 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).
> +  }
> +  
> +  void renderSubviews(llvm::raw_ostream &OS,
> +                      line_iterator &LI,
> +                      std::vector<ExpansionView>::iterator &NextESV,
> +                      std::vector<ExpansionView>::iterator &EndESV,
> +                      std::vector<InstantiationView>::iterator &NextISV,
> +                      std::vector<InstantiationView>::iterator &EndISV,
> +                      const coverage::CoverageSegment *WrappedSegment,
> +                      SmallVector<const coverage::CoverageSegment *, 8>
> +                      LineSegments,
> +                      unsigned IndentLevel,
> +                      unsigned ExpansionColumn,
> +                      unsigned CombinedColumnWidth) 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,
> +                       unsigned IndentLevel = 0) override;
> +public:
> +  SourceCoverageViewHTML(const MemoryBuffer &File,
> +                         const CoverageViewOptions &Options,
> +                         coverage::CoverageData &&CoverageInfo)
> +  : SourceCoverageView(File, Options, std::move(CoverageInfo)) {}
> +  
> +  // \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, StringRef Name) override;
> +};
> +  
> +class SourceCoverageViewConsole : 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(llvm::raw_ostream &OS, std::string 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;
> +  
> +  void renderSubviews(raw_ostream &OS,
> +                      line_iterator &LI,
> +                      std::vector<ExpansionView>::iterator &NextESV,
> +                      std::vector<ExpansionView>::iterator &EndESV,
> +                      std::vector<InstantiationView>::iterator &NextISV,
> +                      std::vector<InstantiationView>::iterator &EndISV,
> +                      const coverage::CoverageSegment *WrappedSegment,
> +                      SmallVector<const coverage::CoverageSegment *, 8>
> +                        LineSegments,
> +                      unsigned IndentLevel,
> +                      unsigned ExpansionColumn,
> +                      unsigned CombinedColumnWidth) override;
> +  
> +  void renderIndent(raw_ostream &OS, unsigned Level);
> +    
> +  void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
> +public:
> +  SourceCoverageViewConsole(const MemoryBuffer &File,
> +                            const CoverageViewOptions &Options,
> +                            coverage::CoverageData &&CoverageInfo)
> +  : SourceCoverageView(File, Options, std::move(CoverageInfo)) {}
> +  
> +  /// \brief Render the title for a given source region (function or file name)
> +  void renderTitle(raw_ostream &OS, StringRef Name) override;
> +};
> +
>  } // namespace llvm
>  
>  #endif // LLVM_COV_SOURCECOVERAGEVIEW_H
> Index: css.inc
> ===================================================================
> --- css.inc	(revision 0)
> +++ css.inc	(working copy)
> @@ -0,0 +1,108 @@
> +//===------------ css.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 css = 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;
> +}
> +tr:nth-child(odd) {
> +  background-color: #fafafa;
> +}
> +tr:nth-child(even) {
> +  background-color: #f0f0f0;
> +}
> +pre {
> +  margin-top: 0px !important;
> +  margin-bottom: 0px !important;
> +}
> +.function-title {
> +  margin-top: 15px;
> +  margin-bottom: 5px;
> +}
> +.centered {
> +  margin-left: auto;
> +  margin-right: auto;
> +}
> +.expansion-view {
> +  border: 2px solid #d8d8d8;
> +  padding-left: 10px;
> +  padding-right: 10px;
> +  padding-bottom: 10px;
> +  margin-bottom: 5px;
> +}
> +table {
> +  border: 2px solid #dbdbdb;
> +  border-collapse: collapse;
> +}
> +.numeric {
> +  text-align: right;
> +}
> +.tooltips {
> +  position: relative;
> +  display: inline;
> +  background-color: #fffee6;
> +  text-decoration: none;
> +}
> +.tooltips span {
> +  position: absolute;
> +  width:140px;
> +  color: #FFFFFF;
> +  background: #000000;
> +  height: 30px;
> +  line-height: 30px;
> +  text-align: center;
> +  visibility: hidden;
> +  border-radius: 6px;
> +}
> +.tooltips span: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 {
> +  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;
> +})";


More information about the llvm-commits mailing list