1 //===- SourceCoverageViewText.cpp - A text-based code coverage view -------===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8 ///
9 /// \file This file implements the text-based coverage renderer.
10 ///
11 //===----------------------------------------------------------------------===//
12
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"
20 #include <optional>
21
22 using namespace llvm;
23
24 Expected<CoveragePrinter::OwnedStream>
createViewFile(StringRef Path,bool InToplevel)25 CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) {
26 return createOutputStream(Path, "txt", InToplevel);
27 }
28
closeViewFile(OwnedStream OS)29 void CoveragePrinterText::closeViewFile(OwnedStream OS) {
30 OS->operator<<('\n');
31 }
32
createIndexFile(ArrayRef<std::string> SourceFiles,const CoverageMapping & Coverage,const CoverageFiltersMatchAll & Filters)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())
38 return E;
39 auto OS = std::move(OSOrErr.get());
40 raw_ostream &OSRef = *OS.get();
41
42 CoverageReport Report(Opts, Coverage);
43 Report.renderFileReports(OSRef, SourceFiles, Filters);
44
45 Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n"
46 << Opts.getLLVMVersionString();
47
48 return Error::success();
49 }
50
51 struct CoveragePrinterTextDirectory::Reporter : public DirectoryCoverageReport {
52 CoveragePrinterTextDirectory &Printer;
53
ReporterCoveragePrinterTextDirectory::Reporter54 Reporter(CoveragePrinterTextDirectory &Printer,
55 const coverage::CoverageMapping &Coverage,
56 const CoverageFiltersMatchAll &Filters)
57 : DirectoryCoverageReport(Printer.Opts, Coverage, Filters),
58 Printer(Printer) {}
59
generateSubDirectoryReportCoveragePrinterTextDirectory::Reporter60 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");
66
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())
72 return E;
73 auto OS = std::move(OSOrErr.get());
74 raw_ostream &OSRef = *OS.get();
75
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));
81
82 CoverageReport Report(Options, Coverage);
83 Report.renderFileReports(OSRef, Reports, SubTotals, Filters.empty());
84
85 Options.colored_ostream(OSRef, raw_ostream::CYAN)
86 << "\n"
87 << Options.getLLVMVersionString();
88
89 return Error::success();
90 }
91 };
92
createIndexFile(ArrayRef<std::string> SourceFiles,const CoverageMapping & Coverage,const CoverageFiltersMatchAll & Filters)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);
98
99 Reporter Report(*this, Coverage, Filters);
100 auto TotalsOrErr = Report.prepareDirectoryReports(SourceFiles);
101 if (auto E = TotalsOrErr.takeError())
102 return E;
103 auto &LCPath = TotalsOrErr->Name;
104
105 auto TopIndexFilePath =
106 getOutputPath("index", "txt", /*InToplevel=*/true, /*Relative=*/false);
107 auto LCPIndexFilePath =
108 getOutputPath((LCPath + "index").str(), "txt", /*InToplevel=*/false,
109 /*Relative=*/false);
110 return errorCodeToError(
111 sys::fs::copy_file(LCPIndexFilePath, TopIndexFilePath));
112 }
113
114 namespace {
115
116 static const unsigned LineCoverageColumnWidth = 7;
117 static const unsigned LineNumberColumnWidth = 5;
118
119 /// Get the width of the leading columns.
getCombinedColumnWidth(const CoverageViewOptions & Opts)120 unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) {
121 return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
122 (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
123 }
124
125 /// The width of the line that is used to divide between the view and
126 /// the subviews.
getDividerWidth(const CoverageViewOptions & Opts)127 unsigned getDividerWidth(const CoverageViewOptions &Opts) {
128 return getCombinedColumnWidth(Opts) + 4;
129 }
130
131 } // anonymous namespace
132
renderViewHeader(raw_ostream &)133 void SourceCoverageViewText::renderViewHeader(raw_ostream &) {}
134
renderViewFooter(raw_ostream &)135 void SourceCoverageViewText::renderViewFooter(raw_ostream &) {}
136
renderSourceName(raw_ostream & OS,bool WholeFile)137 void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) {
138 getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName()
139 << ":\n";
140 }
141
renderLinePrefix(raw_ostream & OS,unsigned ViewDepth)142 void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS,
143 unsigned ViewDepth) {
144 for (unsigned I = 0; I < ViewDepth; ++I)
145 OS << " |";
146 }
147
renderLineSuffix(raw_ostream &,unsigned)148 void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {}
149
renderViewDivider(raw_ostream & OS,unsigned ViewDepth)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);
154 OS.indent(2);
155 unsigned Length = getDividerWidth(getOptions());
156 for (unsigned I = 0; I < Length; ++I)
157 OS << '-';
158 OS << '\n';
159 }
160
renderLine(raw_ostream & OS,LineRef L,const LineCoverageStats & LCS,unsigned ExpansionCol,unsigned ViewDepth)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();
169
170 std::optional<raw_ostream::Colors> Highlight;
171 SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
172
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;
177
178 // Output each segment of the line, possibly highlighted.
179 unsigned Col = 1;
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,
184 /*BG=*/true)
185 << Line.substr(Col - 1, End - Col);
186 if (getOptions().Debug && Highlight)
187 HighlightedRanges.push_back(std::make_pair(Col, End));
188 Col = End;
189 if ((!S->IsGapRegion || Highlight == raw_ostream::RED) && S->HasCount &&
190 S->Count == 0)
191 Highlight = raw_ostream::RED;
192 else if (Col == ExpansionCol)
193 Highlight = raw_ostream::CYAN;
194 else
195 Highlight = std::nullopt;
196 }
197
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);
202 OS << '\n';
203
204 if (getOptions().Debug) {
205 for (const auto &Range : HighlightedRanges)
206 errs() << "Highlighted line " << LineNumber << ", " << Range.first
207 << " -> " << Range.second << '\n';
208 if (Highlight)
209 errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";
210 }
211 }
212
renderLineCoverageColumn(raw_ostream & OS,const LineCoverageStats & Line)213 void SourceCoverageViewText::renderLineCoverageColumn(
214 raw_ostream &OS, const LineCoverageStats &Line) {
215 if (!Line.isMapped()) {
216 OS.indent(LineCoverageColumnWidth) << '|';
217 return;
218 }
219 std::string C = formatBinaryCount(Line.getExecutionCount());
220 OS.indent(LineCoverageColumnWidth - C.size());
221 colored_ostream(OS, raw_ostream::MAGENTA,
222 Line.hasMultipleRegions() && getOptions().Colors)
223 << C;
224 OS << '|';
225 }
226
renderLineNumberColumn(raw_ostream & OS,unsigned LineNo)227 void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS,
228 unsigned LineNo) {
229 SmallString<32> Buffer;
230 raw_svector_ostream BufferOS(Buffer);
231 BufferOS << LineNo;
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 << '|';
236 }
237
renderRegionMarkers(raw_ostream & OS,const LineCoverageStats & Line,unsigned ViewDepth)238 void SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS,
239 const LineCoverageStats &Line,
240 unsigned ViewDepth) {
241 renderLinePrefix(OS, ViewDepth);
242 OS.indent(getCombinedColumnWidth(getOptions()));
243
244 CoverageSegmentArray Segments = Line.getLineSegments();
245
246 // Just consider the segments which start *and* end on this line.
247 if (Segments.size() > 1)
248 Segments = Segments.drop_back();
249
250 unsigned PrevColumn = 1;
251 for (const auto *S : Segments) {
252 if (!S->IsRegionEntry)
253 continue;
254 if (S->Count == Line.getExecutionCount())
255 continue;
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();
262 OS << '^' << C;
263
264 if (getOptions().Debug)
265 errs() << "Marker at " << S->Line << ":" << S->Col << " = "
266 << formatBinaryCount(S->Count) << "\n";
267 }
268 OS << '\n';
269 }
270
renderExpansionSite(raw_ostream & OS,LineRef L,const LineCoverageStats & LCS,unsigned ExpansionCol,unsigned ViewDepth)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);
278 }
279
renderExpansionView(raw_ostream & OS,ExpansionView & ESV,unsigned ViewDepth)280 void SourceCoverageViewText::renderExpansionView(raw_ostream &OS,
281 ExpansionView &ESV,
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);
289 }
290
renderBranchView(raw_ostream & OS,BranchView & BRV,unsigned ViewDepth)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';
296
297 auto BranchCount = [&](StringRef Label, uint64_t Count, bool Folded,
298 double Total) {
299 if (Folded)
300 return std::string{"Folded"};
301
302 std::string Str;
303 raw_string_ostream OS(Str);
304
305 colored_ostream(OS, raw_ostream::RED, getOptions().Colors && !Count,
306 /*Bold=*/false, /*BG=*/true)
307 << Label;
308
309 if (getOptions().ShowBranchCounts)
310 OS << ": " << formatBinaryCount(Count);
311 else
312 OS << ": " << format("%0.2f", (Total != 0 ? 100.0 * Count / Total : 0.0))
313 << "%";
314
315 return Str;
316 };
317
318 for (const auto &R : BRV.Regions) {
319 // This can be `double` since it is only used as a denominator.
320 // FIXME: It is still inaccurate if Count is greater than (1LL << 53).
321 double Total =
322 static_cast<double>(R.ExecutionCount) + R.FalseExecutionCount;
323
324 renderLinePrefix(OS, ViewDepth);
325 OS << " Branch (" << R.LineStart << ":" << R.ColumnStart << "): [";
326
327 if (R.TrueFolded && R.FalseFolded) {
328 OS << "Folded - Ignored]\n";
329 continue;
330 }
331
332 OS << BranchCount("True", R.ExecutionCount, R.TrueFolded, Total) << ", "
333 << BranchCount("False", R.FalseExecutionCount, R.FalseFolded, Total)
334 << "]\n";
335 }
336 }
337
renderMCDCView(raw_ostream & OS,MCDCView & MRV,unsigned ViewDepth)338 void SourceCoverageViewText::renderMCDCView(raw_ostream &OS, MCDCView &MRV,
339 unsigned ViewDepth) {
340 for (auto &Record : MRV.Records) {
341 renderLinePrefix(OS, ViewDepth);
342 OS << "---> MC/DC Decision Region (";
343 // Display Line + Column information.
344 const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion();
345 OS << DecisionRegion.LineStart << ":";
346 OS << DecisionRegion.ColumnStart << ") to (";
347 OS << DecisionRegion.LineEnd << ":";
348 OS << DecisionRegion.ColumnEnd << ")\n";
349 renderLinePrefix(OS, ViewDepth);
350 OS << "\n";
351
352 // Display MC/DC Information.
353 renderLinePrefix(OS, ViewDepth);
354 OS << " Number of Conditions: " << Record.getNumConditions() << "\n";
355 for (unsigned i = 0; i < Record.getNumConditions(); i++) {
356 renderLinePrefix(OS, ViewDepth);
357 OS << " " << Record.getConditionHeaderString(i);
358 }
359 renderLinePrefix(OS, ViewDepth);
360 OS << "\n";
361 renderLinePrefix(OS, ViewDepth);
362 OS << " Executed MC/DC Test Vectors:\n";
363 renderLinePrefix(OS, ViewDepth);
364 OS << "\n";
365 renderLinePrefix(OS, ViewDepth);
366 OS << " ";
367 OS << Record.getTestVectorHeaderString();
368 for (unsigned i = 0; i < Record.getNumTestVectors(); i++) {
369 renderLinePrefix(OS, ViewDepth);
370 OS << Record.getTestVectorString(i);
371 }
372 renderLinePrefix(OS, ViewDepth);
373 OS << "\n";
374 for (unsigned i = 0; i < Record.getNumConditions(); i++) {
375 renderLinePrefix(OS, ViewDepth);
376 OS << Record.getConditionCoverageString(i);
377 }
378 renderLinePrefix(OS, ViewDepth);
379 OS << " MC/DC Coverage for Decision: ";
380 colored_ostream(OS, raw_ostream::RED,
381 getOptions().Colors && Record.getPercentCovered() < 100.0,
382 /*Bold=*/false, /*BG=*/true)
383 << format("%0.2f", Record.getPercentCovered()) << "%";
384 OS << "\n";
385 renderLinePrefix(OS, ViewDepth);
386 OS << "\n";
387 }
388 }
389
renderInstantiationView(raw_ostream & OS,InstantiationView & ISV,unsigned ViewDepth)390 void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS,
391 InstantiationView &ISV,
392 unsigned ViewDepth) {
393 renderLinePrefix(OS, ViewDepth);
394 OS << ' ';
395 if (!ISV.View)
396 getOptions().colored_ostream(OS, raw_ostream::RED)
397 << "Unexecuted instantiation: " << ISV.FunctionName << "\n";
398 else
399 ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,
400 /*ShowTitle=*/false, ViewDepth);
401 }
402
renderTitle(raw_ostream & OS,StringRef Title)403 void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) {
404 if (getOptions().hasProjectTitle())
405 getOptions().colored_ostream(OS, raw_ostream::CYAN)
406 << getOptions().ProjectTitle << "\n";
407
408 getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n";
409
410 if (getOptions().hasCreatedTime())
411 getOptions().colored_ostream(OS, raw_ostream::CYAN)
412 << getOptions().CreatedTimeStr << "\n";
413 }
414
renderTableHeader(raw_ostream &,unsigned)415 void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned) {}
416