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,
90 16, 16, 10, 12, 18, 10};
91 size_t FunctionReportColumns
[] = {25, 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_dots=*/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
);
245 void CoverageReport::render(const FunctionCoverageSummary
&Function
,
246 const DemangleCache
&DC
,
247 raw_ostream
&OS
) const {
248 auto FuncCoverageColor
=
249 determineCoveragePercentageColor(Function
.RegionCoverage
);
250 auto LineCoverageColor
=
251 determineCoveragePercentageColor(Function
.LineCoverage
);
252 OS
<< column(DC
.demangle(Function
.Name
), FunctionReportColumns
[0],
254 << format("%*u", FunctionReportColumns
[1],
255 (unsigned)Function
.RegionCoverage
.getNumRegions());
256 Options
.colored_ostream(OS
, FuncCoverageColor
)
257 << format("%*u", FunctionReportColumns
[2],
258 (unsigned)(Function
.RegionCoverage
.getNumRegions() -
259 Function
.RegionCoverage
.getCovered()));
260 Options
.colored_ostream(
261 OS
, determineCoveragePercentageColor(Function
.RegionCoverage
))
262 << format("%*.2f", FunctionReportColumns
[3] - 1,
263 Function
.RegionCoverage
.getPercentCovered())
265 OS
<< format("%*u", FunctionReportColumns
[4],
266 (unsigned)Function
.LineCoverage
.getNumLines());
267 Options
.colored_ostream(OS
, LineCoverageColor
)
268 << format("%*u", FunctionReportColumns
[5],
269 (unsigned)(Function
.LineCoverage
.getNumLines() -
270 Function
.LineCoverage
.getCovered()));
271 Options
.colored_ostream(
272 OS
, determineCoveragePercentageColor(Function
.LineCoverage
))
273 << format("%*.2f", FunctionReportColumns
[6] - 1,
274 Function
.LineCoverage
.getPercentCovered())
279 void CoverageReport::renderFunctionReports(ArrayRef
<std::string
> Files
,
280 const DemangleCache
&DC
,
283 for (StringRef Filename
: Files
) {
284 auto Functions
= Coverage
.getCoveredFunctions(Filename
);
291 std::vector
<StringRef
> Funcnames
;
292 for (const auto &F
: Functions
)
293 Funcnames
.emplace_back(DC
.demangle(F
.Name
));
294 adjustColumnWidths({}, Funcnames
);
296 OS
<< "File '" << Filename
<< "':\n";
297 OS
<< column("Name", FunctionReportColumns
[0])
298 << column("Regions", FunctionReportColumns
[1], Column::RightAlignment
)
299 << column("Miss", FunctionReportColumns
[2], Column::RightAlignment
)
300 << column("Cover", FunctionReportColumns
[3], Column::RightAlignment
)
301 << column("Lines", FunctionReportColumns
[4], Column::RightAlignment
)
302 << column("Miss", FunctionReportColumns
[5], Column::RightAlignment
)
303 << column("Cover", FunctionReportColumns
[6], Column::RightAlignment
);
305 renderDivider(FunctionReportColumns
, OS
);
307 FunctionCoverageSummary
Totals("TOTAL");
308 for (const auto &F
: Functions
) {
309 auto Function
= FunctionCoverageSummary::get(Coverage
, F
);
310 ++Totals
.ExecutionCount
;
311 Totals
.RegionCoverage
+= Function
.RegionCoverage
;
312 Totals
.LineCoverage
+= Function
.LineCoverage
;
313 render(Function
, DC
, OS
);
315 if (Totals
.ExecutionCount
) {
316 renderDivider(FunctionReportColumns
, OS
);
318 render(Totals
, DC
, OS
);
323 void CoverageReport::prepareSingleFileReport(const StringRef Filename
,
324 const coverage::CoverageMapping
*Coverage
,
325 const CoverageViewOptions
&Options
, const unsigned LCP
,
326 FileCoverageSummary
*FileReport
, const CoverageFilter
*Filters
) {
327 for (const auto &Group
: Coverage
->getInstantiationGroups(Filename
)) {
328 std::vector
<FunctionCoverageSummary
> InstantiationSummaries
;
329 for (const coverage::FunctionRecord
*F
: Group
.getInstantiations()) {
330 if (!Filters
->matches(*Coverage
, *F
))
332 auto InstantiationSummary
= FunctionCoverageSummary::get(*Coverage
, *F
);
333 FileReport
->addInstantiation(InstantiationSummary
);
334 InstantiationSummaries
.push_back(InstantiationSummary
);
336 if (InstantiationSummaries
.empty())
340 FunctionCoverageSummary::get(Group
, InstantiationSummaries
);
343 outs() << "InstantiationGroup: " << GroupSummary
.Name
<< " with "
344 << "size = " << Group
.size() << "\n";
346 FileReport
->addFunction(GroupSummary
);
350 std::vector
<FileCoverageSummary
> CoverageReport::prepareFileReports(
351 const coverage::CoverageMapping
&Coverage
, FileCoverageSummary
&Totals
,
352 ArrayRef
<std::string
> Files
, const CoverageViewOptions
&Options
,
353 const CoverageFilter
&Filters
) {
354 unsigned LCP
= getRedundantPrefixLen(Files
);
355 auto NumThreads
= Options
.NumThreads
;
357 // If NumThreads is not specified, auto-detect a good default.
360 std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(),
361 unsigned(Files
.size())));
363 ThreadPool
Pool(NumThreads
);
365 std::vector
<FileCoverageSummary
> FileReports
;
366 FileReports
.reserve(Files
.size());
368 for (StringRef Filename
: Files
) {
369 FileReports
.emplace_back(Filename
.drop_front(LCP
));
370 Pool
.async(&CoverageReport::prepareSingleFileReport
, Filename
,
371 &Coverage
, Options
, LCP
, &FileReports
.back(), &Filters
);
375 for (const auto &FileReport
: FileReports
)
376 Totals
+= FileReport
;
381 void CoverageReport::renderFileReports(
382 raw_ostream
&OS
, const CoverageFilters
&IgnoreFilenameFilters
) const {
383 std::vector
<std::string
> UniqueSourceFiles
;
384 for (StringRef SF
: Coverage
.getUniqueSourceFiles()) {
385 // Apply ignore source files filters.
386 if (!IgnoreFilenameFilters
.matchesFilename(SF
))
387 UniqueSourceFiles
.emplace_back(SF
.str());
389 renderFileReports(OS
, UniqueSourceFiles
);
392 void CoverageReport::renderFileReports(
393 raw_ostream
&OS
, ArrayRef
<std::string
> Files
) const {
394 renderFileReports(OS
, Files
, CoverageFiltersMatchAll());
397 void CoverageReport::renderFileReports(
398 raw_ostream
&OS
, ArrayRef
<std::string
> Files
,
399 const CoverageFiltersMatchAll
&Filters
) const {
400 FileCoverageSummary
Totals("TOTAL");
402 prepareFileReports(Coverage
, Totals
, Files
, Options
, Filters
);
404 std::vector
<StringRef
> Filenames
;
405 for (const FileCoverageSummary
&FCS
: FileReports
)
406 Filenames
.emplace_back(FCS
.Name
);
407 adjustColumnWidths(Filenames
, {});
409 OS
<< column("Filename", FileReportColumns
[0]);
410 if (Options
.ShowRegionSummary
)
411 OS
<< column("Regions", FileReportColumns
[1], Column::RightAlignment
)
412 << column("Missed Regions", FileReportColumns
[2], Column::RightAlignment
)
413 << column("Cover", FileReportColumns
[3], Column::RightAlignment
);
414 OS
<< column("Functions", FileReportColumns
[4], Column::RightAlignment
)
415 << column("Missed Functions", FileReportColumns
[5], Column::RightAlignment
)
416 << column("Executed", FileReportColumns
[6], Column::RightAlignment
);
417 if (Options
.ShowInstantiationSummary
)
418 OS
<< column("Instantiations", FileReportColumns
[7], Column::RightAlignment
)
419 << column("Missed Insts.", FileReportColumns
[8], Column::RightAlignment
)
420 << column("Executed", FileReportColumns
[9], Column::RightAlignment
);
421 OS
<< column("Lines", FileReportColumns
[10], Column::RightAlignment
)
422 << column("Missed Lines", FileReportColumns
[11], Column::RightAlignment
)
423 << column("Cover", FileReportColumns
[12], Column::RightAlignment
) << "\n";
424 renderDivider(FileReportColumns
, OS
);
427 bool EmptyFiles
= false;
428 for (const FileCoverageSummary
&FCS
: FileReports
) {
429 if (FCS
.FunctionCoverage
.getNumFunctions())
435 if (EmptyFiles
&& Filters
.empty()) {
437 << "Files which contain no functions:\n";
439 for (const FileCoverageSummary
&FCS
: FileReports
)
440 if (!FCS
.FunctionCoverage
.getNumFunctions())
444 renderDivider(FileReportColumns
, OS
);
449 } // end namespace llvm