| //===- 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) {} |