[PATCH][RFC] llvm-cov HTML generation
Justin Bogner via llvm-commits
llvm-commits at lists.llvm.org
Tue Mar 1 23:18:51 PST 2016
Harlan Haskins via llvm-commits <llvm-commits at lists.llvm.org> writes:
> I’ve got a preliminary implementation of HTML generation for llvm-cov’s
> coverage reports.
>
> The patch adds 2 flags to llvm-cov show:
>
> * -format [html | console]
> * -output-dir <dirname>
This looks really useful. I've cc'd a couple of people I think will be
interested.
Commments inline, mostly nitpicks.
> Specifying -format=console will perform the current behavior (now the
> default), and -format=html will generate an HTML report.
> If -output-dir is specified, then the output is split into one html or txt
> file per source file, named <source-name>.<ext>, with a directory structure
> that mimics the file system structure.
>
> If neither are provided, the behavior remains the same.
>
> I’m hoping to add an index with a browsable list of files within their
> directories, but for now I’ve attached the patch and a sample HTML file (In
> this case, AliasAnalysis.h, as included by swiftc).
Something as simple as a list of files with clickable links would
probably be good enough for a start. You could also go further and do
something like the output of llvm-cov report with clickable links, but
that's probably better as a followup.
Harlan Haskins via llvm-commits <llvm-commits at lists.llvm.org> writes:
> Oops, forgot to add a file to the patch.
>
> New patch attached.
>
> Index: CodeCoverage.cpp
Better to generate diffs from the top level llvm source directory, for
future reference. Then people don't need to figure out where to cd first
(tools/llvm-cov) to apply the patch.
> ===================================================================
> --- CodeCoverage.cpp (revision 262364)
> +++ CodeCoverage.cpp (working copy)
> @@ -33,6 +33,7 @@
> #include "llvm/Support/Signals.h"
> #include <functional>
> #include <system_error>
> +#include <unistd.h>
We should probably avoid unistd here, it's not portable.
>
> using namespace llvm;
> using namespace coverage;
> @@ -47,10 +48,17 @@
> /// \brief The report command.
> Report
> };
> -
> +
> + /// \brief Print an error message and exit.
> + void exitWithErrorCode(std::error_code Error, StringRef Whence);
> +
> /// \brief Print the error message to the error output stream.
> void error(const Twine &Message, StringRef Whence = "");
>
> + /// \brief Return a pointer to a stream for the given file.
> + /// Passing "" yields stdout.
> + std::unique_ptr<raw_ostream> streamForFile(std::string Filename);
> +
> /// \brief Return a memory buffer for the given source file.
> ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile);
>
> @@ -135,7 +143,7 @@
> continue;
>
> auto SubViewExpansions = ExpansionCoverage.getExpansions();
> - auto SubView = llvm::make_unique<SourceCoverageView>(
> + auto SubView = SourceCoverageView::create(
> SourceBuffer.get(), ViewOpts, std::move(ExpansionCoverage));
> attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
> View.addExpansion(Expansion.Region, std::move(SubView));
> @@ -153,7 +161,7 @@
> return nullptr;
>
> auto Expansions = FunctionCoverage.getExpansions();
> - auto View = llvm::make_unique<SourceCoverageView>(
> + auto View = SourceCoverageView::create(
> SourceBuffer.get(), ViewOpts, std::move(FunctionCoverage));
> attachExpansionSubViews(*View, Expansions, Coverage);
>
> @@ -171,14 +179,14 @@
> return nullptr;
>
> auto Expansions = FileCoverage.getExpansions();
> - auto View = llvm::make_unique<SourceCoverageView>(
> + auto View = SourceCoverageView::create(
> SourceBuffer.get(), ViewOpts, std::move(FileCoverage));
> attachExpansionSubViews(*View, Expansions, Coverage);
>
> for (auto Function : Coverage.getInstantiations(SourceFile)) {
> auto SubViewCoverage = Coverage.getCoverageForFunction(*Function);
> auto SubViewExpansions = SubViewCoverage.getExpansions();
> - auto SubView = llvm::make_unique<SourceCoverageView>(
> + auto SubView = SourceCoverageView::create(
> SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage));
> attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
>
> @@ -377,6 +385,26 @@
> return 0;
> }
>
> +std::unique_ptr<raw_ostream>
> +CodeCoverageTool::streamForFile(std::string Filename) {
Functions should usually be verb phrases, like getStreamForFile.
> + if (Filename == "") {
> + return make_unique<raw_fd_ostream>(STDOUT_FILENO, false);
> + } else {
The LLVM style avoids the "else" after an early return. Saves some
indentation.
This applies to a few places later in the patch too.
> + std::error_code Error;
> + auto OS = make_unique<raw_fd_ostream>(Filename,
> + Error, sys::fs::F_RW);
> + if (Error)
> + exitWithErrorCode(Error, __PRETTY_FUNCTION__);
Why __PRETTY_FUNCTION__?
I'm not a big fan of calling exit here - I think it'd be better to just
call error(...) and return nullptr, then CodeCoverageTool::view can
just return 1 or whatever.
> + return move(OS);
This is std::move, right? Are we "using namespace std" somewhere? We
shouldn't be.
> + }
> +}
> +
> +void CodeCoverageTool::exitWithErrorCode(std::error_code Error,
> + StringRef Whence = "") {
> + error(Error.message(), Whence);
> + exit(Error.value());
> +}
> +
> int CodeCoverageTool::show(int argc, const char **argv,
> CommandLineParserType commandLineParser) {
>
> @@ -405,6 +433,20 @@
> cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional,
> cl::desc("Show function instantiations"),
> cl::cat(ViewCategory));
> +
> + cl::opt<std::string> OutputDirectory("o", "output-dir", cl::Optional,
> + cl::desc("Directory to output individual files"));
> +
> + cl::opt<CoverageViewOptions::OutputFormat> Format("f", "format",
> + cl::desc("Format to output comparison"),
> + cl::values(clEnumValN(CoverageViewOptions::HTML,
> + "html",
> + "HTML output"),
> + clEnumValN(CoverageViewOptions::Console,
> + "console",
> + "Console table output"),
> + clEnumValEnd),
> + cl::init(CoverageViewOptions::Console));
>
> auto Err = commandLineParser(argc, argv);
> if (Err)
> @@ -417,12 +459,23 @@
> ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts;
> ViewOpts.ShowExpandedRegions = ShowExpansions;
> ViewOpts.ShowFunctionInstantiations = ShowInstantiations;
> + ViewOpts.Format = Format;
> + ViewOpts.Colors = ViewOpts.Colors && Format == CoverageViewOptions::Console;
>
> auto Coverage = load();
> if (!Coverage)
> return 1;
> -
> +
> +
> if (!Filters.empty()) {
> + std::string outputPath = OutputDirectory;
> + if (outputPath != "") {
> + std::string ext = Format == CoverageViewOptions::HTML ? "html" : "txt";
> + outputPath += "/functions." + ext;
> + }
> + auto os = streamForFile(outputPath);
Variables should start with capital letters. Ie, `OutputPath`, `Ext`,
and `OS`.
> + SourceCoverageViewHTML::renderFileHeader(*os);
> +
> // Show functions
> for (const auto &Function : Coverage->getCoveredFunctions()) {
> if (!Filters.matches(Function))
> @@ -435,12 +488,12 @@
> outs() << "\n";
> continue;
> }
> - ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << Function.Name
> - << ":";
> - outs() << "\n";
> - mainView->render(outs(), /*WholeFile=*/false);
> - outs() << "\n";
> + mainView->renderTitle(*os, Function.Name);
> + *os << "\n";
> + mainView->render(*os, /*WholeFile=*/false);
> + *os << "\n";
> }
> + SourceCoverageViewHTML::renderFileFooter(*os);
> return 0;
> }
>
> @@ -453,21 +506,28 @@
> SourceFiles.push_back(Filename);
>
> for (const auto &SourceFile : SourceFiles) {
> + auto basename = sys::path::parent_path(SourceFile);
> + sys::fs::create_directories(OutputDirectory + basename);
> + auto ext = Format == CoverageViewOptions::HTML ? "html" : "txt";
> + std::string outputPath = OutputDirectory;
> + if (outputPath != "") {
> + outputPath += "/" + std::string(SourceFile) + "." + ext;
> + }
> + std::unique_ptr<raw_ostream> os = streamForFile(outputPath);
The code duplication's kind of gross here. To be fair, that was true
before this patch, but we should probably try to clean that up in a
separate NFC patch either before or after this one.
> auto mainView = createSourceFileView(SourceFile, *Coverage);
> if (!mainView) {
> - ViewOpts.colored_ostream(outs(), raw_ostream::RED)
> + ViewOpts.colored_ostream(errs(), raw_ostream::RED)
> << "warning: The file '" << SourceFile << "' isn't covered.";
> - outs() << "\n";
> + errs() << "\n";
> continue;
> }
> -
> +
> if (ShowFilenames) {
> - ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":";
> - outs() << "\n";
> + mainView->renderTitle(*os, SourceFile);
> }
> - mainView->render(outs(), /*Wholefile=*/true);
> + mainView->render(*os, /*Wholefile=*/true);
> if (SourceFiles.size() > 1)
> - outs() << "\n";
> + *os << "\n";
> }
>
> return 0;
> Index: CoverageViewOptions.h
> ===================================================================
> --- CoverageViewOptions.h (revision 262364)
> +++ CoverageViewOptions.h (working copy)
> @@ -16,6 +16,12 @@
>
> /// \brief The options for displaying the code coverage information.
> struct CoverageViewOptions {
> + enum OutputFormat {
> + /// \brief ASCII table output.
> + Console,
> + /// \brief An HTML directory.
> + HTML
These should probably be named something like OutputFormatConsole or
OFConsole to avoid name collisions.
> + };
> bool Debug;
> bool Colors;
> bool ShowLineNumbers;
> @@ -25,6 +31,7 @@
> bool ShowExpandedRegions;
> bool ShowFunctionInstantiations;
> bool ShowFullFilenames;
> + OutputFormat Format;
>
> /// \brief Change the output's stream color if the colors are enabled.
> ColoredRawOstream colored_ostream(raw_ostream &OS,
> Index: SourceCoverageView.cpp
> ===================================================================
> --- SourceCoverageView.cpp (revision 262364)
> +++ SourceCoverageView.cpp (working copy)
> @@ -12,13 +12,99 @@
> //===----------------------------------------------------------------------===//
>
> #include "SourceCoverageView.h"
> -#include "llvm/ADT/Optional.h"
> #include "llvm/ADT/SmallString.h"
> #include "llvm/ADT/StringExtras.h"
> -#include "llvm/Support/LineIterator.h"
>
> using namespace llvm;
>
> +namespace html {
> + using namespace std;
Please don't "using namespace std".
> + /// \returns An HTML-escaped string.
I don't know what doxygen does with a \returns with no \brief. If you're
sure it's reasonable this is fine, but otherwise I'd just drop the
\returns and reword these like "Escapes the string for HTML".
> + string escape(string s) {
> + string result;
> + for (size_t i = 0; i < s.size(); ++i) {
> + string token = s.substr(i,1);
> + if (token == "&")
> + token = "&";
> + else if (token == "<")
> + token = "<";
> + else if (token == "\"")
> + token = """;
> + else if (token == ">")
> + token = ">";
> + result += token;
> + }
> + return result;
> + }
> +
> + /// \returns A tag around the provided text
> + /// (expects the value to be properly escaped).
> + string tag(string name, string text, string _class = "") {
More variables that should start with capital letters, but note that
_Class would be a reserved name (_Capital always is), so you'd have to
go with Class_.
> + string tag = "<" + name;
> + if (_class != "") {
> + tag += " class='" + _class + "'";
> + }
> + return tag + ">" + text + "</" + name + ">";
> + }
> +}
> +
> +std::string classForColor(raw_ostream::Colors Color) {
> + switch (Color) {
> + case raw_ostream::RED: return "red";
> + case raw_ostream::CYAN: return "cyan";
> + case raw_ostream::BLUE: return "blue";
> + case raw_ostream::BLACK: return "black";
> + case raw_ostream::GREEN: return "green";
> + case raw_ostream::MAGENTA: return "magenta";
> + default: return "clear";
> + }
> +}
> +
> +/// Format a count using engineering notation with 3 significant digits.
> +static std::string formatCount(uint64_t N) {
> + std::string Number = utostr(N);
> + int Len = Number.size();
> + if (Len <= 3)
> + return Number;
> + int IntLen = Len % 3 == 0 ? 3 : Len % 3;
> + std::string Result(Number.data(), IntLen);
> + if (IntLen != 3) {
> + Result.push_back('.');
> + Result += Number.substr(IntLen, 3 - IntLen);
> + }
> + Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
> + return Result;
> +}
> +
> +void SourceCoverageViewHTML::output(raw_ostream &OS, std::string Text,
> + Optional<raw_ostream::Colors> Highlight,
> + const coverage::CoverageSegment
> + *Segment) {
This is formatted funny. I recommend clang-format. You can use the
clang-format-diff or git-clang-format script in the clang repo to just
format your changes (and avoid arbitrary reformatting changes elsewhere).
> + auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR;
> + Text = html::escape(Text);
> + if (Highlight) {
> + Text = html::tag("span", Text, classForColor(Color));
> + }
> + if (Options.ShowRegionMarkers && Segment && Segment->IsRegionEntry) {
> + std::string C = formatCount(Segment->Count);
> + auto countTag = html::tag("span", html::escape(C));
> + Text = html::tag("span", Text + countTag, "tooltips");
> + }
> + OS << Text;
> +}
> +
> +void SourceCoverageViewConsole::output(raw_ostream &OS,
> + std::string Text,
> + Optional<raw_ostream::Colors>
> + Highlight,
> + const coverage::CoverageSegment
> + *Segment) {
> + auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR;
> + colored_ostream(OS, Color,
> + Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
> + << Text;
> +}
> +
> void SourceCoverageView::renderLine(
> raw_ostream &OS, StringRef Line, int64_t LineNumber,
> const coverage::CoverageSegment *WrappedSegment,
> @@ -30,14 +116,13 @@
> // The first segment overlaps from a previous line, so we treat it specially.
> if (WrappedSegment && WrappedSegment->HasCount && WrappedSegment->Count == 0)
> Highlight = raw_ostream::RED;
> -
> +
> // Output each segment of the line, possibly highlighted.
> unsigned Col = 1;
> for (const auto *S : Segments) {
> unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1);
> - colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
> - Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
> - << Line.substr(Col - 1, End - Col);
> + auto Text = Line.substr(Col - 1, End - Col);
> + output(OS, Text, Highlight, S);
> if (Options.Debug && Highlight)
> HighlightedRanges.push_back(std::make_pair(Col, End));
> Col = End;
> @@ -48,13 +133,10 @@
> else
> Highlight = None;
> }
> +
> + auto Rest = Line.substr(Col - 1, Line.size() - Col + 1);
> + output(OS, Rest, Highlight, nullptr);
>
> - // Show the rest of the line
> - colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
> - Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
> - << Line.substr(Col - 1, Line.size() - Col + 1);
> - OS << "\n";
> -
> if (Options.Debug) {
> for (const auto &Range : HighlightedRanges)
> errs() << "Highlighted line " << LineNumber << ", " << Range.first
> @@ -64,13 +146,60 @@
> }
> }
>
> -void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) {
> +void SourceCoverageViewHTML::renderTitle(raw_ostream &OS,
> + StringRef Name) {
> + OS << "<div class='function-title'><pre>"
> + << std::string(Name) << ":</pre></div>";
> +}
> +
> +void SourceCoverageViewConsole::renderTitle(raw_ostream &OS,
> + StringRef Name) {
> + Options.colored_ostream(OS, raw_ostream::CYAN) << Name << ":\n";
> +}
> +
> +void SourceCoverageViewHTML::renderLine(raw_ostream &OS,
> + StringRef Line,
> + int64_t LineNumber,
> + const coverage::CoverageSegment
> + *WrappedSegment,
> + ArrayRef<const
> + coverage::CoverageSegment *>
> + Segments,
> + unsigned int ExpansionCol) {
> + OS << "<td><pre>\n";
> + SourceCoverageView::renderLine(OS, Line, LineNumber,
> + WrappedSegment, Segments,
> + ExpansionCol);
> + OS << "</pre>\n";
> + if (ExpansionSubViews.empty() && InstantiationSubViews.empty()) {
> + // don't end the source line if we're going to embed sub-tables.
> + OS << "</td>\n";
> + }
> +}
> +
> +void SourceCoverageViewConsole::renderLine(raw_ostream &OS,
> + StringRef Line,
> + int64_t LineNumber,
> + const coverage::CoverageSegment
> + *WrappedSegment,
> + ArrayRef<const
> + coverage::CoverageSegment *>
> + Segments,
> + unsigned int ExpansionCol) {
> + SourceCoverageView::renderLine(OS, Line, LineNumber,
> + WrappedSegment, Segments,
> + ExpansionCol);
> + OS << "\n";
> +}
> +
> +void SourceCoverageViewConsole::renderIndent(raw_ostream &OS, unsigned Level) {
> for (unsigned I = 0; I < Level; ++I)
> OS << " |";
> }
>
> -void SourceCoverageView::renderViewDivider(unsigned Level, unsigned Length,
> - raw_ostream &OS) {
> +void SourceCoverageViewConsole::renderViewDivider(unsigned Level,
> + unsigned Length,
> + raw_ostream &OS) {
> assert(Level != 0 && "Cannot render divider at top level");
> renderIndent(OS, Level - 1);
> OS.indent(2);
> @@ -78,50 +207,61 @@
> OS << "-";
> }
>
> -/// Format a count using engineering notation with 3 significant digits.
> -static std::string formatCount(uint64_t N) {
> - std::string Number = utostr(N);
> - int Len = Number.size();
> - if (Len <= 3)
> - return Number;
> - int IntLen = Len % 3 == 0 ? 3 : Len % 3;
> - std::string Result(Number.data(), IntLen);
> - if (IntLen != 3) {
> - Result.push_back('.');
> - Result += Number.substr(IntLen, 3 - IntLen);
> - }
> - Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
> - return Result;
> -}
> -
> -void
> -SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS,
> - const LineCoverageInfo &Line) {
> +void SourceCoverageViewConsole::renderLineCoverageColumn(raw_ostream &OS,
> + const LineCoverageInfo
> + &Line) {
> +
> + std::string C = formatCount(Line.ExecutionCount);
> if (!Line.isMapped()) {
> OS.indent(LineCoverageColumnWidth) << '|';
> return;
> }
> - std::string C = formatCount(Line.ExecutionCount);
> OS.indent(LineCoverageColumnWidth - C.size());
> colored_ostream(OS, raw_ostream::MAGENTA,
> Line.hasMultipleRegions() && Options.Colors)
> - << C;
> + << C;
> OS << '|';
> }
>
> -void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS,
> - unsigned LineNo) {
> +void SourceCoverageViewHTML::renderLineCoverageColumn(raw_ostream &OS,
> + const LineCoverageInfo
> + &Line) {
> + if (Line.isMapped()) {
> + std::string C = formatCount(Line.ExecutionCount);
> + OS << html::tag("td",
> + html::tag("pre", html::escape(C)),
> + "numeric") << "\n";
> + } else {
> + OS << html::tag("td", "", "numeric") << "\n";
> + }
> +}
> +
> +void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
> + unsigned int LineNo) {
> SmallString<32> Buffer;
> raw_svector_ostream BufferOS(Buffer);
> BufferOS << LineNo;
> auto Str = BufferOS.str();
> + OS << html::tag("td",
> + html::tag("pre", Str),
> + "numeric") << "\n";
> +}
> +
> +void SourceCoverageViewConsole::renderLineNumberColumn(raw_ostream &OS,
> + unsigned LineNo) {
> + SmallString<32> Buffer;
> + raw_svector_ostream BufferOS(Buffer);
> + BufferOS << LineNo;
> + auto Str = BufferOS.str();
> // Trim and align to the right
> Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
> OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
> }
>
> -void SourceCoverageView::renderRegionMarkers(
> - raw_ostream &OS, ArrayRef<const coverage::CoverageSegment *> Segments) {
> +void SourceCoverageView::renderRegionMarkers(raw_ostream &OS,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned IndentLevel, unsigned CombinedColumnWidth) {
> + OS.indent(CombinedColumnWidth);
> unsigned PrevColumn = 1;
> for (const auto *S : Segments) {
> if (!S->IsRegionEntry)
> @@ -135,23 +275,158 @@
> OS << '^' << C;
> }
> OS << "\n";
> +}
>
> - if (Options.Debug)
> - for (const auto *S : Segments)
> - errs() << "Marker at " << S->Line << ":" << S->Col << " = "
> - << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n");
> +void SourceCoverageViewConsole::renderRegionMarkers(raw_ostream &OS,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned IndentLevel, unsigned CombinedColumnWidth) {
> + renderIndent(OS, IndentLevel);
> + SourceCoverageView::renderRegionMarkers(OS, Segments,
> + IndentLevel, CombinedColumnWidth);
> }
>
> +void SourceCoverageViewConsole::startLine(raw_ostream &OS,
> + unsigned IndentLevel) {
> + renderIndent(OS, IndentLevel);
> +}
> +
> +void SourceCoverageViewHTML::renderFileHeader(raw_ostream &OS) {
> +#include "css.inc"
A more specific name than css.inc would probably be better. This
probably deserves a comment too.
> + OS << "<!doctype html>\n"
> + "<html>\n"
> + " <head>\n"
> + " <meta name='viewport'"
> + "content='width=device-width,initial-scale=1'>\n"
> + " <style>\n" << css <<
> + " </style>\n"
> + " </head>\n"
> + " <body>\n";
> +}
> +
> +void SourceCoverageViewHTML::renderFileFooter(raw_ostream &OS) {
> + OS << " </body>\n"
> + "</html>\n";
> +}
> +
> +void SourceCoverageViewHTML::render(raw_ostream &OS, bool WholeFile,
> + unsigned IndentLevel) {
> + if (WholeFile) {
> + renderFileHeader(OS);
> + OS << " <table class='centered'>\n";
> + } else {
> + OS << " <table>\n";
> + }
> + SourceCoverageView::render(OS, WholeFile, IndentLevel);
> + OS << " </table>\n";
> + if (WholeFile) {
> + renderFileFooter(OS);
> + }
> +}
> +
> +void SourceCoverageViewHTML::startLine(raw_ostream &OS, unsigned IndentLevel) {
> + OS << "<tr>\n";
> +}
> +
> +void SourceCoverageViewHTML::endLine(raw_ostream &OS) {
> + OS << "</tr>\n";
> +}
> +
> +void SourceCoverageViewConsole::renderSubviews(raw_ostream &OS,
> + line_iterator &LI,
> + std::vector<ExpansionView>::iterator &NextESV,
> + std::vector<ExpansionView>::iterator &EndESV,
> + std::vector<InstantiationView>::iterator &NextISV,
> + std::vector<InstantiationView>::iterator &EndISV,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
> + unsigned IndentLevel, unsigned ExpansionColumn,
> + unsigned CombinedColumnWidth) {
> +
> + // The width of the line that is used to divide between the view and the
> + // subviews.
> + unsigned DividerWidth = CombinedColumnWidth + 4;
> +
> + // Show the expansions and instantiations for this line.
> + unsigned NestedIndent = IndentLevel + 1;
> + bool RenderedSubView = false;
> + for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
> + ++NextESV) {
> +
> + renderViewDivider(NestedIndent, DividerWidth, OS);
> + OS << "\n";
> + if (RenderedSubView) {
> + // Re-render the current line and highlight the expansion range for
> + // this subview.
> + ExpansionColumn = NextESV->getStartCol();
> + renderIndent(OS, IndentLevel);
> + OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
> + renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
> + ExpansionColumn);
> + renderViewDivider(NestedIndent, DividerWidth, OS);
> + OS << "\n";
> + }
> + // Render the child subview
> + if (Options.Debug)
> + errs() << "Expansion at line " << NextESV->getLine() << ", "
> + << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
> + << "\n";
> + NextESV->View->render(OS, false, NestedIndent);
> + RenderedSubView = true;
> + }
> + for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
> + renderViewDivider(NestedIndent, DividerWidth, OS);
> + OS << "\n";
> + renderIndent(OS, NestedIndent);
> + OS << ' ';
> + Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName
> + << ":";
> + OS << "\n";
> + NextISV->View->render(OS, false, NestedIndent);
> + RenderedSubView = true;
> + }
> + if (RenderedSubView) {
> + renderViewDivider(NestedIndent, DividerWidth, OS);
> + OS << "\n";
> + }
> +}
> +
> +void SourceCoverageViewHTML::renderSubviews(raw_ostream &OS,
> + line_iterator &LI,
> + std::vector<ExpansionView>::iterator &NextESV,
> + std::vector<ExpansionView>::iterator &EndESV,
> + std::vector<InstantiationView>::iterator &NextISV,
> + std::vector<InstantiationView>::iterator &EndISV,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8>
> + LineSegments, unsigned IndentLevel,
> + unsigned ExpansionColumn, unsigned CombinedColumnWidth) {
> + for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
> + ++NextESV) {
> + OS << "<div class='expansion-view'>\n";
> + // Render the child subview
> + if (Options.Debug)
> + errs() << "Expansion at line " << NextESV->getLine() << ", "
> + << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
> + << "\n";
> + NextESV->View->render(OS, false);
> + OS << "</div>\n";
> + }
> +
> + for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
> + OS << "<div class='expansion-view'>\n";
> + NextISV->View->renderTitle(OS, NextISV->FunctionName);
> + NextISV->View->render(OS, false);
> + OS << "</div>\n";
> + }
> + OS << "</td>\n";
> +}
> +
> void SourceCoverageView::render(raw_ostream &OS, bool WholeFile,
> unsigned IndentLevel) {
> // The width of the leading columns
> unsigned CombinedColumnWidth =
> (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
> (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
> - // The width of the line that is used to divide between the view and the
> - // subviews.
> - unsigned DividerWidth = CombinedColumnWidth + 4;
> -
> // We need the expansions and instantiations sorted so we can go through them
> // while we iterate lines.
> std::sort(ExpansionSubViews.begin(), ExpansionSubViews.end());
> @@ -193,8 +468,8 @@
> if (S->HasCount && S->IsRegionEntry)
> LineCount.addRegionStartCount(S->Count);
>
> - // Render the line prefix.
> - renderIndent(OS, IndentLevel);
> + startLine(OS, IndentLevel);
> +
> if (Options.ShowLineStats)
> renderLineCoverageColumn(OS, LineCount);
> if (Options.ShowLineNumbers)
> @@ -214,51 +489,30 @@
> if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers ||
> LineCount.hasMultipleRegions()) &&
> !LineSegments.empty()) {
> - renderIndent(OS, IndentLevel);
> - OS.indent(CombinedColumnWidth);
> - renderRegionMarkers(OS, LineSegments);
> - }
> + renderRegionMarkers(OS, LineSegments, IndentLevel, CombinedColumnWidth);
>
> - // Show the expansions and instantiations for this line.
> - unsigned NestedIndent = IndentLevel + 1;
> - bool RenderedSubView = false;
> - for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
> - ++NextESV) {
> - renderViewDivider(NestedIndent, DividerWidth, OS);
> - OS << "\n";
> - if (RenderedSubView) {
> - // Re-render the current line and highlight the expansion range for
> - // this subview.
> - ExpansionColumn = NextESV->getStartCol();
> - renderIndent(OS, IndentLevel);
> - OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
> - renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
> - ExpansionColumn);
> - renderViewDivider(NestedIndent, DividerWidth, OS);
> - OS << "\n";
> - }
> - // Render the child subview
> if (Options.Debug)
> - errs() << "Expansion at line " << NextESV->getLine() << ", "
> - << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
> - << "\n";
> - NextESV->View->render(OS, false, NestedIndent);
> - RenderedSubView = true;
> + for (const auto *S : LineSegments)
> + errs() << "Marker at " << S->Line << ":" << S->Col << " = "
> + << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n");
> }
> - for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
> - renderViewDivider(NestedIndent, DividerWidth, OS);
> - OS << "\n";
> - renderIndent(OS, NestedIndent);
> - OS << ' ';
> - Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName
> - << ":";
> - OS << "\n";
> - NextISV->View->render(OS, false, NestedIndent);
> - RenderedSubView = true;
> - }
> - if (RenderedSubView) {
> - renderViewDivider(NestedIndent, DividerWidth, OS);
> - OS << "\n";
> - }
> + renderSubviews(OS, LI, NextESV, EndESV, NextISV, EndISV,
> + WrappedSegment, LineSegments, IndentLevel,
> + ExpansionColumn, CombinedColumnWidth);
> +
> + endLine(OS);
> }
> }
> +
> +std::unique_ptr<SourceCoverageView>
> +SourceCoverageView::create(const MemoryBuffer &File,
> + const CoverageViewOptions &Options,
> + coverage::CoverageData &&CoverageInfo) {
> + if (Options.Format == CoverageViewOptions::HTML) {
> + return make_unique<SourceCoverageViewHTML>(File, Options,
> + std::move(CoverageInfo));
> + } else {
> + return make_unique<SourceCoverageViewConsole>(File, Options,
> + std::move(CoverageInfo));
Better to use a switch statement - it'll give a warning if we add a
value to the enum and forget to update here.
> + }
> +}
> Index: SourceCoverageView.h
> ===================================================================
> --- SourceCoverageView.h (revision 262364)
> +++ SourceCoverageView.h (working copy)
> @@ -17,6 +17,9 @@
> #include "CoverageViewOptions.h"
> #include "llvm/ProfileData/CoverageMapping.h"
> #include "llvm/Support/MemoryBuffer.h"
> +#include "llvm/Support/LineIterator.h"
> +#include "llvm/ADT/Optional.h"
> +#include "llvm/ADT/STLExtras.h"
> #include <vector>
>
> namespace llvm {
> @@ -76,7 +79,7 @@
> /// \brief A code coverage view of a specific source file.
> /// It can have embedded coverage views.
> class SourceCoverageView {
> -private:
> +protected:
> /// \brief Coverage information for a single line.
> struct LineCoverageInfo {
> uint64_t ExecutionCount;
> @@ -108,25 +111,39 @@
> std::vector<InstantiationView> InstantiationSubViews;
>
> /// \brief Render a source line with highlighting.
> - void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> - const coverage::CoverageSegment *WrappedSegment,
> - ArrayRef<const coverage::CoverageSegment *> Segments,
> - unsigned ExpansionCol);
> + virtual void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> + const coverage::CoverageSegment *WrappedSegment,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned ExpansionCol);
>
> - void renderIndent(raw_ostream &OS, unsigned Level);
> -
> - void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
> -
> /// \brief Render the line's execution count column.
> - void renderLineCoverageColumn(raw_ostream &OS, const LineCoverageInfo &Line);
> + virtual void renderLineCoverageColumn(raw_ostream &OS,
> + const LineCoverageInfo &Line) = 0;
>
> /// \brief Render the line number column.
> - void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo);
> + virtual void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) = 0;
>
> /// \brief Render all the region's execution counts on a line.
> - void
> - renderRegionMarkers(raw_ostream &OS,
> - ArrayRef<const coverage::CoverageSegment *> Segments);
> + virtual void renderRegionMarkers(raw_ostream &OS,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned IndentLevel,
> + unsigned CombinedColumnWidth);
> +
> + virtual void renderSubviews(llvm::raw_ostream &OS,
> + line_iterator &LI,
> + std::vector<ExpansionView>::iterator &NextESV,
> + std::vector<ExpansionView>::iterator &EndESV,
> + std::vector<InstantiationView>::iterator &NextISV,
> + std::vector<InstantiationView>::iterator &EndISV,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8>
> + LineSegments,
> + unsigned IndentLevel,
> + unsigned ExpansionColumn,
> + unsigned CombinedColumnWidth) = 0;
> +
> + virtual void startLine(raw_ostream &OS, unsigned IndentLevel) {}
> + virtual void endLine(raw_ostream &OS) {}
>
> static const unsigned LineCoverageColumnWidth = 7;
> static const unsigned LineNumberColumnWidth = 5;
> @@ -136,6 +153,11 @@
> const CoverageViewOptions &Options,
> coverage::CoverageData &&CoverageInfo)
> : File(File), Options(Options), CoverageInfo(std::move(CoverageInfo)) {}
> +
> +
> + static std::unique_ptr<SourceCoverageView> create(const MemoryBuffer &File,
> + const CoverageViewOptions &Options,
> + coverage::CoverageData &&CoverageInfo);
>
> const CoverageViewOptions &getOptions() const { return Options; }
>
> @@ -150,12 +172,139 @@
> std::unique_ptr<SourceCoverageView> View) {
> InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View));
> }
> +
> + virtual void output(llvm::raw_ostream &OS, std::string Text,
> + llvm::Optional<llvm::raw_ostream::Colors> Highlight,
> + const coverage::CoverageSegment *Segment = nullptr) = 0;
>
> /// \brief Print the code coverage information for a specific
> /// portion of a source file to the output stream.
> - void render(raw_ostream &OS, bool WholeFile, unsigned IndentLevel = 0);
> + virtual void render(raw_ostream &OS, bool WholeFile,
> + unsigned IndentLevel = 0);
> +
> + /// \brief Render the title for a given source region (function or file name)
> + virtual void renderTitle(raw_ostream &OS, StringRef Name) = 0;
> +
> + virtual ~SourceCoverageView() {}
> };
> +
> +class SourceCoverageViewHTML : public SourceCoverageView {
> +
> + /// \brief Render a source line with highlighting.
> + void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> + const coverage::CoverageSegment *WrappedSegment,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned ExpansionCol) override;
> +
> + void startLine(raw_ostream &OS, unsigned IndentLevel) override;
> + void endLine(raw_ostream &OS) override;
> +
> + /// \brief Render the line's execution count column.
> + void renderLineCoverageColumn(raw_ostream &OS,
> + const LineCoverageInfo &Line) override;
> +
> + void output(llvm::raw_ostream &OS, std::string Text,
> + llvm::Optional<llvm::raw_ostream::Colors> Highlight,
> + const coverage::CoverageSegment *Segment = nullptr) override;
> +
> + /// \brief Render the line number column.
> + void renderLineNumberColumn(raw_ostream &OS,
> + unsigned LineNo) override;
> +
> + /// \brief Render all the region's execution counts on a line.
> + void renderRegionMarkers(raw_ostream &OS,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned IndentLevel,
> + unsigned CombinedColumnWidth) override {
> + // empty implementation (region markers are tooltips).
> + }
> +
> + void renderSubviews(llvm::raw_ostream &OS,
> + line_iterator &LI,
> + std::vector<ExpansionView>::iterator &NextESV,
> + std::vector<ExpansionView>::iterator &EndESV,
> + std::vector<InstantiationView>::iterator &NextISV,
> + std::vector<InstantiationView>::iterator &EndISV,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8>
> + LineSegments,
> + unsigned IndentLevel,
> + unsigned ExpansionColumn,
> + unsigned CombinedColumnWidth) override;
> +
> + /// \brief Print the code coverage information for a specific
> + /// portion of a source file to the output stream.
> + void render(raw_ostream &OS, bool WholeFile,
> + unsigned IndentLevel = 0) override;
> +public:
> + SourceCoverageViewHTML(const MemoryBuffer &File,
> + const CoverageViewOptions &Options,
> + coverage::CoverageData &&CoverageInfo)
> + : SourceCoverageView(File, Options, std::move(CoverageInfo)) {}
> +
> + // \brief Render the standard boilerplate HTML header.
> + static void renderFileHeader(raw_ostream &OS);
> +
> + // \brief Render the standard boilerplate HTML footer.
> + static void renderFileFooter(raw_ostream &OS);
> +
> + /// \brief Render the title for a given source region (function or file name)
> + void renderTitle(raw_ostream &OS, StringRef Name) override;
> +};
> +
> +class SourceCoverageViewConsole : public SourceCoverageView {
> +
> + /// \brief Render a source line with highlighting.
> + void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
> + const coverage::CoverageSegment *WrappedSegment,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned ExpansionCol) override;
>
> + void startLine(raw_ostream &OS, unsigned IndentLevel) override;
> +
> + /// \brief Render the line's execution count column.
> + void renderLineCoverageColumn(raw_ostream &OS,
> + const LineCoverageInfo &Line) override;
> +
> + void output(llvm::raw_ostream &OS, std::string Text,
> + llvm::Optional<llvm::raw_ostream::Colors> Highlight,
> + const coverage::CoverageSegment *Segment = nullptr) override;
> +
> + /// \brief Render the line number column.
> + void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) override;
> +
> + /// \brief Render all the region's execution counts on a line.
> + void renderRegionMarkers(raw_ostream &OS,
> + ArrayRef<const coverage::CoverageSegment *> Segments,
> + unsigned IndentLevel,
> + unsigned CombinedColumnWidth) override;
> +
> + void renderSubviews(raw_ostream &OS,
> + line_iterator &LI,
> + std::vector<ExpansionView>::iterator &NextESV,
> + std::vector<ExpansionView>::iterator &EndESV,
> + std::vector<InstantiationView>::iterator &NextISV,
> + std::vector<InstantiationView>::iterator &EndISV,
> + const coverage::CoverageSegment *WrappedSegment,
> + SmallVector<const coverage::CoverageSegment *, 8>
> + LineSegments,
> + unsigned IndentLevel,
> + unsigned ExpansionColumn,
> + unsigned CombinedColumnWidth) override;
> +
> + void renderIndent(raw_ostream &OS, unsigned Level);
> +
> + void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
> +public:
> + SourceCoverageViewConsole(const MemoryBuffer &File,
> + const CoverageViewOptions &Options,
> + coverage::CoverageData &&CoverageInfo)
> + : SourceCoverageView(File, Options, std::move(CoverageInfo)) {}
> +
> + /// \brief Render the title for a given source region (function or file name)
> + void renderTitle(raw_ostream &OS, StringRef Name) override;
> +};
> +
> } // namespace llvm
>
> #endif // LLVM_COV_SOURCECOVERAGEVIEW_H
> Index: css.inc
> ===================================================================
> --- css.inc (revision 0)
> +++ css.inc (working copy)
> @@ -0,0 +1,108 @@
> +//===------------ css.inc - css file for HTML coverage reports ------------===//
> +//
> +// The LLVM Compiler Infrastructure
> +//
> +// This file is distributed under the University of Illinois Open Source
> +// License. See LICENSE.TXT for details.
> +//
> +//===----------------------------------------------------------------------===//
> +//
> +// This file has a raw string literal that is inserted as a CSS style into
> +// HTML documents generated by llvm-cov.
> +//
> +// This file includes the necessary styling for centered tables of code and
> +// tooltip generation.
> +//
> +//===----------------------------------------------------------------------===//
> +
> +StringRef css = R"(.red {
> + background-color: #FFD0D0;
> +}
> +.cyan {
> + background-color: cyan;
> +}
> +.black {
> + background-color: black;
> + color: white;
> +}
> +.green {
> + background-color: #98FFA6;
> + color: white;
> +}
> +.magenta {
> + background-color: #F998FF;
> + color: white;
> +}
> +tr:nth-child(odd) {
> + background-color: #fafafa;
> +}
> +tr:nth-child(even) {
> + background-color: #f0f0f0;
> +}
> +pre {
> + margin-top: 0px !important;
> + margin-bottom: 0px !important;
> +}
> +.function-title {
> + margin-top: 15px;
> + margin-bottom: 5px;
> +}
> +.centered {
> + margin-left: auto;
> + margin-right: auto;
> +}
> +.expansion-view {
> + border: 2px solid #d8d8d8;
> + padding-left: 10px;
> + padding-right: 10px;
> + padding-bottom: 10px;
> + margin-bottom: 5px;
> +}
> +table {
> + border: 2px solid #dbdbdb;
> + border-collapse: collapse;
> +}
> +.numeric {
> + text-align: right;
> +}
> +.tooltips {
> + position: relative;
> + display: inline;
> + background-color: #fffee6;
> + text-decoration: none;
> +}
> +.tooltips span {
> + position: absolute;
> + width:140px;
> + color: #FFFFFF;
> + background: #000000;
> + height: 30px;
> + line-height: 30px;
> + text-align: center;
> + visibility: hidden;
> + border-radius: 6px;
> +}
> +.tooltips span:after {
> + content: '';
> + position: absolute;
> + top: 100%;
> + left: 50%;
> + margin-left: -8px;
> + width: 0; height: 0;
> + border-top: 8px solid #000000;
> + border-right: 8px solid transparent;
> + border-left: 8px solid transparent;
> +}
> +:hover.tooltips span {
> + visibility: visible;
> + opacity: 0.8;
> + bottom: 30px;
> + left: 50%;
> + margin-left: -76px;
> + z-index: 999;
> +}
> +th, td {
> + vertical-align: top;
> + padding: 2px 5px;
> + border-collapse: collapse;
> +})";
More information about the llvm-commits
mailing list