[PATCH] D18278: llvm-cov HTML Generation

Justin Bogner via llvm-commits llvm-commits at lists.llvm.org
Fri Mar 25 11:12:57 PDT 2016


Harlan Haskins <harlan at harlanhaskins.com> writes:
> harlanhaskins updated this revision to Diff 51615.
> harlanhaskins added a comment.
>
> Removed comma from HTML function title
>
> It doesn't make much sense when it's in its own, visually distinct box, and
> can actually be confusing if demangled.

This is getting pretty close. A couple more comments below.

>
> http://reviews.llvm.org/D18278
>
> Files:
>   test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.covmapping
>   test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.proftext
>   test/tools/llvm-cov/Inputs/templateInstantiationsHTML.covmapping
>   test/tools/llvm-cov/Inputs/templateInstantiationsHTML.proftext
>   test/tools/llvm-cov/showLineExecutionCounts.cpp
>   test/tools/llvm-cov/showLineExecutionCountsHTML.cpp
>   test/tools/llvm-cov/showTemplateInstantiationsHTML.cpp
>   tools/llvm-cov/CodeCoverage.cpp
>   tools/llvm-cov/CoverageCSS.inc
>   tools/llvm-cov/CoverageViewOptions.h
>   tools/llvm-cov/SourceCoverageView.cpp
>   tools/llvm-cov/SourceCoverageView.h
>
> Index: tools/llvm-cov/SourceCoverageView.h
> ===================================================================
> --- tools/llvm-cov/SourceCoverageView.h
> +++ tools/llvm-cov/SourceCoverageView.h
> @@ -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,39 +106,69 @@
>  
>    const MemoryBuffer &File;
>    const CoverageViewOptions &Options;
> +  StringRef SourceName;
>    coverage::CoverageData CoverageInfo;
>    std::vector<ExpansionView> ExpansionSubViews;
>    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 output(llvm::raw_ostream &OS, StringRef Text,
> +                      llvm::Optional<llvm::raw_ostream::Colors> Highlight,
> +                      const coverage::CoverageSegment *Segment = nullptr) = 0;
>  
> -  void renderIndent(raw_ostream &OS, unsigned Level);
> -
> -  void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
> +  /// \brief Render a source line with highlighting.
> +  virtual void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> +                          const coverage::CoverageSegment *WrappedSegment,
> +                          ArrayRef<const coverage::CoverageSegment *> Segments,
> +                          unsigned ExpansionCol);
>  
>    /// \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);

I find these methods kind of awkward and confusing. They have
implementations in the base class, but aren't really useful unless
they're overridden in the subclasses (which then call into the base
class version).

It'd be better to split out the parts where it makes sense to share code
into (non-virtual) helper methods with more specific names and to keep
the entire interface of the SourceCoverageView base class pure virtual.

> +
> +  /// \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; }
>  
> @@ -153,7 +186,145 @@
>  
>    /// \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
> Index: tools/llvm-cov/SourceCoverageView.cpp
> ===================================================================
> --- tools/llvm-cov/SourceCoverageView.cpp
> +++ tools/llvm-cov/SourceCoverageView.cpp
> @@ -12,13 +12,125 @@
>  //===----------------------------------------------------------------------===//
>  
>  #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";

Please switch over all of the values of the Colors enum here. This
implementation will lead to weird behaviour later when somebody tries to
color something yellow ;)

> +  }
> +}
> +
> +/// 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));
> +  default:
> +    llvm_unreachable("unknown ouput format");
> +  }

Get rid of the default case and put the llvm_unreachable after the
switch so that -Wcovered-switch works here.

> +}
> +
> +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 +147,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 +160,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 +172,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,19 +217,18 @@
>        LineCount.addRegionCount(WrappedSegment->Count);
>      for (const auto *S : LineSegments)
>        if (S->HasCount && S->IsRegionEntry)
> -          LineCount.addRegionStartCount(S->Count);
> +        LineCount.addRegionStartCount(S->Count);
> +
> +    startLine(OS, IndentLevel);
>  
> -    // Render the line prefix.
> -    renderIndent(OS, IndentLevel);
>      if (Options.ShowLineStats)
>        renderLineCoverageColumn(OS, LineCount);
>      if (Options.ShowLineNumbers)
>        renderLineNumberColumn(OS, LI.line_number());
>  
>      // 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 +239,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);
> +  }

Don't bother with braces in single-line if statements.

> +}
> +
> +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/CoverageViewOptions.h
> ===================================================================
> --- tools/llvm-cov/CoverageViewOptions.h
> +++ tools/llvm-cov/CoverageViewOptions.h
> @@ -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/CoverageCSS.inc
> ===================================================================
> --- /dev/null
> +++ tools/llvm-cov/CoverageCSS.inc
> @@ -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/CodeCoverage.cpp
> ===================================================================
> --- tools/llvm-cov/CodeCoverage.cpp
> +++ tools/llvm-cov/CodeCoverage.cpp
> @@ -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).str();
> +  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);
> +    }

No braces here either.

> +
>      // 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: test/tools/llvm-cov/showTemplateInstantiationsHTML.cpp
> ===================================================================
> --- /dev/null
> +++ test/tools/llvm-cov/showTemplateInstantiationsHTML.cpp
> @@ -0,0 +1,28 @@
> +// RUN: llvm-profdata merge -o %t.profdata %S/Inputs/templateInstantiationsHTML.proftext
> +// RUN: llvm-cov show %S/Inputs/templateInstantiationsHTML.covmapping -format html -instr-profile %t.profdata -filename-equivalence %s | FileCheck -check-prefix=CHECK %s
> +// RUN: llvm-cov show %S/Inputs/templateInstantiationsHTML.covmapping -format html -instr-profile %t.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
> +// RUN: llvm-cov show %S/Inputs/templateInstantiationsHTML.covmapping -format html -instr-profile %t.profdata -filename-equivalence %s | FileCheck -check-prefix=CHECK %s
> +// RUN: llvm-cov show %S/Inputs/templateInstantiationsHTML.covmapping -format html -instr-profile %t.profdata -filename-equivalence -name=_Z4funcIbEiT_ %s | FileCheck -check-prefix=CHECK %s
> Index: test/tools/llvm-cov/showLineExecutionCountsHTML.cpp
> ===================================================================
> --- /dev/null
> +++ test/tools/llvm-cov/showLineExecutionCountsHTML.cpp
> @@ -0,0 +1,37 @@
> +// RUN: llvm-profdata merge -o %t.profdata %S/Inputs/lineExecutionCountsHTML.proftext
> +// RUN: llvm-cov show %S/Inputs/lineExecutionCountsHTML.covmapping -format html -instr-profile %t.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 %t.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+2]]</pre></td>
> +                                    // CHECK: <td class='code'><pre>
> + int main() {                       // CHECK: int main() {
> +                                    // CHECK: </pre></td>
> +                                    // CHECK: <td class='numeric'><pre>1</pre></td>
> +                                    // CHECK: <td class='numeric'><pre>[[@LINE+2]]</pre></td>
> +                                    // CHECK: <td class='code'><pre>
> +  int x = 0;                        // CHECK: int x = 0;
> +                                    // CHECK: <td class='numeric'><pre>101</pre></td>
> +                                    // CHECK: <td class='numeric'><pre>[[@LINE+2]]</pre></td>
> +                                    // CHECK: <td class='code'><pre>
> +  for (int i = 0; i < 100; i++) {   // CHECK: for (int i = 0;
> +                                    // CHECK: <td class='numeric'><pre>100</pre></td>
> +                                    // CHECK: <td class='numeric'><pre>[[@LINE+2]]</pre></td>
> +                                    // CHECK: <td class='code'><pre>
> +    x++;                            // CHECK: x++;
> +                                    // CHECK: <td class='numeric'><pre>100</pre></td>
> +                                    // CHECK: <td class='numeric'><pre>[[@LINE+2]]</pre></td>
> +                                    // CHECK: <td class='code'><pre>
> +    if (0) {                        // CHECK: if (0)
> +                                    // CHECK: <span class='red'>{
> +                                    // CHECK: <td class='numeric'><pre>0</pre></td>
> +                                    // CHECK: <td class='numeric'><pre>[[@LINE+2]]</pre></td>
> +                                    // CHECK: <td class='code'><pre>
> +      x += 10;                      // CHECK: x += 10;
> +    }
> +  }
> +  return 0;
> +}
> +// RUN: llvm-cov show %S/Inputs/lineExecutionCountsHTML.covmapping -format html -instr-profile %t.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 %t.profdata -filename-equivalence -name=main %s | FileCheck -check-prefix=CHECK  %s
> Index: test/tools/llvm-cov/showLineExecutionCounts.cpp
> ===================================================================
> --- test/tools/llvm-cov/showLineExecutionCounts.cpp
> +++ test/tools/llvm-cov/showLineExecutionCounts.cpp
> @@ -28,3 +28,7 @@
>  
>  // RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata -filename-equivalence %s | FileCheck -check-prefix=CHECK -check-prefix=WHOLE-FILE %s
>  // RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata -filename-equivalence -name=main %s | FileCheck -check-prefix=CHECK -check-prefix=FILTER %s
> +// RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata -filename-equivalence -output-dir coverage-output %s
> +// RUN: FileCheck -check-prefix=CHECK -check-prefix=WHOLE-FILE -input-file coverage-output/tmp/showLineExecutionCounts.cpp.txt %s
> +// RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata -filename-equivalence -name=main -output-dir coverage-output %s
> +// RUN: FileCheck -check-prefix=CHECK -check-prefix=WHOLE-FILE -input-file coverage-output/tmp/showLineExecutionCounts.cpp.txt %s
> Index: test/tools/llvm-cov/Inputs/templateInstantiationsHTML.proftext
> ===================================================================
> --- /dev/null
> +++ test/tools/llvm-cov/Inputs/templateInstantiationsHTML.proftext
> @@ -0,0 +1,26 @@
> +main
> +# Func Hash:
> +0
> +# Num Counters:
> +1
> +# Counter Values:
> +1
> +
> +_Z4funcIbEiT_
> +# Func Hash:
> +10
> +# Num Counters:
> +2
> +# Counter Values:
> +1
> +1
> +
> +_Z4funcIiEiT_
> +# Func Hash:
> +10
> +# Num Counters:
> +2
> +# Counter Values:
> +1
> +0
> +
> Index: test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.proftext
> ===================================================================
> --- /dev/null
> +++ test/tools/llvm-cov/Inputs/lineExecutionCountsHTML.proftext
> @@ -0,0 +1,10 @@
> +main
> +# Func Hash:
> +266
> +# Num Counters:
> +3
> +# Counter Values:
> +1
> +100
> +0
> +


More information about the llvm-commits mailing list