[PATCH][RFC] llvm-cov HTML generation

Justin Bogner via llvm-commits llvm-commits at lists.llvm.org
Thu Mar 10 14:57:06 PST 2016


Harlan Haskins <hhaskins at apple.com> writes:
>     On Mar 10, 2016, at 1:30 PM, Ying Yi <maggieyi666 at gmail.com> wrote:
>
>     Thanks for your reply and the updated patch.
>    
>     1) When I use the release build of llvm-cov to generate HTML files, all
>     the HTML files are named “html”. This issue also exists on UNIX.
>    
> I haven’t seen this happen. Can you provide some steps to reproduce? Thanks!
>
>     2) The following warning is given when building llvm-cov using Visual
>     Studio 2013.
>    
>     “sourcecoverageview.cpp (108): warning C4715:
>     'llvm::SourceCoverageView::create' : not all control paths return a value”
>    
> That warning is incorrect. It’s a switch statement over both cases of an enum
> — I want this because if we add another format, say, OFJSON or something, I
> want a compiler warning telling me the switch case isn’t exhaustive.

The usual way to appease this warning is to throw an llvm_unreachable
after the switch.

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


More information about the llvm-commits mailing list