1 //===- CoverageReport.cpp - Code coverage report -------------------------===//
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 // This class implements rendering of a code coverage report.
11 //===----------------------------------------------------------------------===//
13 #include "CoverageReport.h"
14 #include "RenderingSupport.h"
15 #include "llvm/ADT/DenseMap.h"
16 #include "llvm/Support/Format.h"
17 #include "llvm/Support/Path.h"
18 #include "llvm/Support/ThreadPool.h"
19 #include "llvm/Support/Threading.h"
26 /// Helper struct which prints trimmed and aligned columns.
28 enum TrimKind
{ NoTrim
, WidthTrim
, RightTrim
};
30 enum AlignmentKind
{ LeftAlignment
, RightAlignment
};
35 AlignmentKind Alignment
;
37 Column(StringRef Str
, unsigned Width
)
38 : Str(Str
), Width(Width
), Trim(WidthTrim
), Alignment(LeftAlignment
) {}
40 Column
&set(TrimKind Value
) {
45 Column
&set(AlignmentKind Value
) {
50 void render(raw_ostream
&OS
) const {
51 if (Str
.size() <= Width
) {
52 if (Alignment
== RightAlignment
) {
53 OS
.indent(Width
- Str
.size());
58 OS
.indent(Width
- Str
.size());
67 OS
<< Str
.substr(0, Width
);
70 OS
<< Str
.substr(0, Width
- 3) << "...";
76 raw_ostream
&operator<<(raw_ostream
&OS
, const Column
&Value
) {
81 Column
column(StringRef Str
, unsigned Width
) { return Column(Str
, Width
); }
84 Column
column(StringRef Str
, unsigned Width
, const T
&Value
) {
85 return Column(Str
, Width
).set(Value
);
88 // Specify the default column widths.
89 size_t FileReportColumns
[] = {25, 12, 18, 10, 12, 18, 10, 16,
90 16, 10, 12, 18, 10, 12, 18, 10};
91 size_t FunctionReportColumns
[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8};
93 /// Adjust column widths to fit long file paths and function names.
94 void adjustColumnWidths(ArrayRef
<StringRef
> Files
,
95 ArrayRef
<StringRef
> Functions
) {
96 for (StringRef Filename
: Files
)
97 FileReportColumns
[0] = std::max(FileReportColumns
[0], Filename
.size());
98 for (StringRef Funcname
: Functions
)
99 FunctionReportColumns
[0] =
100 std::max(FunctionReportColumns
[0], Funcname
.size());
103 /// Prints a horizontal divider long enough to cover the given column
105 void renderDivider(ArrayRef
<size_t> ColumnWidths
, raw_ostream
&OS
) {
106 size_t Length
= std::accumulate(ColumnWidths
.begin(), ColumnWidths
.end(), 0);
107 for (size_t I
= 0; I
< Length
; ++I
)
111 /// Return the color which correponds to the coverage percentage of a
113 template <typename T
>
114 raw_ostream::Colors
determineCoveragePercentageColor(const T
&Info
) {
115 if (Info
.isFullyCovered())
116 return raw_ostream::GREEN
;
117 return Info
.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW
121 /// Get the number of redundant path components in each path in \p Paths.
122 unsigned getNumRedundantPathComponents(ArrayRef
<std::string
> Paths
) {
123 // To start, set the number of redundant path components to the maximum
125 SmallVector
<StringRef
, 8> FirstPathComponents
{sys::path::begin(Paths
[0]),
126 sys::path::end(Paths
[0])};
127 unsigned NumRedundant
= FirstPathComponents
.size();
129 for (unsigned I
= 1, E
= Paths
.size(); NumRedundant
> 0 && I
< E
; ++I
) {
130 StringRef Path
= Paths
[I
];
131 for (const auto &Component
:
132 enumerate(make_range(sys::path::begin(Path
), sys::path::end(Path
)))) {
133 // Do not increase the number of redundant components: that would remove
134 // useful parts of already-visited paths.
135 if (Component
.index() >= NumRedundant
)
138 // Lower the number of redundant components when there's a mismatch
139 // between the first path, and the path under consideration.
140 if (FirstPathComponents
[Component
.index()] != Component
.value()) {
141 NumRedundant
= Component
.index();
150 /// Determine the length of the longest redundant prefix of the paths in
152 unsigned getRedundantPrefixLen(ArrayRef
<std::string
> Paths
) {
153 // If there's at most one path, no path components are redundant.
154 if (Paths
.size() <= 1)
157 unsigned PrefixLen
= 0;
158 unsigned NumRedundant
= getNumRedundantPathComponents(Paths
);
159 auto Component
= sys::path::begin(Paths
[0]);
160 for (unsigned I
= 0; I
< NumRedundant
; ++I
) {
161 auto LastComponent
= Component
;
163 PrefixLen
+= Component
- LastComponent
;
168 } // end anonymous namespace
172 void CoverageReport::render(const FileCoverageSummary
&File
,
173 raw_ostream
&OS
) const {
174 auto FileCoverageColor
=
175 determineCoveragePercentageColor(File
.RegionCoverage
);
176 auto FuncCoverageColor
=
177 determineCoveragePercentageColor(File
.FunctionCoverage
);
178 auto InstantiationCoverageColor
=
179 determineCoveragePercentageColor(File
.InstantiationCoverage
);
180 auto LineCoverageColor
= determineCoveragePercentageColor(File
.LineCoverage
);
181 SmallString
<256> FileName
= File
.Name
;
182 sys::path::remove_dots(FileName
, /*remove_dot_dot=*/true);
183 sys::path::native(FileName
);
184 OS
<< column(FileName
, FileReportColumns
[0], Column::NoTrim
);
186 if (Options
.ShowRegionSummary
) {
187 OS
<< format("%*u", FileReportColumns
[1],
188 (unsigned)File
.RegionCoverage
.getNumRegions());
189 Options
.colored_ostream(OS
, FileCoverageColor
)
190 << format("%*u", FileReportColumns
[2],
191 (unsigned)(File
.RegionCoverage
.getNumRegions() -
192 File
.RegionCoverage
.getCovered()));
193 if (File
.RegionCoverage
.getNumRegions())
194 Options
.colored_ostream(OS
, FileCoverageColor
)
195 << format("%*.2f", FileReportColumns
[3] - 1,
196 File
.RegionCoverage
.getPercentCovered())
199 OS
<< column("-", FileReportColumns
[3], Column::RightAlignment
);
202 OS
<< format("%*u", FileReportColumns
[4],
203 (unsigned)File
.FunctionCoverage
.getNumFunctions());
204 OS
<< format("%*u", FileReportColumns
[5],
205 (unsigned)(File
.FunctionCoverage
.getNumFunctions() -
206 File
.FunctionCoverage
.getExecuted()));
207 if (File
.FunctionCoverage
.getNumFunctions())
208 Options
.colored_ostream(OS
, FuncCoverageColor
)
209 << format("%*.2f", FileReportColumns
[6] - 1,
210 File
.FunctionCoverage
.getPercentCovered())
213 OS
<< column("-", FileReportColumns
[6], Column::RightAlignment
);
215 if (Options
.ShowInstantiationSummary
) {
216 OS
<< format("%*u", FileReportColumns
[7],
217 (unsigned)File
.InstantiationCoverage
.getNumFunctions());
218 OS
<< format("%*u", FileReportColumns
[8],
219 (unsigned)(File
.InstantiationCoverage
.getNumFunctions() -
220 File
.InstantiationCoverage
.getExecuted()));
221 if (File
.InstantiationCoverage
.getNumFunctions())
222 Options
.colored_ostream(OS
, InstantiationCoverageColor
)
223 << format("%*.2f", FileReportColumns
[9] - 1,
224 File
.InstantiationCoverage
.getPercentCovered())
227 OS
<< column("-", FileReportColumns
[9], Column::RightAlignment
);
230 OS
<< format("%*u", FileReportColumns
[10],
231 (unsigned)File
.LineCoverage
.getNumLines());
232 Options
.colored_ostream(OS
, LineCoverageColor
) << format(
233 "%*u", FileReportColumns
[11], (unsigned)(File
.LineCoverage
.getNumLines() -
234 File
.LineCoverage
.getCovered()));
235 if (File
.LineCoverage
.getNumLines())
236 Options
.colored_ostream(OS
, LineCoverageColor
)
237 << format("%*.2f", FileReportColumns
[12] - 1,
238 File
.LineCoverage
.getPercentCovered())
241 OS
<< column("-", FileReportColumns
[12], Column::RightAlignment
);
243 if (Options
.ShowBranchSummary
) {
244 OS
<< format("%*u", FileReportColumns
[13],
245 (unsigned)File
.BranchCoverage
.getNumBranches());
246 Options
.colored_ostream(OS
, LineCoverageColor
)
247 << format("%*u", FileReportColumns
[14],
248 (unsigned)(File
.BranchCoverage
.getNumBranches() -
249 File
.BranchCoverage
.getCovered()));
250 if (File
.BranchCoverage
.getNumBranches())
251 Options
.colored_ostream(OS
, LineCoverageColor
)
252 << format("%*.2f", FileReportColumns
[15] - 1,
253 File
.BranchCoverage
.getPercentCovered())
256 OS
<< column("-", FileReportColumns
[15], Column::RightAlignment
);
262 void CoverageReport::render(const FunctionCoverageSummary
&Function
,
263 const DemangleCache
&DC
,
264 raw_ostream
&OS
) const {
265 auto FuncCoverageColor
=
266 determineCoveragePercentageColor(Function
.RegionCoverage
);
267 auto LineCoverageColor
=
268 determineCoveragePercentageColor(Function
.LineCoverage
);
269 OS
<< column(DC
.demangle(Function
.Name
), FunctionReportColumns
[0],
271 << format("%*u", FunctionReportColumns
[1],
272 (unsigned)Function
.RegionCoverage
.getNumRegions());
273 Options
.colored_ostream(OS
, FuncCoverageColor
)
274 << format("%*u", FunctionReportColumns
[2],
275 (unsigned)(Function
.RegionCoverage
.getNumRegions() -
276 Function
.RegionCoverage
.getCovered()));
277 Options
.colored_ostream(
278 OS
, determineCoveragePercentageColor(Function
.RegionCoverage
))
279 << format("%*.2f", FunctionReportColumns
[3] - 1,
280 Function
.RegionCoverage
.getPercentCovered())
282 OS
<< format("%*u", FunctionReportColumns
[4],
283 (unsigned)Function
.LineCoverage
.getNumLines());
284 Options
.colored_ostream(OS
, LineCoverageColor
)
285 << format("%*u", FunctionReportColumns
[5],
286 (unsigned)(Function
.LineCoverage
.getNumLines() -
287 Function
.LineCoverage
.getCovered()));
288 Options
.colored_ostream(
289 OS
, determineCoveragePercentageColor(Function
.LineCoverage
))
290 << format("%*.2f", FunctionReportColumns
[6] - 1,
291 Function
.LineCoverage
.getPercentCovered())
293 if (Options
.ShowBranchSummary
) {
294 OS
<< format("%*u", FunctionReportColumns
[7],
295 (unsigned)Function
.BranchCoverage
.getNumBranches());
296 Options
.colored_ostream(OS
, LineCoverageColor
)
297 << format("%*u", FunctionReportColumns
[8],
298 (unsigned)(Function
.BranchCoverage
.getNumBranches() -
299 Function
.BranchCoverage
.getCovered()));
300 Options
.colored_ostream(
301 OS
, determineCoveragePercentageColor(Function
.BranchCoverage
))
302 << format("%*.2f", FunctionReportColumns
[9] - 1,
303 Function
.BranchCoverage
.getPercentCovered())
309 void CoverageReport::renderFunctionReports(ArrayRef
<std::string
> Files
,
310 const DemangleCache
&DC
,
313 for (StringRef Filename
: Files
) {
314 auto Functions
= Coverage
.getCoveredFunctions(Filename
);
321 std::vector
<StringRef
> Funcnames
;
322 for (const auto &F
: Functions
)
323 Funcnames
.emplace_back(DC
.demangle(F
.Name
));
324 adjustColumnWidths({}, Funcnames
);
326 OS
<< "File '" << Filename
<< "':\n";
327 OS
<< column("Name", FunctionReportColumns
[0])
328 << column("Regions", FunctionReportColumns
[1], Column::RightAlignment
)
329 << column("Miss", FunctionReportColumns
[2], Column::RightAlignment
)
330 << column("Cover", FunctionReportColumns
[3], Column::RightAlignment
)
331 << column("Lines", FunctionReportColumns
[4], Column::RightAlignment
)
332 << column("Miss", FunctionReportColumns
[5], Column::RightAlignment
)
333 << column("Cover", FunctionReportColumns
[6], Column::RightAlignment
);
334 if (Options
.ShowBranchSummary
)
335 OS
<< column("Branches", FunctionReportColumns
[7], Column::RightAlignment
)
336 << column("Miss", FunctionReportColumns
[8], Column::RightAlignment
)
337 << column("Cover", FunctionReportColumns
[9], Column::RightAlignment
);
339 renderDivider(FunctionReportColumns
, OS
);
341 FunctionCoverageSummary
Totals("TOTAL");
342 for (const auto &F
: Functions
) {
343 auto Function
= FunctionCoverageSummary::get(Coverage
, F
);
344 ++Totals
.ExecutionCount
;
345 Totals
.RegionCoverage
+= Function
.RegionCoverage
;
346 Totals
.LineCoverage
+= Function
.LineCoverage
;
347 Totals
.BranchCoverage
+= Function
.BranchCoverage
;
348 render(Function
, DC
, OS
);
350 if (Totals
.ExecutionCount
) {
351 renderDivider(FunctionReportColumns
, OS
);
353 render(Totals
, DC
, OS
);
358 void CoverageReport::prepareSingleFileReport(const StringRef Filename
,
359 const coverage::CoverageMapping
*Coverage
,
360 const CoverageViewOptions
&Options
, const unsigned LCP
,
361 FileCoverageSummary
*FileReport
, const CoverageFilter
*Filters
) {
362 for (const auto &Group
: Coverage
->getInstantiationGroups(Filename
)) {
363 std::vector
<FunctionCoverageSummary
> InstantiationSummaries
;
364 for (const coverage::FunctionRecord
*F
: Group
.getInstantiations()) {
365 if (!Filters
->matches(*Coverage
, *F
))
367 auto InstantiationSummary
= FunctionCoverageSummary::get(*Coverage
, *F
);
368 FileReport
->addInstantiation(InstantiationSummary
);
369 InstantiationSummaries
.push_back(InstantiationSummary
);
371 if (InstantiationSummaries
.empty())
375 FunctionCoverageSummary::get(Group
, InstantiationSummaries
);
378 outs() << "InstantiationGroup: " << GroupSummary
.Name
<< " with "
379 << "size = " << Group
.size() << "\n";
381 FileReport
->addFunction(GroupSummary
);
385 std::vector
<FileCoverageSummary
> CoverageReport::prepareFileReports(
386 const coverage::CoverageMapping
&Coverage
, FileCoverageSummary
&Totals
,
387 ArrayRef
<std::string
> Files
, const CoverageViewOptions
&Options
,
388 const CoverageFilter
&Filters
) {
389 unsigned LCP
= getRedundantPrefixLen(Files
);
391 ThreadPoolStrategy S
= hardware_concurrency(Options
.NumThreads
);
392 if (Options
.NumThreads
== 0) {
393 // If NumThreads is not specified, create one thread for each input, up to
394 // the number of hardware cores.
395 S
= heavyweight_hardware_concurrency(Files
.size());
400 std::vector
<FileCoverageSummary
> FileReports
;
401 FileReports
.reserve(Files
.size());
403 for (StringRef Filename
: Files
) {
404 FileReports
.emplace_back(Filename
.drop_front(LCP
));
405 Pool
.async(&CoverageReport::prepareSingleFileReport
, Filename
,
406 &Coverage
, Options
, LCP
, &FileReports
.back(), &Filters
);
410 for (const auto &FileReport
: FileReports
)
411 Totals
+= FileReport
;
416 void CoverageReport::renderFileReports(
417 raw_ostream
&OS
, const CoverageFilters
&IgnoreFilenameFilters
) const {
418 std::vector
<std::string
> UniqueSourceFiles
;
419 for (StringRef SF
: Coverage
.getUniqueSourceFiles()) {
420 // Apply ignore source files filters.
421 if (!IgnoreFilenameFilters
.matchesFilename(SF
))
422 UniqueSourceFiles
.emplace_back(SF
.str());
424 renderFileReports(OS
, UniqueSourceFiles
);
427 void CoverageReport::renderFileReports(
428 raw_ostream
&OS
, ArrayRef
<std::string
> Files
) const {
429 renderFileReports(OS
, Files
, CoverageFiltersMatchAll());
432 void CoverageReport::renderFileReports(
433 raw_ostream
&OS
, ArrayRef
<std::string
> Files
,
434 const CoverageFiltersMatchAll
&Filters
) const {
435 FileCoverageSummary
Totals("TOTAL");
437 prepareFileReports(Coverage
, Totals
, Files
, Options
, Filters
);
439 std::vector
<StringRef
> Filenames
;
440 Filenames
.reserve(FileReports
.size());
441 for (const FileCoverageSummary
&FCS
: FileReports
)
442 Filenames
.emplace_back(FCS
.Name
);
443 adjustColumnWidths(Filenames
, {});
445 OS
<< column("Filename", FileReportColumns
[0]);
446 if (Options
.ShowRegionSummary
)
447 OS
<< column("Regions", FileReportColumns
[1], Column::RightAlignment
)
448 << column("Missed Regions", FileReportColumns
[2], Column::RightAlignment
)
449 << column("Cover", FileReportColumns
[3], Column::RightAlignment
);
450 OS
<< column("Functions", FileReportColumns
[4], Column::RightAlignment
)
451 << column("Missed Functions", FileReportColumns
[5], Column::RightAlignment
)
452 << column("Executed", FileReportColumns
[6], Column::RightAlignment
);
453 if (Options
.ShowInstantiationSummary
)
454 OS
<< column("Instantiations", FileReportColumns
[7], Column::RightAlignment
)
455 << column("Missed Insts.", FileReportColumns
[8], Column::RightAlignment
)
456 << column("Executed", FileReportColumns
[9], Column::RightAlignment
);
457 OS
<< column("Lines", FileReportColumns
[10], Column::RightAlignment
)
458 << column("Missed Lines", FileReportColumns
[11], Column::RightAlignment
)
459 << column("Cover", FileReportColumns
[12], Column::RightAlignment
);
460 if (Options
.ShowBranchSummary
)
461 OS
<< column("Branches", FileReportColumns
[13], Column::RightAlignment
)
462 << column("Missed Branches", FileReportColumns
[14],
463 Column::RightAlignment
)
464 << column("Cover", FileReportColumns
[15], Column::RightAlignment
);
466 renderDivider(FileReportColumns
, OS
);
469 bool EmptyFiles
= false;
470 for (const FileCoverageSummary
&FCS
: FileReports
) {
471 if (FCS
.FunctionCoverage
.getNumFunctions())
477 if (EmptyFiles
&& Filters
.empty()) {
479 << "Files which contain no functions:\n";
481 for (const FileCoverageSummary
&FCS
: FileReports
)
482 if (!FCS
.FunctionCoverage
.getNumFunctions())
486 renderDivider(FileReportColumns
, OS
);
491 } // end namespace llvm