[PATCH][RFC] llvm-cov HTML generation

Justin Bogner via llvm-commits llvm-commits at lists.llvm.org
Mon Mar 7 09:02:47 PST 2016


Harlan Haskins <hhaskins at apple.com> writes:
> Hi all,
>
> Thanks again for the reviews!
>
> I’ve restructured how I handle subviews and pulled out the common behavior. I
> also addressed the issue with highlighting and showing macro expansions (and
> found a possible bug in clang because of it).
>
> Attached is a) a new patch, and b) an HTML file showing a single line
> multi-macro expansion.

This is coming along, thanks!

I wonder if the rendering logic would be easier to follow if you
structured it as a concrete/final SourceCoverageView class that "has-a"
TextRenderer/HTMLRenderer. I suspect that most of the rendering state
could be hidden inside the renderer type and separated a bit from the
logic for walking the coverage. Thoughts?

> Thanks,
> Harlan Haskins
>
>
> Index: tools/llvm-cov/CodeCoverage.cpp
> ===================================================================
> --- tools/llvm-cov/CodeCoverage.cpp	(revision 262364)
> +++ tools/llvm-cov/CodeCoverage.cpp	(working copy)
> @@ -51,6 +51,10 @@
>    /// \brief Print the error message to the error output stream.
>    void error(const Twine &Message, StringRef Whence = "");
>  
> +  /// \brief Return a pointer to a stream for the given file.
> +  ///        Passing "" yields stdout.
> +  std::unique_ptr<raw_ostream> getStreamForFile(std::string Filename);
> +
>    /// \brief Return a memory buffer for the given source file.
>    ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile);
>  
> @@ -100,6 +104,22 @@
>    errs() << Message << "\n";
>  }
>  
> +std::unique_ptr<raw_ostream>
> +CodeCoverageTool::getStreamForFile(std::string Filename) {
> +  if (Filename == "") {
> +    return make_unique<raw_fd_ostream>(fileno(stdout), false);
> +  }
> +
> +  std::error_code Error;
> +  auto OS = make_unique<raw_fd_ostream>(Filename, Error, sys::fs::F_RW);
> +  if (Error) {
> +    error(Error.message(), "getting stream for " + Filename);
> +    return nullptr;
> +  }
> +
> +  return std::move(OS);
> +}
> +
>  ErrorOr<const MemoryBuffer &>
>  CodeCoverageTool::getSourceFile(StringRef SourceFile) {
>    // If we've remapped filenames, look up the real location for this file.
> @@ -135,8 +155,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 +174,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 +192,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 +337,16 @@
>      ViewOpts.Debug = DebugDump;
>      CompareFilenamesOnly = FilenameEquivalence;
>  
> -    ViewOpts.Colors = UseColor == cl::BOU_UNSET
> -                          ? sys::Process::StandardOutHasColors()
> -                          : UseColor == cl::BOU_TRUE;
> +    switch (ViewOpts.Format) {
> +    case CoverageViewOptions::OFConsole:
> +      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 +435,18 @@
>                                     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::OFHTML, "html", "HTML output"),
> +                 clEnumValN(CoverageViewOptions::OFConsole, "console",
> +                            "Console table output"),
> +                 clEnumValEnd),
> +      cl::init(CoverageViewOptions::OFConsole));
> +
>    auto Err = commandLineParser(argc, argv);
>    if (Err)
>      return Err;
> @@ -417,12 +458,29 @@
>    ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts;
>    ViewOpts.ShowExpandedRegions = ShowExpansions;
>    ViewOpts.ShowFunctionInstantiations = ShowInstantiations;
> +  ViewOpts.Format = Format;
>  
>    auto Coverage = load();
>    if (!Coverage)
>      return 1;
>  
> +  bool isHTML = Format == CoverageViewOptions::OFHTML;
> +  std::string FileExt = isHTML ? "html" : "txt";
> +
>    if (!Filters.empty()) {
> +    std::string OutputPath = OutputDirectory;
> +    if (OutputPath != "") {
> +      sys::fs::create_directories(OutputDirectory);
> +      OutputPath += "/functions." + FileExt;
> +    }
> +    auto OS = getStreamForFile(OutputPath);
> +    if (!OS)
> +      return 1;
> +
> +    if (isHTML) {
> +      SourceCoverageViewHTML::renderFileHeader(*OS);
> +    }
> +
>      // Show functions
>      for (const auto &Function : Coverage->getCoveredFunctions()) {
>        if (!Filters.matches(Function))
> @@ -435,12 +493,13 @@
>          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;
>    }
>  
> @@ -453,21 +512,25 @@
>        SourceFiles.push_back(Filename);
>  
>    for (const auto &SourceFile : SourceFiles) {
> +    auto Basename = sys::path::parent_path(SourceFile);
> +    sys::fs::create_directories(OutputDirectory + Basename);
> +    std::string OutputPath = OutputDirectory;
> +    if (OutputPath != "") {
> +      OutputPath += "/" + std::string(SourceFile) + "." + FileExt;
> +    }
> +    auto OS = getStreamForFile(OutputPath);
> +    if (!OS)
> +      return 1;
>      auto mainView = createSourceFileView(SourceFile, *Coverage);
>      if (!mainView) {
> -      ViewOpts.colored_ostream(outs(), raw_ostream::RED)
> +      ViewOpts.colored_ostream(errs(), raw_ostream::RED)
>            << "warning: The file '" << SourceFile << "' isn't covered.";
> -      outs() << "\n";
> +      errs() << "\n";
>        continue;
>      }
> -
> -    if (ShowFilenames) {
> -      ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":";
> -      outs() << "\n";
> -    }
> -    mainView->render(outs(), /*Wholefile=*/true);
> +    mainView->render(*OS, /*Wholefile=*/true, /*RenderTitle=*/ShowFilenames);
>      if (SourceFiles.size() > 1)
> -      outs() << "\n";
> +      *OS << "\n";
>    }
>  
>    return 0;
> Index: tools/llvm-cov/CoverageViewOptions.h
> ===================================================================
> --- tools/llvm-cov/CoverageViewOptions.h	(revision 262364)
> +++ tools/llvm-cov/CoverageViewOptions.h	(working copy)
> @@ -16,6 +16,12 @@
>  
>  /// \brief The options for displaying the code coverage information.
>  struct CoverageViewOptions {
> +  enum OutputFormat {
> +    /// \brief ASCII table output.
> +    OFConsole,
> +    /// \brief An HTML directory.
> +    OFHTML
> +  };
>    bool Debug;
>    bool Colors;
>    bool ShowLineNumbers;
> @@ -25,6 +31,7 @@
>    bool ShowExpandedRegions;
>    bool ShowFunctionInstantiations;
>    bool ShowFullFilenames;
> +  OutputFormat Format;
>  
>    /// \brief Change the output's stream color if the colors are enabled.
>    ColoredRawOstream colored_ostream(raw_ostream &OS,
> Index: tools/llvm-cov/SourceCoverageView.cpp
> ===================================================================
> --- tools/llvm-cov/SourceCoverageView.cpp	(revision 262364)
> +++ tools/llvm-cov/SourceCoverageView.cpp	(working copy)
> @@ -12,13 +12,118 @@
>  //===----------------------------------------------------------------------===//
>  
>  #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 {
> +/// 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 classForColor(raw_ostream::Colors Color) {
> +  switch (Color) {
> +  case raw_ostream::RED:
> +    return "red";
> +  case raw_ostream::CYAN:
> +    return "cyan";
> +  case raw_ostream::BLUE:
> +    return "blue";
> +  case raw_ostream::BLACK:
> +    return "black";
> +  case raw_ostream::GREEN:
> +    return "green";
> +  case raw_ostream::MAGENTA:
> +    return "magenta";
> +  default:
> +    return "clear";
> +  }
> +}
> +
> +/// Format a count using engineering notation with 3 significant digits.
> +std::string formatCount(uint64_t N) {
> +  std::string Number = utostr(N);
> +  int Len = Number.size();
> +  if (Len <= 3)
> +    return Number;
> +  int IntLen = Len % 3 == 0 ? 3 : Len % 3;
> +  std::string Result(Number.data(), IntLen);
> +  if (IntLen != 3) {
> +    Result.push_back('.');
> +    Result += Number.substr(IntLen, 3 - IntLen);
> +  }
> +  Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
> +  return Result;
> +}
> +
> +//===----------------------------------------------------------------------===//
> +//
> +// SourceCoverageView
> +//
> +//===----------------------------------------------------------------------===//
> +
> +std::unique_ptr<SourceCoverageView> SourceCoverageView::create(
> +    const MemoryBuffer &File, const CoverageViewOptions &Options,
> +    StringRef SourceName, coverage::CoverageData &&CoverageInfo) {
> +  switch (Options.Format) {
> +  case CoverageViewOptions::OFHTML:
> +    return make_unique<SourceCoverageViewHTML>(File, Options, SourceName,
> +                                               std::move(CoverageInfo));
> +  case CoverageViewOptions::OFConsole:
> +    return make_unique<SourceCoverageViewConsole>(File, Options, SourceName,
> +                                                  std::move(CoverageInfo));
> +  }
> +}
> +
> +void SourceCoverageView::renderExpansionView(
> +    raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView,
> +    const coverage::CoverageSegment *WrappedSegment,
> +    SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> +    bool RenderedSubview, unsigned IndentLevel, unsigned NestedIndent,
> +    unsigned CombinedColumnWidth, unsigned DividerWidth) {
> +
> +  if (Options.Debug)
> +    errs() << "Expansion at line " << ExpansionView.getLine() << ", "
> +           << ExpansionView.getStartCol() << " -> " << ExpansionView.getEndCol()
> +           << "\n";
> +  ExpansionView.View->render(OS, /*WholeFile=*/false,
> +                             /*RenderTitle*/ false, NestedIndent);
> +}
> +
> +void SourceCoverageView::renderInstantiationView(
> +    raw_ostream &OS, InstantiationView &InstantiationView, unsigned IndentLevel,
> +    unsigned NestedIndent, unsigned DividerWidth) {
> +  InstantiationView.View->render(OS, /*WholeFile=*/false,
> +                                 /*RenderTitle=*/true, NestedIndent);
> +}
> +
>  void SourceCoverageView::renderLine(
>      raw_ostream &OS, StringRef Line, int64_t LineNumber,
>      const coverage::CoverageSegment *WrappedSegment,
> @@ -35,9 +140,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 +153,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 +165,13 @@
>    }
>  }
>  
> -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");
> -}
> -
> +#include <unistd.h>
>  void SourceCoverageView::render(raw_ostream &OS, bool WholeFile,
> -                                unsigned IndentLevel) {
> +                                bool RenderTitle, unsigned IndentLevel) {
>    // The width of the leading columns
>    unsigned CombinedColumnWidth =
>        (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
>        (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
> -  // The width of the line that is used to divide between the view and the
> -  // subviews.
> -  unsigned DividerWidth = CombinedColumnWidth + 4;
> -
>    // We need the expansions and instantiations sorted so we can go through them
>    // while we iterate lines.
>    std::sort(ExpansionSubViews.begin(), ExpansionSubViews.end());
> @@ -191,10 +211,10 @@
>        LineCount.addRegionCount(WrappedSegment->Count);
>      for (const auto *S : LineSegments)
>        if (S->HasCount && S->IsRegionEntry)
> -          LineCount.addRegionStartCount(S->Count);
> +        LineCount.addRegionStartCount(S->Count);
>  
> -    // Render the line prefix.
> -    renderIndent(OS, IndentLevel);
> +    startLine(OS, IndentLevel);
> +
>      if (Options.ShowLineStats)
>        renderLineCoverageColumn(OS, LineCount);
>      if (Options.ShowLineNumbers)
> @@ -202,8 +222,7 @@
>  
>      // If there are expansion subviews, we want to highlight the first one.
>      unsigned ExpansionColumn = 0;
> -    if (NextESV != EndESV && NextESV->getLine() == LI.line_number() &&
> -        Options.Colors)
> +    if (NextESV != EndESV && NextESV->getLine() == LI.line_number())
>        ExpansionColumn = NextESV->getStartCol();
>  
>      // Display the source code for the current line.
> @@ -214,51 +233,339 @@
>      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));
> +    EscapedText = html::tag("span", 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><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'><pre>" << std::string(SourceName)
> +     << ":</pre></div>";
> +}
> +
> +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) {
> +  OS << "<div class='expansion-view'>\n";
> +  // 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";
> +  }
> +
> +  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"
> +        "    <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) {
> +  std::string tableDecl;
> +
> +  if (WholeFile) {
> +    renderFileHeader(OS);
> +    tableDecl = "<table class='centered'>\n";
> +  } else {
> +    tableDecl = "<table>\n";
> +  }
> +
> +  if (RenderTitle)
> +    renderTitle(OS);
> +
> +  OS << tableDecl;
> +  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"; }
> +
> +//===----------------------------------------------------------------------===//
> +//
> +// SourceCoverageViewConsole
> +//
> +//===----------------------------------------------------------------------===//
> +
> +void SourceCoverageViewConsole::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 SourceCoverageViewConsole::render(raw_ostream &OS, bool WholeFile,
> +                                       bool RenderTitle, unsigned IndentLevel) {
> +  if (RenderTitle)
> +    renderTitle(OS);
> +  SourceCoverageView::render(OS, WholeFile, RenderTitle, IndentLevel);
> +}
> +
> +void SourceCoverageViewConsole::renderTitle(raw_ostream &OS) {
> +  Options.colored_ostream(OS, raw_ostream::CYAN) << SourceName << ":\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 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);
> +  for (unsigned I = 0; I < Length; ++I)
> +    OS << "-";
> +}
> +
> +void SourceCoverageViewConsole::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 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 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 SourceCoverageViewConsole::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 SourceCoverageViewConsole::finishRenderingSubviews(raw_ostream &OS,
> +                                                        unsigned IndentLevel,
> +                                                        unsigned DividerWidth) {
> +  renderViewDivider(IndentLevel, DividerWidth, OS);
> +  OS << "\n";
> +}
> +
> +void SourceCoverageViewConsole::renderExpansionView(
> +    raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView,
> +    const coverage::CoverageSegment *WrappedSegment,
> +    SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> +    bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent,
> +    unsigned CombinedColumnWidth, unsigned DividerWidth) {
> +
> +  renderViewDivider(NestedIndent, DividerWidth, OS);
> +  OS << "\n";
> +  if (RenderedSubView) {
> +    // Re-render the current line and highlight the expansion range for
> +    // this subview.
> +    unsigned ExpansionColumn = ExpansionView.getStartCol();
> +    renderIndent(OS, IndentLevel);
> +    OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
> +    renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
> +               ExpansionColumn);
> +    renderViewDivider(NestedIndent, DividerWidth, OS);
> +    OS << "\n";
> +  }
> +  SourceCoverageView::renderExpansionView(
> +      OS, LI, ExpansionView, WrappedSegment, LineSegments, RenderedSubView,
> +      IndentLevel, NestedIndent, CombinedColumnWidth, DividerWidth);
> +}
> Index: tools/llvm-cov/SourceCoverageView.h
> ===================================================================
> --- tools/llvm-cov/SourceCoverageView.h	(revision 262364)
> +++ tools/llvm-cov/SourceCoverageView.h	(working copy)
> @@ -17,6 +17,9 @@
>  #include "CoverageViewOptions.h"
>  #include "llvm/ProfileData/CoverageMapping.h"
>  #include "llvm/Support/MemoryBuffer.h"
> +#include "llvm/Support/LineIterator.h"
> +#include "llvm/ADT/Optional.h"
> +#include "llvm/ADT/STLExtras.h"
>  #include <vector>
>  
>  namespace llvm {
> @@ -76,7 +79,7 @@
>  /// \brief A code coverage view of a specific source file.
>  /// It can have embedded coverage views.
>  class SourceCoverageView {
> -private:
> +protected:
>    /// \brief Coverage information for a single line.
>    struct LineCoverageInfo {
>      uint64_t ExecutionCount;
> @@ -103,40 +106,70 @@
>  
>    const MemoryBuffer &File;
>    const CoverageViewOptions &Options;
> +  StringRef SourceName;
>    coverage::CoverageData CoverageInfo;
>    std::vector<ExpansionView> ExpansionSubViews;
>    std::vector<InstantiationView> InstantiationSubViews;
>  
> +  virtual void output(llvm::raw_ostream &OS, StringRef Text,
> +                      llvm::Optional<llvm::raw_ostream::Colors> Highlight,
> +                      const coverage::CoverageSegment *Segment = nullptr) = 0;
> +
>    /// \brief Render a source line with highlighting.
> -  void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> -                  const coverage::CoverageSegment *WrappedSegment,
> -                  ArrayRef<const coverage::CoverageSegment *> Segments,
> -                  unsigned ExpansionCol);
> +  virtual void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> +                          const coverage::CoverageSegment *WrappedSegment,
> +                          ArrayRef<const coverage::CoverageSegment *> Segments,
> +                          unsigned ExpansionCol);
>  
> -  void renderIndent(raw_ostream &OS, unsigned Level);
> -
> -  void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
> -
>    /// \brief Render the line's execution count column.
> -  void renderLineCoverageColumn(raw_ostream &OS, const LineCoverageInfo &Line);
> +  virtual void renderLineCoverageColumn(raw_ostream &OS,
> +                                        const LineCoverageInfo &Line) = 0;
>  
>    /// \brief Render the line number column.
> -  void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo);
> +  virtual void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) = 0;
>  
>    /// \brief Render all the region's execution counts on a line.
> -  void
> +  virtual void
>    renderRegionMarkers(raw_ostream &OS,
> -                      ArrayRef<const coverage::CoverageSegment *> Segments);
> +                      ArrayRef<const coverage::CoverageSegment *> Segments,
> +                      unsigned IndentLevel, unsigned CombinedColumnWidth);
>  
> +  /// \brief Render an expansion view inline, using format-specific logic.
> +  virtual void renderExpansionView(
> +      raw_ostream &OS, line_iterator &LI, ExpansionView &ExpansionView,
> +      const coverage::CoverageSegment *WrappedSegment,
> +      SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> +      bool RenderedSubView, unsigned IndentLevel, unsigned NestedIndent,
> +      unsigned CombinedColumnWidth, unsigned DividerWidth);
> +
> +  /// \brief Render an instantiation view inline, using format-specific logic.
> +  virtual void renderInstantiationView(raw_ostream &OS,
> +                                       InstantiationView &InstantiationView,
> +                                       unsigned IndentLevel,
> +                                       unsigned NestedIndent,
> +                                       unsigned DividerWidth);
> +
> +  /// \brief Render all the region's execution counts on a line.
> +  virtual void finishRenderingSubviews(raw_ostream &OS, unsigned IndentLevel,
> +                                       unsigned DividerWidth) = 0;
> +
> +  virtual void startLine(raw_ostream &OS, unsigned IndentLevel) {}
> +  virtual void endLine(raw_ostream &OS) {}
> +
>    static const unsigned LineCoverageColumnWidth = 7;
>    static const unsigned LineNumberColumnWidth = 5;
>  
>  public:
>    SourceCoverageView(const MemoryBuffer &File,
> -                     const CoverageViewOptions &Options,
> +                     const CoverageViewOptions &Options, StringRef SourceName,
>                       coverage::CoverageData &&CoverageInfo)
> -      : File(File), Options(Options), CoverageInfo(std::move(CoverageInfo)) {}
> +      : File(File), Options(Options), SourceName(SourceName),
> +        CoverageInfo(std::move(CoverageInfo)) {}
>  
> +  static std::unique_ptr<SourceCoverageView>
> +  create(const MemoryBuffer &File, const CoverageViewOptions &Options,
> +         StringRef SourceName, coverage::CoverageData &&CoverageInfo);
> +
>    const CoverageViewOptions &getOptions() const { return Options; }
>  
>    /// \brief Add an expansion subview to this view.
> @@ -153,9 +186,144 @@
>  
>    /// \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)) {
> +  }
> +
> +  // \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 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(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:
> +  SourceCoverageViewConsole(const MemoryBuffer &File,
> +                            const CoverageViewOptions &Options,
> +                            StringRef SourceName,
> +                            coverage::CoverageData &&CoverageInfo)
> +      : SourceCoverageView(File, Options, SourceName, std::move(CoverageInfo)) {
> +  }
> +
> +  /// \brief Render the title for a given source region (function or file name)
> +  void renderTitle(raw_ostream &OS) override;
> +};
> +
>  } // namespace llvm
>  
>  #endif // LLVM_COV_SOURCECOVERAGEVIEW_H
>     On Mar 2, 2016, at 3:00 PM, Harlan Haskins via llvm-commits <
>     llvm-commits at lists.llvm.org> wrote:
>    
>     Oh, I see! Yeah, this seems like something I overlooked. The HTML view
>     currently just shows two expansions, one after the other.
>    
>     I’ll revise the subview rendering with this in mind. Thanks for the simple
>     example case!
>    
>     Best,
>     Harlan
>
>         On Mar 2, 2016, at 2:40 PM, Xinliang David Li via llvm-commits <
>         llvm-commits at lists.llvm.org> wrote:
>
>         On Wed, Mar 2, 2016 at 1:41 PM, Harlan Haskins <hhaskins at apple.com> 
>         wrote:
>        
>             Hi David,
>            
>             Specifically with renderSubviews, in my refactor it seemed that
>             their bodies were different enough (i.e. SourceCoverageViewConsole
>             needs to track state between loop invocations to know whether or
>             not to display a final view divider, and the common behavior is
>             really just looping over the instantiation and expansion subviews.
>             I can investigate converging them more, but I think it’s going to
>             increase complexity.
>
>         Emitting the final divider is easy to abstract away. The main
>         difference I see is that in Console view, if there are multiple macro
>         expansions in the same line, the line will be re-rendered again in
>         order to highlight the macro. For instance given the following line
>         with two macros,
>           
>            MY_MACRO(10, 10); MY_MACRO(20,10);    // line 10
>        
>         The console dump will be two lines:
>        
>         1|   10| MY_MACRO(10, 10); MY_MACRO(20,10);    // line 10
>                  ^^^^^^^^^^^
>               .... expansion lines 
>        
>                  MY_MACRO(10, 10); MY_MACRO(20,10);    // line 10             
>                                                                ^^^^^^^^^^^^^
>                 .... expansion lines
>
>         Does HTML view lose that functionality? 
>        
>         Also the template instantiation subview rendering code looks
>         sufficiently close between two classes.
>        
>         thanks,
>        
>         David
>
>             Also, I definitely need to add a test case.
>            
>             Thanks!
>             Harlan
>
>                 On Mar 2, 2016, at 10:43 AM, Xinliang David Li <
>                 xinliangli at gmail.com> wrote:
>                
>                 Hi Harlan,
>                
>                 This looks great! Some high level comments. I find the code
>                 can be further restructured
>                 1) high level methods can be commoned between two derived
>                 classes  (and pushed to the base class) -- such as
>                 renderSubviews
>                 2) the subclasses just need to provide virtual functions that
>                 implement the view specific low level routines.
>                
>                 Also there does not seem to be a test case.
>                
>                 thanks,
>                
>                 David
>                
>                 On Tue, Mar 1, 2016 at 6:03 PM, Harlan Haskins via
>                 llvm-commits <llvm-commits at lists.llvm.org> wrote:
>                
>                     Oops, forgot to add a file to the patch.
>                    
>                     New patch attached.
>
>                         On Mar 1, 2016, at 5:48 PM, Harlan Haskins via
>                         llvm-commits <llvm-commits at lists.llvm.org> wrote:
>                        
>                         Hi all,
>                        
>                         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>
>                        
>                         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).
>                        
>                         Thanks,
>                         Harlan Haskins
>                        
>                         <AliasAnalysis.h.html>
>                         <llvm-cov-html.diff>
>                         _______________________________________________
>                         llvm-commits mailing list
>                         llvm-commits at lists.llvm.org
>                         http://lists.llvm.org/cgi-bin/mailman/listinfo/
>                         llvm-commits
>
>                     _______________________________________________
>                     llvm-commits mailing list
>                     llvm-commits at lists.llvm.org
>                     http://lists.llvm.org/cgi-bin/mailman/listinfo/
>                     llvm-commits
>
>         _______________________________________________
>         llvm-commits mailing list
>         llvm-commits at lists.llvm.org
>         http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
>
>     _______________________________________________
>     llvm-commits mailing list
>     llvm-commits at lists.llvm.org
>     http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits


More information about the llvm-commits mailing list