1 //===- SourceCoverageViewText.cpp - A text-based code coverage view -------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 /// \file This file implements the text-based coverage renderer.
11 //===----------------------------------------------------------------------===//
13 #include "SourceCoverageViewText.h"
14 #include "CoverageReport.h"
15 #include "llvm/ADT/SmallString.h"
16 #include "llvm/ADT/StringExtras.h"
17 #include "llvm/Support/Format.h"
22 Expected
<CoveragePrinter::OwnedStream
>
23 CoveragePrinterText::createViewFile(StringRef Path
, bool InToplevel
) {
24 return createOutputStream(Path
, "txt", InToplevel
);
27 void CoveragePrinterText::closeViewFile(OwnedStream OS
) {
31 Error
CoveragePrinterText::createIndexFile(
32 ArrayRef
<std::string
> SourceFiles
, const CoverageMapping
&Coverage
,
33 const CoverageFiltersMatchAll
&Filters
) {
34 auto OSOrErr
= createOutputStream("index", "txt", /*InToplevel=*/true);
35 if (Error E
= OSOrErr
.takeError())
37 auto OS
= std::move(OSOrErr
.get());
38 raw_ostream
&OSRef
= *OS
.get();
40 CoverageReport
Report(Opts
, Coverage
);
41 Report
.renderFileReports(OSRef
, SourceFiles
, Filters
);
43 Opts
.colored_ostream(OSRef
, raw_ostream::CYAN
) << "\n"
44 << Opts
.getLLVMVersionString();
46 return Error::success();
51 static const unsigned LineCoverageColumnWidth
= 7;
52 static const unsigned LineNumberColumnWidth
= 5;
54 /// Get the width of the leading columns.
55 unsigned getCombinedColumnWidth(const CoverageViewOptions
&Opts
) {
56 return (Opts
.ShowLineStats
? LineCoverageColumnWidth
+ 1 : 0) +
57 (Opts
.ShowLineNumbers
? LineNumberColumnWidth
+ 1 : 0);
60 /// The width of the line that is used to divide between the view and
62 unsigned getDividerWidth(const CoverageViewOptions
&Opts
) {
63 return getCombinedColumnWidth(Opts
) + 4;
66 } // anonymous namespace
68 void SourceCoverageViewText::renderViewHeader(raw_ostream
&) {}
70 void SourceCoverageViewText::renderViewFooter(raw_ostream
&) {}
72 void SourceCoverageViewText::renderSourceName(raw_ostream
&OS
, bool WholeFile
) {
73 getOptions().colored_ostream(OS
, raw_ostream::CYAN
) << getSourceName()
77 void SourceCoverageViewText::renderLinePrefix(raw_ostream
&OS
,
79 for (unsigned I
= 0; I
< ViewDepth
; ++I
)
83 void SourceCoverageViewText::renderLineSuffix(raw_ostream
&, unsigned) {}
85 void SourceCoverageViewText::renderViewDivider(raw_ostream
&OS
,
87 assert(ViewDepth
!= 0 && "Cannot render divider at top level");
88 renderLinePrefix(OS
, ViewDepth
- 1);
90 unsigned Length
= getDividerWidth(getOptions());
91 for (unsigned I
= 0; I
< Length
; ++I
)
96 void SourceCoverageViewText::renderLine(raw_ostream
&OS
, LineRef L
,
97 const LineCoverageStats
&LCS
,
98 unsigned ExpansionCol
,
100 StringRef Line
= L
.Line
;
101 unsigned LineNumber
= L
.LineNo
;
102 auto *WrappedSegment
= LCS
.getWrappedSegment();
103 CoverageSegmentArray Segments
= LCS
.getLineSegments();
105 std::optional
<raw_ostream::Colors
> Highlight
;
106 SmallVector
<std::pair
<unsigned, unsigned>, 2> HighlightedRanges
;
108 // The first segment overlaps from a previous line, so we treat it specially.
109 if (WrappedSegment
&& !WrappedSegment
->IsGapRegion
&&
110 WrappedSegment
->HasCount
&& WrappedSegment
->Count
== 0)
111 Highlight
= raw_ostream::RED
;
113 // Output each segment of the line, possibly highlighted.
115 for (const auto *S
: Segments
) {
116 unsigned End
= std::min(S
->Col
, static_cast<unsigned>(Line
.size()) + 1);
117 colored_ostream(OS
, Highlight
? *Highlight
: raw_ostream::SAVEDCOLOR
,
118 getOptions().Colors
&& Highlight
, /*Bold=*/false,
120 << Line
.substr(Col
- 1, End
- Col
);
121 if (getOptions().Debug
&& Highlight
)
122 HighlightedRanges
.push_back(std::make_pair(Col
, End
));
124 if ((!S
->IsGapRegion
|| (Highlight
&& *Highlight
== raw_ostream::RED
)) &&
125 S
->HasCount
&& S
->Count
== 0)
126 Highlight
= raw_ostream::RED
;
127 else if (Col
== ExpansionCol
)
128 Highlight
= raw_ostream::CYAN
;
130 Highlight
= std::nullopt
;
133 // Show the rest of the line.
134 colored_ostream(OS
, Highlight
? *Highlight
: raw_ostream::SAVEDCOLOR
,
135 getOptions().Colors
&& Highlight
, /*Bold=*/false, /*BG=*/true)
136 << Line
.substr(Col
- 1, Line
.size() - Col
+ 1);
139 if (getOptions().Debug
) {
140 for (const auto &Range
: HighlightedRanges
)
141 errs() << "Highlighted line " << LineNumber
<< ", " << Range
.first
142 << " -> " << Range
.second
<< '\n';
144 errs() << "Highlighted line " << LineNumber
<< ", " << Col
<< " -> ?\n";
148 void SourceCoverageViewText::renderLineCoverageColumn(
149 raw_ostream
&OS
, const LineCoverageStats
&Line
) {
150 if (!Line
.isMapped()) {
151 OS
.indent(LineCoverageColumnWidth
) << '|';
154 std::string C
= formatCount(Line
.getExecutionCount());
155 OS
.indent(LineCoverageColumnWidth
- C
.size());
156 colored_ostream(OS
, raw_ostream::MAGENTA
,
157 Line
.hasMultipleRegions() && getOptions().Colors
)
162 void SourceCoverageViewText::renderLineNumberColumn(raw_ostream
&OS
,
164 SmallString
<32> Buffer
;
165 raw_svector_ostream
BufferOS(Buffer
);
167 auto Str
= BufferOS
.str();
168 // Trim and align to the right.
169 Str
= Str
.substr(0, std::min(Str
.size(), (size_t)LineNumberColumnWidth
));
170 OS
.indent(LineNumberColumnWidth
- Str
.size()) << Str
<< '|';
173 void SourceCoverageViewText::renderRegionMarkers(raw_ostream
&OS
,
174 const LineCoverageStats
&Line
,
175 unsigned ViewDepth
) {
176 renderLinePrefix(OS
, ViewDepth
);
177 OS
.indent(getCombinedColumnWidth(getOptions()));
179 CoverageSegmentArray Segments
= Line
.getLineSegments();
181 // Just consider the segments which start *and* end on this line.
182 if (Segments
.size() > 1)
183 Segments
= Segments
.drop_back();
185 unsigned PrevColumn
= 1;
186 for (const auto *S
: Segments
) {
187 if (!S
->IsRegionEntry
)
189 if (S
->Count
== Line
.getExecutionCount())
191 // Skip to the new region.
192 if (S
->Col
> PrevColumn
)
193 OS
.indent(S
->Col
- PrevColumn
);
194 PrevColumn
= S
->Col
+ 1;
195 std::string C
= formatCount(S
->Count
);
196 PrevColumn
+= C
.size();
199 if (getOptions().Debug
)
200 errs() << "Marker at " << S
->Line
<< ":" << S
->Col
<< " = "
201 << formatCount(S
->Count
) << "\n";
206 void SourceCoverageViewText::renderExpansionSite(raw_ostream
&OS
, LineRef L
,
207 const LineCoverageStats
&LCS
,
208 unsigned ExpansionCol
,
209 unsigned ViewDepth
) {
210 renderLinePrefix(OS
, ViewDepth
);
211 OS
.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth
== 0 ? 0 : 1));
212 renderLine(OS
, L
, LCS
, ExpansionCol
, ViewDepth
);
215 void SourceCoverageViewText::renderExpansionView(raw_ostream
&OS
,
217 unsigned ViewDepth
) {
218 // Render the child subview.
219 if (getOptions().Debug
)
220 errs() << "Expansion at line " << ESV
.getLine() << ", " << ESV
.getStartCol()
221 << " -> " << ESV
.getEndCol() << '\n';
222 ESV
.View
->print(OS
, /*WholeFile=*/false, /*ShowSourceName=*/false,
223 /*ShowTitle=*/false, ViewDepth
+ 1);
226 void SourceCoverageViewText::renderBranchView(raw_ostream
&OS
, BranchView
&BRV
,
227 unsigned ViewDepth
) {
228 // Render the child subview.
229 if (getOptions().Debug
)
230 errs() << "Branch at line " << BRV
.getLine() << '\n';
232 for (const auto &R
: BRV
.Regions
) {
233 double TruePercent
= 0.0;
234 double FalsePercent
= 0.0;
235 unsigned Total
= R
.ExecutionCount
+ R
.FalseExecutionCount
;
237 if (!getOptions().ShowBranchCounts
&& Total
!= 0) {
238 TruePercent
= ((double)(R
.ExecutionCount
) / (double)Total
) * 100.0;
239 FalsePercent
= ((double)(R
.FalseExecutionCount
) / (double)Total
) * 100.0;
242 renderLinePrefix(OS
, ViewDepth
);
243 OS
<< " Branch (" << R
.LineStart
<< ":" << R
.ColumnStart
<< "): [";
246 OS
<< "Folded - Ignored]\n";
250 colored_ostream(OS
, raw_ostream::RED
,
251 getOptions().Colors
&& !R
.ExecutionCount
,
252 /*Bold=*/false, /*BG=*/true)
255 if (getOptions().ShowBranchCounts
)
256 OS
<< ": " << formatCount(R
.ExecutionCount
) << ", ";
258 OS
<< ": " << format("%0.2f", TruePercent
) << "%, ";
260 colored_ostream(OS
, raw_ostream::RED
,
261 getOptions().Colors
&& !R
.FalseExecutionCount
,
262 /*Bold=*/false, /*BG=*/true)
265 if (getOptions().ShowBranchCounts
)
266 OS
<< ": " << formatCount(R
.FalseExecutionCount
);
268 OS
<< ": " << format("%0.2f", FalsePercent
) << "%";
273 void SourceCoverageViewText::renderInstantiationView(raw_ostream
&OS
,
274 InstantiationView
&ISV
,
275 unsigned ViewDepth
) {
276 renderLinePrefix(OS
, ViewDepth
);
279 getOptions().colored_ostream(OS
, raw_ostream::RED
)
280 << "Unexecuted instantiation: " << ISV
.FunctionName
<< "\n";
282 ISV
.View
->print(OS
, /*WholeFile=*/false, /*ShowSourceName=*/true,
283 /*ShowTitle=*/false, ViewDepth
);
286 void SourceCoverageViewText::renderTitle(raw_ostream
&OS
, StringRef Title
) {
287 if (getOptions().hasProjectTitle())
288 getOptions().colored_ostream(OS
, raw_ostream::CYAN
)
289 << getOptions().ProjectTitle
<< "\n";
291 getOptions().colored_ostream(OS
, raw_ostream::CYAN
) << Title
<< "\n";
293 if (getOptions().hasCreatedTime())
294 getOptions().colored_ostream(OS
, raw_ostream::CYAN
)
295 << getOptions().CreatedTimeStr
<< "\n";
298 void SourceCoverageViewText::renderTableHeader(raw_ostream
&, unsigned,