
to reflect the new license. We understand that people may be surprised that we're moving the header entirely to discuss the new license. We checked this carefully with the Foundation's lawyer and we believe this is the correct approach. Essentially, all code in the project is now made available by the LLVM project under our new license, so you will see that the license headers include that license only. Some of our contributors have contributed code under our old license, and accordingly, we have retained a copy of our old license notice in the top-level files in each project and repository. llvm-svn: 351636
252 lines
9.1 KiB
C++
252 lines
9.1 KiB
C++
//===- SourceCoverageViewText.cpp - A text-based code coverage view -------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file This file implements the text-based coverage renderer.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "CoverageReport.h"
|
|
#include "SourceCoverageViewText.h"
|
|
#include "llvm/ADT/Optional.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
|
|
using namespace llvm;
|
|
|
|
Expected<CoveragePrinter::OwnedStream>
|
|
CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) {
|
|
return createOutputStream(Path, "txt", InToplevel);
|
|
}
|
|
|
|
void CoveragePrinterText::closeViewFile(OwnedStream OS) {
|
|
OS->operator<<('\n');
|
|
}
|
|
|
|
Error CoveragePrinterText::createIndexFile(
|
|
ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
|
|
const CoverageFiltersMatchAll &Filters) {
|
|
auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true);
|
|
if (Error E = OSOrErr.takeError())
|
|
return E;
|
|
auto OS = std::move(OSOrErr.get());
|
|
raw_ostream &OSRef = *OS.get();
|
|
|
|
CoverageReport Report(Opts, Coverage);
|
|
Report.renderFileReports(OSRef, SourceFiles, Filters);
|
|
|
|
Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n"
|
|
<< Opts.getLLVMVersionString();
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
namespace {
|
|
|
|
static const unsigned LineCoverageColumnWidth = 7;
|
|
static const unsigned LineNumberColumnWidth = 5;
|
|
|
|
/// Get the width of the leading columns.
|
|
unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) {
|
|
return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
|
|
(Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
|
|
}
|
|
|
|
/// The width of the line that is used to divide between the view and
|
|
/// the subviews.
|
|
unsigned getDividerWidth(const CoverageViewOptions &Opts) {
|
|
return getCombinedColumnWidth(Opts) + 4;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
void SourceCoverageViewText::renderViewHeader(raw_ostream &) {}
|
|
|
|
void SourceCoverageViewText::renderViewFooter(raw_ostream &) {}
|
|
|
|
void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) {
|
|
getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName()
|
|
<< ":\n";
|
|
}
|
|
|
|
void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS,
|
|
unsigned ViewDepth) {
|
|
for (unsigned I = 0; I < ViewDepth; ++I)
|
|
OS << " |";
|
|
}
|
|
|
|
void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {}
|
|
|
|
void SourceCoverageViewText::renderViewDivider(raw_ostream &OS,
|
|
unsigned ViewDepth) {
|
|
assert(ViewDepth != 0 && "Cannot render divider at top level");
|
|
renderLinePrefix(OS, ViewDepth - 1);
|
|
OS.indent(2);
|
|
unsigned Length = getDividerWidth(getOptions());
|
|
for (unsigned I = 0; I < Length; ++I)
|
|
OS << '-';
|
|
OS << '\n';
|
|
}
|
|
|
|
void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L,
|
|
const LineCoverageStats &LCS,
|
|
unsigned ExpansionCol,
|
|
unsigned ViewDepth) {
|
|
StringRef Line = L.Line;
|
|
unsigned LineNumber = L.LineNo;
|
|
auto *WrappedSegment = LCS.getWrappedSegment();
|
|
CoverageSegmentArray Segments = LCS.getLineSegments();
|
|
|
|
Optional<raw_ostream::Colors> Highlight;
|
|
SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
|
|
|
|
// The first segment overlaps from a previous line, so we treat it specially.
|
|
if (WrappedSegment && !WrappedSegment->IsGapRegion &&
|
|
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,
|
|
getOptions().Colors && Highlight, /*Bold=*/false,
|
|
/*BG=*/true)
|
|
<< Line.substr(Col - 1, End - Col);
|
|
if (getOptions().Debug && Highlight)
|
|
HighlightedRanges.push_back(std::make_pair(Col, End));
|
|
Col = End;
|
|
if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) &&
|
|
S->HasCount && S->Count == 0)
|
|
Highlight = raw_ostream::RED;
|
|
else if (Col == ExpansionCol)
|
|
Highlight = raw_ostream::CYAN;
|
|
else
|
|
Highlight = None;
|
|
}
|
|
|
|
// Show the rest of the line.
|
|
colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
|
|
getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true)
|
|
<< Line.substr(Col - 1, Line.size() - Col + 1);
|
|
OS << '\n';
|
|
|
|
if (getOptions().Debug) {
|
|
for (const auto &Range : HighlightedRanges)
|
|
errs() << "Highlighted line " << LineNumber << ", " << Range.first
|
|
<< " -> " << Range.second << '\n';
|
|
if (Highlight)
|
|
errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";
|
|
}
|
|
}
|
|
|
|
void SourceCoverageViewText::renderLineCoverageColumn(
|
|
raw_ostream &OS, const LineCoverageStats &Line) {
|
|
if (!Line.isMapped()) {
|
|
OS.indent(LineCoverageColumnWidth) << '|';
|
|
return;
|
|
}
|
|
std::string C = formatCount(Line.getExecutionCount());
|
|
OS.indent(LineCoverageColumnWidth - C.size());
|
|
colored_ostream(OS, raw_ostream::MAGENTA,
|
|
Line.hasMultipleRegions() && getOptions().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,
|
|
const LineCoverageStats &Line,
|
|
unsigned ViewDepth) {
|
|
renderLinePrefix(OS, ViewDepth);
|
|
OS.indent(getCombinedColumnWidth(getOptions()));
|
|
|
|
CoverageSegmentArray Segments = Line.getLineSegments();
|
|
|
|
// Just consider the segments which start *and* end on this line.
|
|
if (Segments.size() > 1)
|
|
Segments = Segments.drop_back();
|
|
|
|
unsigned PrevColumn = 1;
|
|
for (const auto *S : Segments) {
|
|
if (!S->IsRegionEntry)
|
|
continue;
|
|
if (S->Count == Line.getExecutionCount())
|
|
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;
|
|
|
|
if (getOptions().Debug)
|
|
errs() << "Marker at " << S->Line << ":" << S->Col << " = "
|
|
<< formatCount(S->Count) << "\n";
|
|
}
|
|
OS << '\n';
|
|
}
|
|
|
|
void SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L,
|
|
const LineCoverageStats &LCS,
|
|
unsigned ExpansionCol,
|
|
unsigned ViewDepth) {
|
|
renderLinePrefix(OS, ViewDepth);
|
|
OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1));
|
|
renderLine(OS, L, LCS, ExpansionCol, ViewDepth);
|
|
}
|
|
|
|
void SourceCoverageViewText::renderExpansionView(raw_ostream &OS,
|
|
ExpansionView &ESV,
|
|
unsigned ViewDepth) {
|
|
// Render the child subview.
|
|
if (getOptions().Debug)
|
|
errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol()
|
|
<< " -> " << ESV.getEndCol() << '\n';
|
|
ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
|
|
/*ShowTitle=*/false, ViewDepth + 1);
|
|
}
|
|
|
|
void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS,
|
|
InstantiationView &ISV,
|
|
unsigned ViewDepth) {
|
|
renderLinePrefix(OS, ViewDepth);
|
|
OS << ' ';
|
|
if (!ISV.View)
|
|
getOptions().colored_ostream(OS, raw_ostream::RED)
|
|
<< "Unexecuted instantiation: " << ISV.FunctionName << "\n";
|
|
else
|
|
ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,
|
|
/*ShowTitle=*/false, ViewDepth);
|
|
}
|
|
|
|
void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) {
|
|
if (getOptions().hasProjectTitle())
|
|
getOptions().colored_ostream(OS, raw_ostream::CYAN)
|
|
<< getOptions().ProjectTitle << "\n";
|
|
|
|
getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n";
|
|
|
|
if (getOptions().hasCreatedTime())
|
|
getOptions().colored_ostream(OS, raw_ostream::CYAN)
|
|
<< getOptions().CreatedTimeStr << "\n";
|
|
}
|
|
|
|
void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned,
|
|
unsigned) {}
|