Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / llvm / tools / llvm-cov / CoverageReport.cpp
blob518f6cd3efa2f47194c968f048b055978c87f4fa
1 //===- CoverageReport.cpp - Code coverage report -------------------------===//
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 // 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/ADT/SmallString.h"
17 #include "llvm/Support/Format.h"
18 #include "llvm/Support/Path.h"
19 #include "llvm/Support/ThreadPool.h"
20 #include "llvm/Support/Threading.h"
21 #include <numeric>
23 using namespace llvm;
25 namespace {
27 /// Helper struct which prints trimmed and aligned columns.
28 struct Column {
29 enum TrimKind { NoTrim, WidthTrim, RightTrim };
31 enum AlignmentKind { LeftAlignment, RightAlignment };
33 StringRef Str;
34 unsigned Width;
35 TrimKind Trim;
36 AlignmentKind Alignment;
38 Column(StringRef Str, unsigned Width)
39 : Str(Str), Width(Width), Trim(WidthTrim), Alignment(LeftAlignment) {}
41 Column &set(TrimKind Value) {
42 Trim = Value;
43 return *this;
46 Column &set(AlignmentKind Value) {
47 Alignment = Value;
48 return *this;
51 void render(raw_ostream &OS) const {
52 if (Str.size() <= Width) {
53 if (Alignment == RightAlignment) {
54 OS.indent(Width - Str.size());
55 OS << Str;
56 return;
58 OS << Str;
59 OS.indent(Width - Str.size());
60 return;
63 switch (Trim) {
64 case NoTrim:
65 OS << Str;
66 break;
67 case WidthTrim:
68 OS << Str.substr(0, Width);
69 break;
70 case RightTrim:
71 OS << Str.substr(0, Width - 3) << "...";
72 break;
77 raw_ostream &operator<<(raw_ostream &OS, const Column &Value) {
78 Value.render(OS);
79 return OS;
82 Column column(StringRef Str, unsigned Width) { return Column(Str, Width); }
84 template <typename T>
85 Column column(StringRef Str, unsigned Width, const T &Value) {
86 return Column(Str, Width).set(Value);
89 // Specify the default column widths.
90 size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10, 16,
91 16, 10, 12, 18, 10, 12, 18, 10};
92 size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8};
94 /// Adjust column widths to fit long file paths and function names.
95 void adjustColumnWidths(ArrayRef<StringRef> Files,
96 ArrayRef<StringRef> Functions) {
97 for (StringRef Filename : Files)
98 FileReportColumns[0] = std::max(FileReportColumns[0], Filename.size());
99 for (StringRef Funcname : Functions)
100 FunctionReportColumns[0] =
101 std::max(FunctionReportColumns[0], Funcname.size());
104 /// Prints a horizontal divider long enough to cover the given column
105 /// widths.
106 void renderDivider(ArrayRef<size_t> ColumnWidths, raw_ostream &OS) {
107 size_t Length = std::accumulate(ColumnWidths.begin(), ColumnWidths.end(), 0);
108 for (size_t I = 0; I < Length; ++I)
109 OS << '-';
112 /// Return the color which correponds to the coverage percentage of a
113 /// certain metric.
114 template <typename T>
115 raw_ostream::Colors determineCoveragePercentageColor(const T &Info) {
116 if (Info.isFullyCovered())
117 return raw_ostream::GREEN;
118 return Info.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW
119 : raw_ostream::RED;
122 /// Get the number of redundant path components in each path in \p Paths.
123 unsigned getNumRedundantPathComponents(ArrayRef<std::string> Paths) {
124 // To start, set the number of redundant path components to the maximum
125 // possible value.
126 SmallVector<StringRef, 8> FirstPathComponents{sys::path::begin(Paths[0]),
127 sys::path::end(Paths[0])};
128 unsigned NumRedundant = FirstPathComponents.size();
130 for (unsigned I = 1, E = Paths.size(); NumRedundant > 0 && I < E; ++I) {
131 StringRef Path = Paths[I];
132 for (const auto &Component :
133 enumerate(make_range(sys::path::begin(Path), sys::path::end(Path)))) {
134 // Do not increase the number of redundant components: that would remove
135 // useful parts of already-visited paths.
136 if (Component.index() >= NumRedundant)
137 break;
139 // Lower the number of redundant components when there's a mismatch
140 // between the first path, and the path under consideration.
141 if (FirstPathComponents[Component.index()] != Component.value()) {
142 NumRedundant = Component.index();
143 break;
148 return NumRedundant;
151 /// Determine the length of the longest redundant prefix of the paths in
152 /// \p Paths.
153 unsigned getRedundantPrefixLen(ArrayRef<std::string> Paths) {
154 // If there's at most one path, no path components are redundant.
155 if (Paths.size() <= 1)
156 return 0;
158 unsigned PrefixLen = 0;
159 unsigned NumRedundant = getNumRedundantPathComponents(Paths);
160 auto Component = sys::path::begin(Paths[0]);
161 for (unsigned I = 0; I < NumRedundant; ++I) {
162 auto LastComponent = Component;
163 ++Component;
164 PrefixLen += Component - LastComponent;
166 return PrefixLen;
169 /// Determine the length of the longest redundant prefix of the substrs starts
170 /// from \p LCP in \p Paths. \p Paths can't be empty. If there's only one
171 /// element in \p Paths, the length of the substr is returned. Note this is
172 /// differnet from the behavior of the function above.
173 unsigned getRedundantPrefixLen(ArrayRef<StringRef> Paths, unsigned LCP) {
174 assert(!Paths.empty() && "Paths must have at least one element");
176 auto Iter = Paths.begin();
177 auto IterE = Paths.end();
178 auto Prefix = Iter->substr(LCP);
179 while (++Iter != IterE) {
180 auto Other = Iter->substr(LCP);
181 auto Len = std::min(Prefix.size(), Other.size());
182 for (std::size_t I = 0; I < Len; ++I) {
183 if (Prefix[I] != Other[I]) {
184 Prefix = Prefix.substr(0, I);
185 break;
190 for (auto I = Prefix.size(); --I != SIZE_MAX;) {
191 if (Prefix[I] == '/' || Prefix[I] == '\\')
192 return I + 1;
195 return Prefix.size();
198 } // end anonymous namespace
200 namespace llvm {
202 void CoverageReport::render(const FileCoverageSummary &File,
203 raw_ostream &OS) const {
204 auto FileCoverageColor =
205 determineCoveragePercentageColor(File.RegionCoverage);
206 auto FuncCoverageColor =
207 determineCoveragePercentageColor(File.FunctionCoverage);
208 auto InstantiationCoverageColor =
209 determineCoveragePercentageColor(File.InstantiationCoverage);
210 auto LineCoverageColor = determineCoveragePercentageColor(File.LineCoverage);
211 SmallString<256> FileName = File.Name;
212 sys::path::native(FileName);
214 // remove_dots will remove trailing slash, so we need to check before it.
215 auto IsDir = FileName.endswith(sys::path::get_separator());
216 sys::path::remove_dots(FileName, /*remove_dot_dot=*/true);
217 if (IsDir)
218 FileName += sys::path::get_separator();
220 OS << column(FileName, FileReportColumns[0], Column::NoTrim);
222 if (Options.ShowRegionSummary) {
223 OS << format("%*u", FileReportColumns[1],
224 (unsigned)File.RegionCoverage.getNumRegions());
225 Options.colored_ostream(OS, FileCoverageColor)
226 << format("%*u", FileReportColumns[2],
227 (unsigned)(File.RegionCoverage.getNumRegions() -
228 File.RegionCoverage.getCovered()));
229 if (File.RegionCoverage.getNumRegions())
230 Options.colored_ostream(OS, FileCoverageColor)
231 << format("%*.2f", FileReportColumns[3] - 1,
232 File.RegionCoverage.getPercentCovered())
233 << '%';
234 else
235 OS << column("-", FileReportColumns[3], Column::RightAlignment);
238 OS << format("%*u", FileReportColumns[4],
239 (unsigned)File.FunctionCoverage.getNumFunctions());
240 OS << format("%*u", FileReportColumns[5],
241 (unsigned)(File.FunctionCoverage.getNumFunctions() -
242 File.FunctionCoverage.getExecuted()));
243 if (File.FunctionCoverage.getNumFunctions())
244 Options.colored_ostream(OS, FuncCoverageColor)
245 << format("%*.2f", FileReportColumns[6] - 1,
246 File.FunctionCoverage.getPercentCovered())
247 << '%';
248 else
249 OS << column("-", FileReportColumns[6], Column::RightAlignment);
251 if (Options.ShowInstantiationSummary) {
252 OS << format("%*u", FileReportColumns[7],
253 (unsigned)File.InstantiationCoverage.getNumFunctions());
254 OS << format("%*u", FileReportColumns[8],
255 (unsigned)(File.InstantiationCoverage.getNumFunctions() -
256 File.InstantiationCoverage.getExecuted()));
257 if (File.InstantiationCoverage.getNumFunctions())
258 Options.colored_ostream(OS, InstantiationCoverageColor)
259 << format("%*.2f", FileReportColumns[9] - 1,
260 File.InstantiationCoverage.getPercentCovered())
261 << '%';
262 else
263 OS << column("-", FileReportColumns[9], Column::RightAlignment);
266 OS << format("%*u", FileReportColumns[10],
267 (unsigned)File.LineCoverage.getNumLines());
268 Options.colored_ostream(OS, LineCoverageColor) << format(
269 "%*u", FileReportColumns[11], (unsigned)(File.LineCoverage.getNumLines() -
270 File.LineCoverage.getCovered()));
271 if (File.LineCoverage.getNumLines())
272 Options.colored_ostream(OS, LineCoverageColor)
273 << format("%*.2f", FileReportColumns[12] - 1,
274 File.LineCoverage.getPercentCovered())
275 << '%';
276 else
277 OS << column("-", FileReportColumns[12], Column::RightAlignment);
279 if (Options.ShowBranchSummary) {
280 OS << format("%*u", FileReportColumns[13],
281 (unsigned)File.BranchCoverage.getNumBranches());
282 Options.colored_ostream(OS, LineCoverageColor)
283 << format("%*u", FileReportColumns[14],
284 (unsigned)(File.BranchCoverage.getNumBranches() -
285 File.BranchCoverage.getCovered()));
286 if (File.BranchCoverage.getNumBranches())
287 Options.colored_ostream(OS, LineCoverageColor)
288 << format("%*.2f", FileReportColumns[15] - 1,
289 File.BranchCoverage.getPercentCovered())
290 << '%';
291 else
292 OS << column("-", FileReportColumns[15], Column::RightAlignment);
295 OS << "\n";
298 void CoverageReport::render(const FunctionCoverageSummary &Function,
299 const DemangleCache &DC,
300 raw_ostream &OS) const {
301 auto FuncCoverageColor =
302 determineCoveragePercentageColor(Function.RegionCoverage);
303 auto LineCoverageColor =
304 determineCoveragePercentageColor(Function.LineCoverage);
305 OS << column(DC.demangle(Function.Name), FunctionReportColumns[0],
306 Column::RightTrim)
307 << format("%*u", FunctionReportColumns[1],
308 (unsigned)Function.RegionCoverage.getNumRegions());
309 Options.colored_ostream(OS, FuncCoverageColor)
310 << format("%*u", FunctionReportColumns[2],
311 (unsigned)(Function.RegionCoverage.getNumRegions() -
312 Function.RegionCoverage.getCovered()));
313 Options.colored_ostream(
314 OS, determineCoveragePercentageColor(Function.RegionCoverage))
315 << format("%*.2f", FunctionReportColumns[3] - 1,
316 Function.RegionCoverage.getPercentCovered())
317 << '%';
318 OS << format("%*u", FunctionReportColumns[4],
319 (unsigned)Function.LineCoverage.getNumLines());
320 Options.colored_ostream(OS, LineCoverageColor)
321 << format("%*u", FunctionReportColumns[5],
322 (unsigned)(Function.LineCoverage.getNumLines() -
323 Function.LineCoverage.getCovered()));
324 Options.colored_ostream(
325 OS, determineCoveragePercentageColor(Function.LineCoverage))
326 << format("%*.2f", FunctionReportColumns[6] - 1,
327 Function.LineCoverage.getPercentCovered())
328 << '%';
329 if (Options.ShowBranchSummary) {
330 OS << format("%*u", FunctionReportColumns[7],
331 (unsigned)Function.BranchCoverage.getNumBranches());
332 Options.colored_ostream(OS, LineCoverageColor)
333 << format("%*u", FunctionReportColumns[8],
334 (unsigned)(Function.BranchCoverage.getNumBranches() -
335 Function.BranchCoverage.getCovered()));
336 Options.colored_ostream(
337 OS, determineCoveragePercentageColor(Function.BranchCoverage))
338 << format("%*.2f", FunctionReportColumns[9] - 1,
339 Function.BranchCoverage.getPercentCovered())
340 << '%';
342 OS << "\n";
345 void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
346 const DemangleCache &DC,
347 raw_ostream &OS) {
348 bool isFirst = true;
349 for (StringRef Filename : Files) {
350 auto Functions = Coverage.getCoveredFunctions(Filename);
352 if (isFirst)
353 isFirst = false;
354 else
355 OS << "\n";
357 std::vector<StringRef> Funcnames;
358 for (const auto &F : Functions)
359 Funcnames.emplace_back(DC.demangle(F.Name));
360 adjustColumnWidths({}, Funcnames);
362 OS << "File '" << Filename << "':\n";
363 OS << column("Name", FunctionReportColumns[0])
364 << column("Regions", FunctionReportColumns[1], Column::RightAlignment)
365 << column("Miss", FunctionReportColumns[2], Column::RightAlignment)
366 << column("Cover", FunctionReportColumns[3], Column::RightAlignment)
367 << column("Lines", FunctionReportColumns[4], Column::RightAlignment)
368 << column("Miss", FunctionReportColumns[5], Column::RightAlignment)
369 << column("Cover", FunctionReportColumns[6], Column::RightAlignment);
370 if (Options.ShowBranchSummary)
371 OS << column("Branches", FunctionReportColumns[7], Column::RightAlignment)
372 << column("Miss", FunctionReportColumns[8], Column::RightAlignment)
373 << column("Cover", FunctionReportColumns[9], Column::RightAlignment);
374 OS << "\n";
375 renderDivider(FunctionReportColumns, OS);
376 OS << "\n";
377 FunctionCoverageSummary Totals("TOTAL");
378 for (const auto &F : Functions) {
379 auto Function = FunctionCoverageSummary::get(Coverage, F);
380 ++Totals.ExecutionCount;
381 Totals.RegionCoverage += Function.RegionCoverage;
382 Totals.LineCoverage += Function.LineCoverage;
383 Totals.BranchCoverage += Function.BranchCoverage;
384 render(Function, DC, OS);
386 if (Totals.ExecutionCount) {
387 renderDivider(FunctionReportColumns, OS);
388 OS << "\n";
389 render(Totals, DC, OS);
394 void CoverageReport::prepareSingleFileReport(const StringRef Filename,
395 const coverage::CoverageMapping *Coverage,
396 const CoverageViewOptions &Options, const unsigned LCP,
397 FileCoverageSummary *FileReport, const CoverageFilter *Filters) {
398 for (const auto &Group : Coverage->getInstantiationGroups(Filename)) {
399 std::vector<FunctionCoverageSummary> InstantiationSummaries;
400 for (const coverage::FunctionRecord *F : Group.getInstantiations()) {
401 if (!Filters->matches(*Coverage, *F))
402 continue;
403 auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F);
404 FileReport->addInstantiation(InstantiationSummary);
405 InstantiationSummaries.push_back(InstantiationSummary);
407 if (InstantiationSummaries.empty())
408 continue;
410 auto GroupSummary =
411 FunctionCoverageSummary::get(Group, InstantiationSummaries);
413 if (Options.Debug)
414 outs() << "InstantiationGroup: " << GroupSummary.Name << " with "
415 << "size = " << Group.size() << "\n";
417 FileReport->addFunction(GroupSummary);
421 std::vector<FileCoverageSummary> CoverageReport::prepareFileReports(
422 const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals,
423 ArrayRef<std::string> Files, const CoverageViewOptions &Options,
424 const CoverageFilter &Filters) {
425 unsigned LCP = getRedundantPrefixLen(Files);
427 ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);
428 if (Options.NumThreads == 0) {
429 // If NumThreads is not specified, create one thread for each input, up to
430 // the number of hardware cores.
431 S = heavyweight_hardware_concurrency(Files.size());
432 S.Limit = true;
434 ThreadPool Pool(S);
436 std::vector<FileCoverageSummary> FileReports;
437 FileReports.reserve(Files.size());
439 for (StringRef Filename : Files) {
440 FileReports.emplace_back(Filename.drop_front(LCP));
441 Pool.async(&CoverageReport::prepareSingleFileReport, Filename,
442 &Coverage, Options, LCP, &FileReports.back(), &Filters);
444 Pool.wait();
446 for (const auto &FileReport : FileReports)
447 Totals += FileReport;
449 return FileReports;
452 void CoverageReport::renderFileReports(
453 raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const {
454 std::vector<std::string> UniqueSourceFiles;
455 for (StringRef SF : Coverage.getUniqueSourceFiles()) {
456 // Apply ignore source files filters.
457 if (!IgnoreFilenameFilters.matchesFilename(SF))
458 UniqueSourceFiles.emplace_back(SF.str());
460 renderFileReports(OS, UniqueSourceFiles);
463 void CoverageReport::renderFileReports(
464 raw_ostream &OS, ArrayRef<std::string> Files) const {
465 renderFileReports(OS, Files, CoverageFiltersMatchAll());
468 void CoverageReport::renderFileReports(
469 raw_ostream &OS, ArrayRef<std::string> Files,
470 const CoverageFiltersMatchAll &Filters) const {
471 FileCoverageSummary Totals("TOTAL");
472 auto FileReports =
473 prepareFileReports(Coverage, Totals, Files, Options, Filters);
474 renderFileReports(OS, FileReports, Totals, Filters.empty());
477 void CoverageReport::renderFileReports(
478 raw_ostream &OS, const std::vector<FileCoverageSummary> &FileReports,
479 const FileCoverageSummary &Totals, bool ShowEmptyFiles) const {
480 std::vector<StringRef> Filenames;
481 Filenames.reserve(FileReports.size());
482 for (const FileCoverageSummary &FCS : FileReports)
483 Filenames.emplace_back(FCS.Name);
484 adjustColumnWidths(Filenames, {});
486 OS << column("Filename", FileReportColumns[0]);
487 if (Options.ShowRegionSummary)
488 OS << column("Regions", FileReportColumns[1], Column::RightAlignment)
489 << column("Missed Regions", FileReportColumns[2], Column::RightAlignment)
490 << column("Cover", FileReportColumns[3], Column::RightAlignment);
491 OS << column("Functions", FileReportColumns[4], Column::RightAlignment)
492 << column("Missed Functions", FileReportColumns[5], Column::RightAlignment)
493 << column("Executed", FileReportColumns[6], Column::RightAlignment);
494 if (Options.ShowInstantiationSummary)
495 OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment)
496 << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment)
497 << column("Executed", FileReportColumns[9], Column::RightAlignment);
498 OS << column("Lines", FileReportColumns[10], Column::RightAlignment)
499 << column("Missed Lines", FileReportColumns[11], Column::RightAlignment)
500 << column("Cover", FileReportColumns[12], Column::RightAlignment);
501 if (Options.ShowBranchSummary)
502 OS << column("Branches", FileReportColumns[13], Column::RightAlignment)
503 << column("Missed Branches", FileReportColumns[14],
504 Column::RightAlignment)
505 << column("Cover", FileReportColumns[15], Column::RightAlignment);
506 OS << "\n";
507 renderDivider(FileReportColumns, OS);
508 OS << "\n";
510 std::vector<const FileCoverageSummary *> EmptyFiles;
511 for (const FileCoverageSummary &FCS : FileReports) {
512 if (FCS.FunctionCoverage.getNumFunctions())
513 render(FCS, OS);
514 else
515 EmptyFiles.push_back(&FCS);
518 if (!EmptyFiles.empty() && ShowEmptyFiles) {
519 OS << "\n"
520 << "Files which contain no functions:\n";
522 for (auto FCS : EmptyFiles)
523 render(*FCS, OS);
526 renderDivider(FileReportColumns, OS);
527 OS << "\n";
528 render(Totals, OS);
531 Expected<FileCoverageSummary> DirectoryCoverageReport::prepareDirectoryReports(
532 ArrayRef<std::string> SourceFiles) {
533 std::vector<StringRef> Files(SourceFiles.begin(), SourceFiles.end());
535 unsigned RootLCP = getRedundantPrefixLen(Files, 0);
536 auto LCPath = Files.front().substr(0, RootLCP);
538 ThreadPoolStrategy PoolS = hardware_concurrency(Options.NumThreads);
539 if (Options.NumThreads == 0) {
540 PoolS = heavyweight_hardware_concurrency(Files.size());
541 PoolS.Limit = true;
543 ThreadPool Pool(PoolS);
545 TPool = &Pool;
546 LCPStack = {RootLCP};
547 FileCoverageSummary RootTotals(LCPath);
548 if (auto E = prepareSubDirectoryReports(Files, &RootTotals))
549 return {std::move(E)};
550 return {std::move(RootTotals)};
553 /// Filter out files in LCPStack.back(), group others by subdirectory name
554 /// and recurse on them. After returning from all subdirectories, call
555 /// generateSubDirectoryReport(). \p Files must be non-empty. The
556 /// FileCoverageSummary of this directory will be added to \p Totals.
557 Error DirectoryCoverageReport::prepareSubDirectoryReports(
558 const ArrayRef<StringRef> &Files, FileCoverageSummary *Totals) {
559 assert(!Files.empty() && "Files must have at least one element");
561 auto LCP = LCPStack.back();
562 auto LCPath = Files.front().substr(0, LCP).str();
564 // Use ordered map to keep entries in order.
565 SubFileReports SubFiles;
566 SubDirReports SubDirs;
567 for (auto &&File : Files) {
568 auto SubPath = File.substr(LCPath.size());
569 SmallVector<char, 128> NativeSubPath;
570 sys::path::native(SubPath, NativeSubPath);
571 StringRef NativeSubPathRef(NativeSubPath.data(), NativeSubPath.size());
573 auto I = sys::path::begin(NativeSubPathRef);
574 auto E = sys::path::end(NativeSubPathRef);
575 assert(I != E && "Such case should have been filtered out in the caller");
577 auto Name = SubPath.substr(0, I->size());
578 if (++I == E) {
579 auto Iter = SubFiles.insert_or_assign(Name, SubPath).first;
580 // Makes files reporting overlap with subdir reporting.
581 TPool->async(&CoverageReport::prepareSingleFileReport, File, &Coverage,
582 Options, LCP, &Iter->second, &Filters);
583 } else {
584 SubDirs[Name].second.push_back(File);
588 // Call recursively on subdirectories.
589 for (auto &&KV : SubDirs) {
590 auto &V = KV.second;
591 if (V.second.size() == 1) {
592 // If there's only one file in that subdirectory, we don't bother to
593 // recurse on it further.
594 V.first.Name = V.second.front().substr(LCP);
595 TPool->async(&CoverageReport::prepareSingleFileReport, V.second.front(),
596 &Coverage, Options, LCP, &V.first, &Filters);
597 } else {
598 auto SubDirLCP = getRedundantPrefixLen(V.second, LCP);
599 V.first.Name = V.second.front().substr(LCP, SubDirLCP);
600 LCPStack.push_back(LCP + SubDirLCP);
601 if (auto E = prepareSubDirectoryReports(V.second, &V.first))
602 return E;
606 TPool->wait();
608 FileCoverageSummary CurrentTotals(LCPath);
609 for (auto &&KV : SubFiles)
610 CurrentTotals += KV.second;
611 for (auto &&KV : SubDirs)
612 CurrentTotals += KV.second.first;
613 *Totals += CurrentTotals;
615 if (auto E = generateSubDirectoryReport(
616 std::move(SubFiles), std::move(SubDirs), std::move(CurrentTotals)))
617 return E;
619 LCPStack.pop_back();
620 return Error::success();
623 } // end namespace llvm