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/FileSystem.h"
18 #include "llvm/Support/Format.h"
19 #include "llvm/Support/Path.h"
24 Expected
<CoveragePrinter::OwnedStream
>
25 CoveragePrinterText::createViewFile(StringRef Path
, bool InToplevel
) {
26 return createOutputStream(Path
, "txt", InToplevel
);
29 void CoveragePrinterText::closeViewFile(OwnedStream OS
) {
33 Error
CoveragePrinterText::createIndexFile(
34 ArrayRef
<std::string
> SourceFiles
, const CoverageMapping
&Coverage
,
35 const CoverageFiltersMatchAll
&Filters
) {
36 auto OSOrErr
= createOutputStream("index", "txt", /*InToplevel=*/true);
37 if (Error E
= OSOrErr
.takeError())
39 auto OS
= std::move(OSOrErr
.get());
40 raw_ostream
&OSRef
= *OS
.get();
42 CoverageReport
Report(Opts
, Coverage
);
43 Report
.renderFileReports(OSRef
, SourceFiles
, Filters
);
45 Opts
.colored_ostream(OSRef
, raw_ostream::CYAN
) << "\n"
46 << Opts
.getLLVMVersionString();
48 return Error::success();
51 struct CoveragePrinterTextDirectory::Reporter
: public DirectoryCoverageReport
{
52 CoveragePrinterTextDirectory
&Printer
;
54 Reporter(CoveragePrinterTextDirectory
&Printer
,
55 const coverage::CoverageMapping
&Coverage
,
56 const CoverageFiltersMatchAll
&Filters
)
57 : DirectoryCoverageReport(Printer
.Opts
, Coverage
, Filters
),
60 Error
generateSubDirectoryReport(SubFileReports
&&SubFiles
,
61 SubDirReports
&&SubDirs
,
62 FileCoverageSummary
&&SubTotals
) override
{
63 auto &LCPath
= SubTotals
.Name
;
64 assert(Options
.hasOutputDirectory() &&
65 "No output directory for index file");
67 SmallString
<128> OSPath
= LCPath
;
68 sys::path::append(OSPath
, "index");
69 auto OSOrErr
= Printer
.createOutputStream(OSPath
, "txt",
70 /*InToplevel=*/false);
71 if (auto E
= OSOrErr
.takeError())
73 auto OS
= std::move(OSOrErr
.get());
74 raw_ostream
&OSRef
= *OS
.get();
76 std::vector
<FileCoverageSummary
> Reports
;
77 for (auto &&SubDir
: SubDirs
)
78 Reports
.push_back(std::move(SubDir
.second
.first
));
79 for (auto &&SubFile
: SubFiles
)
80 Reports
.push_back(std::move(SubFile
.second
));
82 CoverageReport
Report(Options
, Coverage
);
83 Report
.renderFileReports(OSRef
, Reports
, SubTotals
, Filters
.empty());
85 Options
.colored_ostream(OSRef
, raw_ostream::CYAN
)
87 << Options
.getLLVMVersionString();
89 return Error::success();
93 Error
CoveragePrinterTextDirectory::createIndexFile(
94 ArrayRef
<std::string
> SourceFiles
, const CoverageMapping
&Coverage
,
95 const CoverageFiltersMatchAll
&Filters
) {
96 if (SourceFiles
.size() <= 1)
97 return CoveragePrinterText::createIndexFile(SourceFiles
, Coverage
, Filters
);
99 Reporter
Report(*this, Coverage
, Filters
);
100 auto TotalsOrErr
= Report
.prepareDirectoryReports(SourceFiles
);
101 if (auto E
= TotalsOrErr
.takeError())
103 auto &LCPath
= TotalsOrErr
->Name
;
105 auto TopIndexFilePath
=
106 getOutputPath("index", "txt", /*InToplevel=*/true, /*Relative=*/false);
107 auto LCPIndexFilePath
=
108 getOutputPath((LCPath
+ "index").str(), "txt", /*InToplevel=*/false,
110 return errorCodeToError(
111 sys::fs::copy_file(LCPIndexFilePath
, TopIndexFilePath
));
116 static const unsigned LineCoverageColumnWidth
= 7;
117 static const unsigned LineNumberColumnWidth
= 5;
119 /// Get the width of the leading columns.
120 unsigned getCombinedColumnWidth(const CoverageViewOptions
&Opts
) {
121 return (Opts
.ShowLineStats
? LineCoverageColumnWidth
+ 1 : 0) +
122 (Opts
.ShowLineNumbers
? LineNumberColumnWidth
+ 1 : 0);
125 /// The width of the line that is used to divide between the view and
127 unsigned getDividerWidth(const CoverageViewOptions
&Opts
) {
128 return getCombinedColumnWidth(Opts
) + 4;
131 } // anonymous namespace
133 void SourceCoverageViewText::renderViewHeader(raw_ostream
&) {}
135 void SourceCoverageViewText::renderViewFooter(raw_ostream
&) {}
137 void SourceCoverageViewText::renderSourceName(raw_ostream
&OS
, bool WholeFile
) {
138 getOptions().colored_ostream(OS
, raw_ostream::CYAN
) << getSourceName()
142 void SourceCoverageViewText::renderLinePrefix(raw_ostream
&OS
,
143 unsigned ViewDepth
) {
144 for (unsigned I
= 0; I
< ViewDepth
; ++I
)
148 void SourceCoverageViewText::renderLineSuffix(raw_ostream
&, unsigned) {}
150 void SourceCoverageViewText::renderViewDivider(raw_ostream
&OS
,
151 unsigned ViewDepth
) {
152 assert(ViewDepth
!= 0 && "Cannot render divider at top level");
153 renderLinePrefix(OS
, ViewDepth
- 1);
155 unsigned Length
= getDividerWidth(getOptions());
156 for (unsigned I
= 0; I
< Length
; ++I
)
161 void SourceCoverageViewText::renderLine(raw_ostream
&OS
, LineRef L
,
162 const LineCoverageStats
&LCS
,
163 unsigned ExpansionCol
,
164 unsigned ViewDepth
) {
165 StringRef Line
= L
.Line
;
166 unsigned LineNumber
= L
.LineNo
;
167 auto *WrappedSegment
= LCS
.getWrappedSegment();
168 CoverageSegmentArray Segments
= LCS
.getLineSegments();
170 std::optional
<raw_ostream::Colors
> Highlight
;
171 SmallVector
<std::pair
<unsigned, unsigned>, 2> HighlightedRanges
;
173 // The first segment overlaps from a previous line, so we treat it specially.
174 if (WrappedSegment
&& !WrappedSegment
->IsGapRegion
&&
175 WrappedSegment
->HasCount
&& WrappedSegment
->Count
== 0)
176 Highlight
= raw_ostream::RED
;
178 // Output each segment of the line, possibly highlighted.
180 for (const auto *S
: Segments
) {
181 unsigned End
= std::min(S
->Col
, static_cast<unsigned>(Line
.size()) + 1);
182 colored_ostream(OS
, Highlight
.value_or(raw_ostream::SAVEDCOLOR
),
183 getOptions().Colors
&& Highlight
, /*Bold=*/false,
185 << Line
.substr(Col
- 1, End
- Col
);
186 if (getOptions().Debug
&& Highlight
)
187 HighlightedRanges
.push_back(std::make_pair(Col
, End
));
189 if ((!S
->IsGapRegion
|| (Highlight
&& *Highlight
== raw_ostream::RED
)) &&
190 S
->HasCount
&& S
->Count
== 0)
191 Highlight
= raw_ostream::RED
;
192 else if (Col
== ExpansionCol
)
193 Highlight
= raw_ostream::CYAN
;
195 Highlight
= std::nullopt
;
198 // Show the rest of the line.
199 colored_ostream(OS
, Highlight
.value_or(raw_ostream::SAVEDCOLOR
),
200 getOptions().Colors
&& Highlight
, /*Bold=*/false, /*BG=*/true)
201 << Line
.substr(Col
- 1, Line
.size() - Col
+ 1);
204 if (getOptions().Debug
) {
205 for (const auto &Range
: HighlightedRanges
)
206 errs() << "Highlighted line " << LineNumber
<< ", " << Range
.first
207 << " -> " << Range
.second
<< '\n';
209 errs() << "Highlighted line " << LineNumber
<< ", " << Col
<< " -> ?\n";
213 void SourceCoverageViewText::renderLineCoverageColumn(
214 raw_ostream
&OS
, const LineCoverageStats
&Line
) {
215 if (!Line
.isMapped()) {
216 OS
.indent(LineCoverageColumnWidth
) << '|';
219 std::string C
= formatCount(Line
.getExecutionCount());
220 OS
.indent(LineCoverageColumnWidth
- C
.size());
221 colored_ostream(OS
, raw_ostream::MAGENTA
,
222 Line
.hasMultipleRegions() && getOptions().Colors
)
227 void SourceCoverageViewText::renderLineNumberColumn(raw_ostream
&OS
,
229 SmallString
<32> Buffer
;
230 raw_svector_ostream
BufferOS(Buffer
);
232 auto Str
= BufferOS
.str();
233 // Trim and align to the right.
234 Str
= Str
.substr(0, std::min(Str
.size(), (size_t)LineNumberColumnWidth
));
235 OS
.indent(LineNumberColumnWidth
- Str
.size()) << Str
<< '|';
238 void SourceCoverageViewText::renderRegionMarkers(raw_ostream
&OS
,
239 const LineCoverageStats
&Line
,
240 unsigned ViewDepth
) {
241 renderLinePrefix(OS
, ViewDepth
);
242 OS
.indent(getCombinedColumnWidth(getOptions()));
244 CoverageSegmentArray Segments
= Line
.getLineSegments();
246 // Just consider the segments which start *and* end on this line.
247 if (Segments
.size() > 1)
248 Segments
= Segments
.drop_back();
250 unsigned PrevColumn
= 1;
251 for (const auto *S
: Segments
) {
252 if (!S
->IsRegionEntry
)
254 if (S
->Count
== Line
.getExecutionCount())
256 // Skip to the new region.
257 if (S
->Col
> PrevColumn
)
258 OS
.indent(S
->Col
- PrevColumn
);
259 PrevColumn
= S
->Col
+ 1;
260 std::string C
= formatCount(S
->Count
);
261 PrevColumn
+= C
.size();
264 if (getOptions().Debug
)
265 errs() << "Marker at " << S
->Line
<< ":" << S
->Col
<< " = "
266 << formatCount(S
->Count
) << "\n";
271 void SourceCoverageViewText::renderExpansionSite(raw_ostream
&OS
, LineRef L
,
272 const LineCoverageStats
&LCS
,
273 unsigned ExpansionCol
,
274 unsigned ViewDepth
) {
275 renderLinePrefix(OS
, ViewDepth
);
276 OS
.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth
== 0 ? 0 : 1));
277 renderLine(OS
, L
, LCS
, ExpansionCol
, ViewDepth
);
280 void SourceCoverageViewText::renderExpansionView(raw_ostream
&OS
,
282 unsigned ViewDepth
) {
283 // Render the child subview.
284 if (getOptions().Debug
)
285 errs() << "Expansion at line " << ESV
.getLine() << ", " << ESV
.getStartCol()
286 << " -> " << ESV
.getEndCol() << '\n';
287 ESV
.View
->print(OS
, /*WholeFile=*/false, /*ShowSourceName=*/false,
288 /*ShowTitle=*/false, ViewDepth
+ 1);
291 void SourceCoverageViewText::renderBranchView(raw_ostream
&OS
, BranchView
&BRV
,
292 unsigned ViewDepth
) {
293 // Render the child subview.
294 if (getOptions().Debug
)
295 errs() << "Branch at line " << BRV
.getLine() << '\n';
297 for (const auto &R
: BRV
.Regions
) {
298 double TruePercent
= 0.0;
299 double FalsePercent
= 0.0;
300 // FIXME: It may overflow when the data is too large, but I have not
301 // encountered it in actual use, and not sure whether to use __uint128_t.
302 uint64_t Total
= R
.ExecutionCount
+ R
.FalseExecutionCount
;
304 if (!getOptions().ShowBranchCounts
&& Total
!= 0) {
305 TruePercent
= ((double)(R
.ExecutionCount
) / (double)Total
) * 100.0;
306 FalsePercent
= ((double)(R
.FalseExecutionCount
) / (double)Total
) * 100.0;
309 renderLinePrefix(OS
, ViewDepth
);
310 OS
<< " Branch (" << R
.LineStart
<< ":" << R
.ColumnStart
<< "): [";
312 if (R
.TrueFolded
&& R
.FalseFolded
) {
313 OS
<< "Folded - Ignored]\n";
320 colored_ostream(OS
, raw_ostream::RED
,
321 getOptions().Colors
&& !R
.ExecutionCount
,
322 /*Bold=*/false, /*BG=*/true)
325 if (getOptions().ShowBranchCounts
)
326 OS
<< ": " << formatCount(R
.ExecutionCount
) << ", ";
328 OS
<< ": " << format("%0.2f", TruePercent
) << "%, ";
334 colored_ostream(OS
, raw_ostream::RED
,
335 getOptions().Colors
&& !R
.FalseExecutionCount
,
336 /*Bold=*/false, /*BG=*/true)
339 if (getOptions().ShowBranchCounts
)
340 OS
<< ": " << formatCount(R
.FalseExecutionCount
) << "]\n";
342 OS
<< ": " << format("%0.2f", FalsePercent
) << "%]\n";
347 void SourceCoverageViewText::renderMCDCView(raw_ostream
&OS
, MCDCView
&MRV
,
348 unsigned ViewDepth
) {
349 for (auto &Record
: MRV
.Records
) {
350 renderLinePrefix(OS
, ViewDepth
);
351 OS
<< "---> MC/DC Decision Region (";
352 // Display Line + Column information.
353 const CounterMappingRegion
&DecisionRegion
= Record
.getDecisionRegion();
354 OS
<< DecisionRegion
.LineStart
<< ":";
355 OS
<< DecisionRegion
.ColumnStart
<< ") to (";
356 OS
<< DecisionRegion
.LineEnd
<< ":";
357 OS
<< DecisionRegion
.ColumnEnd
<< ")\n";
358 renderLinePrefix(OS
, ViewDepth
);
361 // Display MC/DC Information.
362 renderLinePrefix(OS
, ViewDepth
);
363 OS
<< " Number of Conditions: " << Record
.getNumConditions() << "\n";
364 for (unsigned i
= 0; i
< Record
.getNumConditions(); i
++) {
365 renderLinePrefix(OS
, ViewDepth
);
366 OS
<< " " << Record
.getConditionHeaderString(i
);
368 renderLinePrefix(OS
, ViewDepth
);
370 renderLinePrefix(OS
, ViewDepth
);
371 OS
<< " Executed MC/DC Test Vectors:\n";
372 renderLinePrefix(OS
, ViewDepth
);
374 renderLinePrefix(OS
, ViewDepth
);
376 OS
<< Record
.getTestVectorHeaderString();
377 for (unsigned i
= 0; i
< Record
.getNumTestVectors(); i
++) {
378 renderLinePrefix(OS
, ViewDepth
);
379 OS
<< Record
.getTestVectorString(i
);
381 renderLinePrefix(OS
, ViewDepth
);
383 for (unsigned i
= 0; i
< Record
.getNumConditions(); i
++) {
384 renderLinePrefix(OS
, ViewDepth
);
385 OS
<< Record
.getConditionCoverageString(i
);
387 renderLinePrefix(OS
, ViewDepth
);
388 OS
<< " MC/DC Coverage for Decision: ";
389 colored_ostream(OS
, raw_ostream::RED
,
390 getOptions().Colors
&& Record
.getPercentCovered() < 100.0,
391 /*Bold=*/false, /*BG=*/true)
392 << format("%0.2f", Record
.getPercentCovered()) << "%";
394 renderLinePrefix(OS
, ViewDepth
);
399 void SourceCoverageViewText::renderInstantiationView(raw_ostream
&OS
,
400 InstantiationView
&ISV
,
401 unsigned ViewDepth
) {
402 renderLinePrefix(OS
, ViewDepth
);
405 getOptions().colored_ostream(OS
, raw_ostream::RED
)
406 << "Unexecuted instantiation: " << ISV
.FunctionName
<< "\n";
408 ISV
.View
->print(OS
, /*WholeFile=*/false, /*ShowSourceName=*/true,
409 /*ShowTitle=*/false, ViewDepth
);
412 void SourceCoverageViewText::renderTitle(raw_ostream
&OS
, StringRef Title
) {
413 if (getOptions().hasProjectTitle())
414 getOptions().colored_ostream(OS
, raw_ostream::CYAN
)
415 << getOptions().ProjectTitle
<< "\n";
417 getOptions().colored_ostream(OS
, raw_ostream::CYAN
) << Title
<< "\n";
419 if (getOptions().hasCreatedTime())
420 getOptions().colored_ostream(OS
, raw_ostream::CYAN
)
421 << getOptions().CreatedTimeStr
<< "\n";
424 void SourceCoverageViewText::renderTableHeader(raw_ostream
&, unsigned) {}