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/SmallString.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, 16, 10,
90 12, 18, 10, 12, 18, 10, 20, 21, 10};
91 size_t FunctionReportColumns
[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8, 20, 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(raw_ostream
&OS
, const CoverageViewOptions
&Options
, bool isFileReport
) {
108 Length
= std::accumulate(std::begin(FileReportColumns
), std::end(FileReportColumns
), 0);
109 if (!Options
.ShowRegionSummary
)
110 Length
-= (FileReportColumns
[1] + FileReportColumns
[2] + FileReportColumns
[3]);
111 if (!Options
.ShowInstantiationSummary
)
112 Length
-= (FileReportColumns
[7] + FileReportColumns
[8] + FileReportColumns
[9]);
113 if (!Options
.ShowBranchSummary
)
114 Length
-= (FileReportColumns
[13] + FileReportColumns
[14] + FileReportColumns
[15]);
115 if (!Options
.ShowMCDCSummary
)
116 Length
-= (FileReportColumns
[16] + FileReportColumns
[17] + FileReportColumns
[18]);
118 Length
= std::accumulate(std::begin(FunctionReportColumns
), std::end(FunctionReportColumns
), 0);
119 if (!Options
.ShowBranchSummary
)
120 Length
-= (FunctionReportColumns
[7] + FunctionReportColumns
[8] + FunctionReportColumns
[9]);
121 if (!Options
.ShowMCDCSummary
)
122 Length
-= (FunctionReportColumns
[10] + FunctionReportColumns
[11] + FunctionReportColumns
[12]);
124 for (size_t I
= 0; I
< Length
; ++I
)
128 /// Return the color which correponds to the coverage percentage of a
130 template <typename T
>
131 raw_ostream::Colors
determineCoveragePercentageColor(const T
&Info
) {
132 if (Info
.isFullyCovered())
133 return raw_ostream::GREEN
;
134 return Info
.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW
138 /// Get the number of redundant path components in each path in \p Paths.
139 unsigned getNumRedundantPathComponents(ArrayRef
<std::string
> Paths
) {
140 // To start, set the number of redundant path components to the maximum
142 SmallVector
<StringRef
, 8> FirstPathComponents
{sys::path::begin(Paths
[0]),
143 sys::path::end(Paths
[0])};
144 unsigned NumRedundant
= FirstPathComponents
.size();
146 for (unsigned I
= 1, E
= Paths
.size(); NumRedundant
> 0 && I
< E
; ++I
) {
147 StringRef Path
= Paths
[I
];
148 for (const auto &Component
:
149 enumerate(make_range(sys::path::begin(Path
), sys::path::end(Path
)))) {
150 // Do not increase the number of redundant components: that would remove
151 // useful parts of already-visited paths.
152 if (Component
.index() >= NumRedundant
)
155 // Lower the number of redundant components when there's a mismatch
156 // between the first path, and the path under consideration.
157 if (FirstPathComponents
[Component
.index()] != Component
.value()) {
158 NumRedundant
= Component
.index();
167 /// Determine the length of the longest redundant prefix of the paths in
169 unsigned getRedundantPrefixLen(ArrayRef
<std::string
> Paths
) {
170 // If there's at most one path, no path components are redundant.
171 if (Paths
.size() <= 1)
174 unsigned PrefixLen
= 0;
175 unsigned NumRedundant
= getNumRedundantPathComponents(Paths
);
176 auto Component
= sys::path::begin(Paths
[0]);
177 for (unsigned I
= 0; I
< NumRedundant
; ++I
) {
178 auto LastComponent
= Component
;
180 PrefixLen
+= Component
- LastComponent
;
185 /// Determine the length of the longest redundant prefix of the substrs starts
186 /// from \p LCP in \p Paths. \p Paths can't be empty. If there's only one
187 /// element in \p Paths, the length of the substr is returned. Note this is
188 /// differnet from the behavior of the function above.
189 unsigned getRedundantPrefixLen(ArrayRef
<StringRef
> Paths
, unsigned LCP
) {
190 assert(!Paths
.empty() && "Paths must have at least one element");
192 auto Iter
= Paths
.begin();
193 auto IterE
= Paths
.end();
194 auto Prefix
= Iter
->substr(LCP
);
195 while (++Iter
!= IterE
) {
196 auto Other
= Iter
->substr(LCP
);
197 auto Len
= std::min(Prefix
.size(), Other
.size());
198 for (std::size_t I
= 0; I
< Len
; ++I
) {
199 if (Prefix
[I
] != Other
[I
]) {
200 Prefix
= Prefix
.substr(0, I
);
206 for (auto I
= Prefix
.size(); --I
!= SIZE_MAX
;) {
207 if (Prefix
[I
] == '/' || Prefix
[I
] == '\\')
211 return Prefix
.size();
214 } // end anonymous namespace
218 void CoverageReport::render(const FileCoverageSummary
&File
,
219 raw_ostream
&OS
) const {
220 auto FileCoverageColor
=
221 determineCoveragePercentageColor(File
.RegionCoverage
);
222 auto FuncCoverageColor
=
223 determineCoveragePercentageColor(File
.FunctionCoverage
);
224 auto InstantiationCoverageColor
=
225 determineCoveragePercentageColor(File
.InstantiationCoverage
);
226 auto LineCoverageColor
= determineCoveragePercentageColor(File
.LineCoverage
);
227 SmallString
<256> FileName
= File
.Name
;
228 sys::path::native(FileName
);
230 // remove_dots will remove trailing slash, so we need to check before it.
231 auto IsDir
= FileName
.ends_with(sys::path::get_separator());
232 sys::path::remove_dots(FileName
, /*remove_dot_dot=*/true);
234 FileName
+= sys::path::get_separator();
236 OS
<< column(FileName
, FileReportColumns
[0], Column::NoTrim
);
238 if (Options
.ShowRegionSummary
) {
239 OS
<< format("%*u", FileReportColumns
[1],
240 (unsigned)File
.RegionCoverage
.getNumRegions());
241 Options
.colored_ostream(OS
, FileCoverageColor
)
242 << format("%*u", FileReportColumns
[2],
243 (unsigned)(File
.RegionCoverage
.getNumRegions() -
244 File
.RegionCoverage
.getCovered()));
245 if (File
.RegionCoverage
.getNumRegions())
246 Options
.colored_ostream(OS
, FileCoverageColor
)
247 << format("%*.2f", FileReportColumns
[3] - 1,
248 File
.RegionCoverage
.getPercentCovered())
251 OS
<< column("-", FileReportColumns
[3], Column::RightAlignment
);
254 OS
<< format("%*u", FileReportColumns
[4],
255 (unsigned)File
.FunctionCoverage
.getNumFunctions());
256 OS
<< format("%*u", FileReportColumns
[5],
257 (unsigned)(File
.FunctionCoverage
.getNumFunctions() -
258 File
.FunctionCoverage
.getExecuted()));
259 if (File
.FunctionCoverage
.getNumFunctions())
260 Options
.colored_ostream(OS
, FuncCoverageColor
)
261 << format("%*.2f", FileReportColumns
[6] - 1,
262 File
.FunctionCoverage
.getPercentCovered())
265 OS
<< column("-", FileReportColumns
[6], Column::RightAlignment
);
267 if (Options
.ShowInstantiationSummary
) {
268 OS
<< format("%*u", FileReportColumns
[7],
269 (unsigned)File
.InstantiationCoverage
.getNumFunctions());
270 OS
<< format("%*u", FileReportColumns
[8],
271 (unsigned)(File
.InstantiationCoverage
.getNumFunctions() -
272 File
.InstantiationCoverage
.getExecuted()));
273 if (File
.InstantiationCoverage
.getNumFunctions())
274 Options
.colored_ostream(OS
, InstantiationCoverageColor
)
275 << format("%*.2f", FileReportColumns
[9] - 1,
276 File
.InstantiationCoverage
.getPercentCovered())
279 OS
<< column("-", FileReportColumns
[9], Column::RightAlignment
);
282 OS
<< format("%*u", FileReportColumns
[10],
283 (unsigned)File
.LineCoverage
.getNumLines());
284 Options
.colored_ostream(OS
, LineCoverageColor
) << format(
285 "%*u", FileReportColumns
[11], (unsigned)(File
.LineCoverage
.getNumLines() -
286 File
.LineCoverage
.getCovered()));
287 if (File
.LineCoverage
.getNumLines())
288 Options
.colored_ostream(OS
, LineCoverageColor
)
289 << format("%*.2f", FileReportColumns
[12] - 1,
290 File
.LineCoverage
.getPercentCovered())
293 OS
<< column("-", FileReportColumns
[12], Column::RightAlignment
);
295 if (Options
.ShowBranchSummary
) {
296 OS
<< format("%*u", FileReportColumns
[13],
297 (unsigned)File
.BranchCoverage
.getNumBranches());
298 Options
.colored_ostream(OS
, LineCoverageColor
)
299 << format("%*u", FileReportColumns
[14],
300 (unsigned)(File
.BranchCoverage
.getNumBranches() -
301 File
.BranchCoverage
.getCovered()));
302 if (File
.BranchCoverage
.getNumBranches())
303 Options
.colored_ostream(OS
, LineCoverageColor
)
304 << format("%*.2f", FileReportColumns
[15] - 1,
305 File
.BranchCoverage
.getPercentCovered())
308 OS
<< column("-", FileReportColumns
[15], Column::RightAlignment
);
311 if (Options
.ShowMCDCSummary
) {
312 OS
<< format("%*u", FileReportColumns
[16],
313 (unsigned)File
.MCDCCoverage
.getNumPairs());
314 Options
.colored_ostream(OS
, LineCoverageColor
)
315 << format("%*u", FileReportColumns
[17],
316 (unsigned)(File
.MCDCCoverage
.getNumPairs() -
317 File
.MCDCCoverage
.getCoveredPairs()));
318 if (File
.MCDCCoverage
.getNumPairs())
319 Options
.colored_ostream(OS
, LineCoverageColor
)
320 << format("%*.2f", FileReportColumns
[18] - 1,
321 File
.MCDCCoverage
.getPercentCovered())
324 OS
<< column("-", FileReportColumns
[18], Column::RightAlignment
);
330 void CoverageReport::render(const FunctionCoverageSummary
&Function
,
331 const DemangleCache
&DC
,
332 raw_ostream
&OS
) const {
333 auto FuncCoverageColor
=
334 determineCoveragePercentageColor(Function
.RegionCoverage
);
335 auto LineCoverageColor
=
336 determineCoveragePercentageColor(Function
.LineCoverage
);
337 OS
<< column(DC
.demangle(Function
.Name
), FunctionReportColumns
[0],
339 << format("%*u", FunctionReportColumns
[1],
340 (unsigned)Function
.RegionCoverage
.getNumRegions());
341 Options
.colored_ostream(OS
, FuncCoverageColor
)
342 << format("%*u", FunctionReportColumns
[2],
343 (unsigned)(Function
.RegionCoverage
.getNumRegions() -
344 Function
.RegionCoverage
.getCovered()));
345 Options
.colored_ostream(
346 OS
, determineCoveragePercentageColor(Function
.RegionCoverage
))
347 << format("%*.2f", FunctionReportColumns
[3] - 1,
348 Function
.RegionCoverage
.getPercentCovered())
350 OS
<< format("%*u", FunctionReportColumns
[4],
351 (unsigned)Function
.LineCoverage
.getNumLines());
352 Options
.colored_ostream(OS
, LineCoverageColor
)
353 << format("%*u", FunctionReportColumns
[5],
354 (unsigned)(Function
.LineCoverage
.getNumLines() -
355 Function
.LineCoverage
.getCovered()));
356 Options
.colored_ostream(
357 OS
, determineCoveragePercentageColor(Function
.LineCoverage
))
358 << format("%*.2f", FunctionReportColumns
[6] - 1,
359 Function
.LineCoverage
.getPercentCovered())
361 if (Options
.ShowBranchSummary
) {
362 OS
<< format("%*u", FunctionReportColumns
[7],
363 (unsigned)Function
.BranchCoverage
.getNumBranches());
364 Options
.colored_ostream(OS
, LineCoverageColor
)
365 << format("%*u", FunctionReportColumns
[8],
366 (unsigned)(Function
.BranchCoverage
.getNumBranches() -
367 Function
.BranchCoverage
.getCovered()));
368 Options
.colored_ostream(
369 OS
, determineCoveragePercentageColor(Function
.BranchCoverage
))
370 << format("%*.2f", FunctionReportColumns
[9] - 1,
371 Function
.BranchCoverage
.getPercentCovered())
374 if (Options
.ShowMCDCSummary
) {
375 OS
<< format("%*u", FunctionReportColumns
[10],
376 (unsigned)Function
.MCDCCoverage
.getNumPairs());
377 Options
.colored_ostream(OS
, LineCoverageColor
)
378 << format("%*u", FunctionReportColumns
[11],
379 (unsigned)(Function
.MCDCCoverage
.getNumPairs() -
380 Function
.MCDCCoverage
.getCoveredPairs()));
381 Options
.colored_ostream(
382 OS
, determineCoveragePercentageColor(Function
.MCDCCoverage
))
383 << format("%*.2f", FunctionReportColumns
[12] - 1,
384 Function
.MCDCCoverage
.getPercentCovered())
390 void CoverageReport::renderFunctionReports(ArrayRef
<std::string
> Files
,
391 const DemangleCache
&DC
,
394 for (StringRef Filename
: Files
) {
395 auto Functions
= Coverage
.getCoveredFunctions(Filename
);
402 std::vector
<StringRef
> Funcnames
;
403 for (const auto &F
: Functions
)
404 Funcnames
.emplace_back(DC
.demangle(F
.Name
));
405 adjustColumnWidths({}, Funcnames
);
407 OS
<< "File '" << Filename
<< "':\n";
408 OS
<< column("Name", FunctionReportColumns
[0])
409 << column("Regions", FunctionReportColumns
[1], Column::RightAlignment
)
410 << column("Miss", FunctionReportColumns
[2], Column::RightAlignment
)
411 << column("Cover", FunctionReportColumns
[3], Column::RightAlignment
)
412 << column("Lines", FunctionReportColumns
[4], Column::RightAlignment
)
413 << column("Miss", FunctionReportColumns
[5], Column::RightAlignment
)
414 << column("Cover", FunctionReportColumns
[6], Column::RightAlignment
);
415 if (Options
.ShowBranchSummary
)
416 OS
<< column("Branches", FunctionReportColumns
[7], Column::RightAlignment
)
417 << column("Miss", FunctionReportColumns
[8], Column::RightAlignment
)
418 << column("Cover", FunctionReportColumns
[9], Column::RightAlignment
);
419 if (Options
.ShowMCDCSummary
)
420 OS
<< column("MC/DC Conditions", FunctionReportColumns
[10],
421 Column::RightAlignment
)
422 << column("Miss", FunctionReportColumns
[11], Column::RightAlignment
)
423 << column("Cover", FunctionReportColumns
[12], Column::RightAlignment
);
425 renderDivider(OS
, Options
, false);
427 FunctionCoverageSummary
Totals("TOTAL");
428 for (const auto &F
: Functions
) {
429 auto Function
= FunctionCoverageSummary::get(Coverage
, F
);
430 ++Totals
.ExecutionCount
;
431 Totals
.RegionCoverage
+= Function
.RegionCoverage
;
432 Totals
.LineCoverage
+= Function
.LineCoverage
;
433 Totals
.BranchCoverage
+= Function
.BranchCoverage
;
434 Totals
.MCDCCoverage
+= Function
.MCDCCoverage
;
435 render(Function
, DC
, OS
);
437 if (Totals
.ExecutionCount
) {
438 renderDivider(OS
, Options
, false);
440 render(Totals
, DC
, OS
);
445 void CoverageReport::prepareSingleFileReport(const StringRef Filename
,
446 const coverage::CoverageMapping
*Coverage
,
447 const CoverageViewOptions
&Options
, const unsigned LCP
,
448 FileCoverageSummary
*FileReport
, const CoverageFilter
*Filters
) {
449 for (const auto &Group
: Coverage
->getInstantiationGroups(Filename
)) {
450 std::vector
<FunctionCoverageSummary
> InstantiationSummaries
;
451 for (const coverage::FunctionRecord
*F
: Group
.getInstantiations()) {
452 if (!Filters
->matches(*Coverage
, *F
))
454 auto InstantiationSummary
= FunctionCoverageSummary::get(*Coverage
, *F
);
455 FileReport
->addInstantiation(InstantiationSummary
);
456 InstantiationSummaries
.push_back(InstantiationSummary
);
458 if (InstantiationSummaries
.empty())
462 FunctionCoverageSummary::get(Group
, InstantiationSummaries
);
465 outs() << "InstantiationGroup: " << GroupSummary
.Name
<< " with "
466 << "size = " << Group
.size() << "\n";
468 FileReport
->addFunction(GroupSummary
);
472 std::vector
<FileCoverageSummary
> CoverageReport::prepareFileReports(
473 const coverage::CoverageMapping
&Coverage
, FileCoverageSummary
&Totals
,
474 ArrayRef
<std::string
> Files
, const CoverageViewOptions
&Options
,
475 const CoverageFilter
&Filters
) {
476 unsigned LCP
= getRedundantPrefixLen(Files
);
478 ThreadPoolStrategy S
= hardware_concurrency(Options
.NumThreads
);
479 if (Options
.NumThreads
== 0) {
480 // If NumThreads is not specified, create one thread for each input, up to
481 // the number of hardware cores.
482 S
= heavyweight_hardware_concurrency(Files
.size());
485 DefaultThreadPool
Pool(S
);
487 std::vector
<FileCoverageSummary
> FileReports
;
488 FileReports
.reserve(Files
.size());
490 for (StringRef Filename
: Files
) {
491 FileReports
.emplace_back(Filename
.drop_front(LCP
));
492 Pool
.async(&CoverageReport::prepareSingleFileReport
, Filename
,
493 &Coverage
, Options
, LCP
, &FileReports
.back(), &Filters
);
497 for (const auto &FileReport
: FileReports
)
498 Totals
+= FileReport
;
503 void CoverageReport::renderFileReports(
504 raw_ostream
&OS
, const CoverageFilters
&IgnoreFilenameFilters
) const {
505 std::vector
<std::string
> UniqueSourceFiles
;
506 for (StringRef SF
: Coverage
.getUniqueSourceFiles()) {
507 // Apply ignore source files filters.
508 if (!IgnoreFilenameFilters
.matchesFilename(SF
))
509 UniqueSourceFiles
.emplace_back(SF
.str());
511 renderFileReports(OS
, UniqueSourceFiles
);
514 void CoverageReport::renderFileReports(
515 raw_ostream
&OS
, ArrayRef
<std::string
> Files
) const {
516 renderFileReports(OS
, Files
, CoverageFiltersMatchAll());
519 void CoverageReport::renderFileReports(
520 raw_ostream
&OS
, ArrayRef
<std::string
> Files
,
521 const CoverageFiltersMatchAll
&Filters
) const {
522 FileCoverageSummary
Totals("TOTAL");
524 prepareFileReports(Coverage
, Totals
, Files
, Options
, Filters
);
525 renderFileReports(OS
, FileReports
, Totals
, Filters
.empty());
528 void CoverageReport::renderFileReports(
529 raw_ostream
&OS
, const std::vector
<FileCoverageSummary
> &FileReports
,
530 const FileCoverageSummary
&Totals
, bool ShowEmptyFiles
) const {
531 std::vector
<StringRef
> Filenames
;
532 Filenames
.reserve(FileReports
.size());
533 for (const FileCoverageSummary
&FCS
: FileReports
)
534 Filenames
.emplace_back(FCS
.Name
);
535 adjustColumnWidths(Filenames
, {});
537 OS
<< column("Filename", FileReportColumns
[0]);
538 if (Options
.ShowRegionSummary
)
539 OS
<< column("Regions", FileReportColumns
[1], Column::RightAlignment
)
540 << column("Missed Regions", FileReportColumns
[2], Column::RightAlignment
)
541 << column("Cover", FileReportColumns
[3], Column::RightAlignment
);
542 OS
<< column("Functions", FileReportColumns
[4], Column::RightAlignment
)
543 << column("Missed Functions", FileReportColumns
[5], Column::RightAlignment
)
544 << column("Executed", FileReportColumns
[6], Column::RightAlignment
);
545 if (Options
.ShowInstantiationSummary
)
546 OS
<< column("Instantiations", FileReportColumns
[7], Column::RightAlignment
)
547 << column("Missed Insts.", FileReportColumns
[8], Column::RightAlignment
)
548 << column("Executed", FileReportColumns
[9], Column::RightAlignment
);
549 OS
<< column("Lines", FileReportColumns
[10], Column::RightAlignment
)
550 << column("Missed Lines", FileReportColumns
[11], Column::RightAlignment
)
551 << column("Cover", FileReportColumns
[12], Column::RightAlignment
);
552 if (Options
.ShowBranchSummary
)
553 OS
<< column("Branches", FileReportColumns
[13], Column::RightAlignment
)
554 << column("Missed Branches", FileReportColumns
[14],
555 Column::RightAlignment
)
556 << column("Cover", FileReportColumns
[15], Column::RightAlignment
);
557 if (Options
.ShowMCDCSummary
)
558 OS
<< column("MC/DC Conditions", FileReportColumns
[16],
559 Column::RightAlignment
)
560 << column("Missed Conditions", FileReportColumns
[17],
561 Column::RightAlignment
)
562 << column("Cover", FileReportColumns
[18], Column::RightAlignment
);
564 renderDivider(OS
, Options
, true);
567 std::vector
<const FileCoverageSummary
*> EmptyFiles
;
568 for (const FileCoverageSummary
&FCS
: FileReports
) {
569 if (FCS
.FunctionCoverage
.getNumFunctions())
572 EmptyFiles
.push_back(&FCS
);
575 if (!EmptyFiles
.empty() && ShowEmptyFiles
) {
577 << "Files which contain no functions:\n";
579 for (auto FCS
: EmptyFiles
)
583 renderDivider(OS
, Options
, true);
588 Expected
<FileCoverageSummary
> DirectoryCoverageReport::prepareDirectoryReports(
589 ArrayRef
<std::string
> SourceFiles
) {
590 std::vector
<StringRef
> Files(SourceFiles
.begin(), SourceFiles
.end());
592 unsigned RootLCP
= getRedundantPrefixLen(Files
, 0);
593 auto LCPath
= Files
.front().substr(0, RootLCP
);
595 ThreadPoolStrategy PoolS
= hardware_concurrency(Options
.NumThreads
);
596 if (Options
.NumThreads
== 0) {
597 PoolS
= heavyweight_hardware_concurrency(Files
.size());
600 DefaultThreadPool
Pool(PoolS
);
603 LCPStack
= {RootLCP
};
604 FileCoverageSummary
RootTotals(LCPath
);
605 if (auto E
= prepareSubDirectoryReports(Files
, &RootTotals
))
606 return {std::move(E
)};
607 return {std::move(RootTotals
)};
610 /// Filter out files in LCPStack.back(), group others by subdirectory name
611 /// and recurse on them. After returning from all subdirectories, call
612 /// generateSubDirectoryReport(). \p Files must be non-empty. The
613 /// FileCoverageSummary of this directory will be added to \p Totals.
614 Error
DirectoryCoverageReport::prepareSubDirectoryReports(
615 const ArrayRef
<StringRef
> &Files
, FileCoverageSummary
*Totals
) {
616 assert(!Files
.empty() && "Files must have at least one element");
618 auto LCP
= LCPStack
.back();
619 auto LCPath
= Files
.front().substr(0, LCP
).str();
621 // Use ordered map to keep entries in order.
622 SubFileReports SubFiles
;
623 SubDirReports SubDirs
;
624 for (auto &&File
: Files
) {
625 auto SubPath
= File
.substr(LCPath
.size());
626 SmallVector
<char, 128> NativeSubPath
;
627 sys::path::native(SubPath
, NativeSubPath
);
628 StringRef
NativeSubPathRef(NativeSubPath
.data(), NativeSubPath
.size());
630 auto I
= sys::path::begin(NativeSubPathRef
);
631 auto E
= sys::path::end(NativeSubPathRef
);
632 assert(I
!= E
&& "Such case should have been filtered out in the caller");
634 auto Name
= SubPath
.substr(0, I
->size());
636 auto Iter
= SubFiles
.insert_or_assign(Name
, SubPath
).first
;
637 // Makes files reporting overlap with subdir reporting.
638 TPool
->async(&CoverageReport::prepareSingleFileReport
, File
, &Coverage
,
639 Options
, LCP
, &Iter
->second
, &Filters
);
641 SubDirs
[Name
].second
.push_back(File
);
645 // Call recursively on subdirectories.
646 for (auto &&KV
: SubDirs
) {
648 if (V
.second
.size() == 1) {
649 // If there's only one file in that subdirectory, we don't bother to
650 // recurse on it further.
651 V
.first
.Name
= V
.second
.front().substr(LCP
);
652 TPool
->async(&CoverageReport::prepareSingleFileReport
, V
.second
.front(),
653 &Coverage
, Options
, LCP
, &V
.first
, &Filters
);
655 auto SubDirLCP
= getRedundantPrefixLen(V
.second
, LCP
);
656 V
.first
.Name
= V
.second
.front().substr(LCP
, SubDirLCP
);
657 LCPStack
.push_back(LCP
+ SubDirLCP
);
658 if (auto E
= prepareSubDirectoryReports(V
.second
, &V
.first
))
665 FileCoverageSummary
CurrentTotals(LCPath
);
666 for (auto &&KV
: SubFiles
)
667 CurrentTotals
+= KV
.second
;
668 for (auto &&KV
: SubDirs
)
669 CurrentTotals
+= KV
.second
.first
;
670 *Totals
+= CurrentTotals
;
672 if (auto E
= generateSubDirectoryReport(
673 std::move(SubFiles
), std::move(SubDirs
), std::move(CurrentTotals
)))
677 return Error::success();
680 } // end namespace llvm