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. 10 // 11 //===----------------------------------------------------------------------===// 12 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" 20 #include <numeric> 21 22 using namespace llvm; 23 24 namespace { 25 26 /// Helper struct which prints trimmed and aligned columns. 27 struct Column { 28 enum TrimKind { NoTrim, WidthTrim, RightTrim }; 29 30 enum AlignmentKind { LeftAlignment, RightAlignment }; 31 32 StringRef Str; 33 unsigned Width; 34 TrimKind Trim; 35 AlignmentKind Alignment; 36 37 Column(StringRef Str, unsigned Width) 38 : Str(Str), Width(Width), Trim(WidthTrim), Alignment(LeftAlignment) {} 39 40 Column &set(TrimKind Value) { 41 Trim = Value; 42 return *this; 43 } 44 45 Column &set(AlignmentKind Value) { 46 Alignment = Value; 47 return *this; 48 } 49 50 void render(raw_ostream &OS) const { 51 if (Str.size() <= Width) { 52 if (Alignment == RightAlignment) { 53 OS.indent(Width - Str.size()); 54 OS << Str; 55 return; 56 } 57 OS << Str; 58 OS.indent(Width - Str.size()); 59 return; 60 } 61 62 switch (Trim) { 63 case NoTrim: 64 OS << Str; 65 break; 66 case WidthTrim: 67 OS << Str.substr(0, Width); 68 break; 69 case RightTrim: 70 OS << Str.substr(0, Width - 3) << "..."; 71 break; 72 } 73 } 74 }; 75 76 raw_ostream &operator<<(raw_ostream &OS, const Column &Value) { 77 Value.render(OS); 78 return OS; 79 } 80 81 Column column(StringRef Str, unsigned Width) { return Column(Str, Width); } 82 83 template <typename T> 84 Column column(StringRef Str, unsigned Width, const T &Value) { 85 return Column(Str, Width).set(Value); 86 } 87 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}; 92 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()); 101 } 102 103 /// Prints a horizontal divider long enough to cover the given column 104 /// widths. 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) 108 OS << '-'; 109 } 110 111 /// Return the color which correponds to the coverage percentage of a 112 /// certain metric. 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 118 : raw_ostream::RED; 119 } 120 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 124 // possible value. 125 SmallVector<StringRef, 8> FirstPathComponents{sys::path::begin(Paths[0]), 126 sys::path::end(Paths[0])}; 127 unsigned NumRedundant = FirstPathComponents.size(); 128 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) 136 break; 137 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(); 142 break; 143 } 144 } 145 } 146 147 return NumRedundant; 148 } 149 150 /// Determine the length of the longest redundant prefix of the paths in 151 /// \p Paths. 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) 155 return 0; 156 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; 162 ++Component; 163 PrefixLen += Component - LastComponent; 164 } 165 return PrefixLen; 166 } 167 168 /// Determine the length of the longest redundant prefix of the substrs starts 169 /// from \p LCP in \p Paths. \p Paths can't be empty. If there's only one 170 /// element in \p Paths, the length of the substr is returned. Note this is 171 /// differnet from the behavior of the function above. 172 unsigned getRedundantPrefixLen(ArrayRef<StringRef> Paths, unsigned LCP) { 173 assert(!Paths.empty() && "Paths must have at least one element"); 174 175 auto Iter = Paths.begin(); 176 auto IterE = Paths.end(); 177 auto Prefix = Iter->substr(LCP); 178 while (++Iter != IterE) { 179 auto Other = Iter->substr(LCP); 180 auto Len = std::min(Prefix.size(), Other.size()); 181 for (std::size_t I = 0; I < Len; ++I) { 182 if (Prefix[I] != Other[I]) { 183 Prefix = Prefix.substr(0, I); 184 break; 185 } 186 } 187 } 188 189 for (auto I = Prefix.size(); --I != SIZE_MAX;) { 190 if (Prefix[I] == '/' || Prefix[I] == '\\') 191 return I + 1; 192 } 193 194 return Prefix.size(); 195 } 196 197 } // end anonymous namespace 198 199 namespace llvm { 200 201 void CoverageReport::render(const FileCoverageSummary &File, 202 raw_ostream &OS) const { 203 auto FileCoverageColor = 204 determineCoveragePercentageColor(File.RegionCoverage); 205 auto FuncCoverageColor = 206 determineCoveragePercentageColor(File.FunctionCoverage); 207 auto InstantiationCoverageColor = 208 determineCoveragePercentageColor(File.InstantiationCoverage); 209 auto LineCoverageColor = determineCoveragePercentageColor(File.LineCoverage); 210 SmallString<256> FileName = File.Name; 211 sys::path::native(FileName); 212 213 // remove_dots will remove trailing slash, so we need to check before it. 214 auto IsDir = FileName.ends_with(sys::path::get_separator()); 215 sys::path::remove_dots(FileName, /*remove_dot_dot=*/true); 216 if (IsDir) 217 FileName += sys::path::get_separator(); 218 219 OS << column(FileName, FileReportColumns[0], Column::NoTrim); 220 221 if (Options.ShowRegionSummary) { 222 OS << format("%*u", FileReportColumns[1], 223 (unsigned)File.RegionCoverage.getNumRegions()); 224 Options.colored_ostream(OS, FileCoverageColor) 225 << format("%*u", FileReportColumns[2], 226 (unsigned)(File.RegionCoverage.getNumRegions() - 227 File.RegionCoverage.getCovered())); 228 if (File.RegionCoverage.getNumRegions()) 229 Options.colored_ostream(OS, FileCoverageColor) 230 << format("%*.2f", FileReportColumns[3] - 1, 231 File.RegionCoverage.getPercentCovered()) 232 << '%'; 233 else 234 OS << column("-", FileReportColumns[3], Column::RightAlignment); 235 } 236 237 OS << format("%*u", FileReportColumns[4], 238 (unsigned)File.FunctionCoverage.getNumFunctions()); 239 OS << format("%*u", FileReportColumns[5], 240 (unsigned)(File.FunctionCoverage.getNumFunctions() - 241 File.FunctionCoverage.getExecuted())); 242 if (File.FunctionCoverage.getNumFunctions()) 243 Options.colored_ostream(OS, FuncCoverageColor) 244 << format("%*.2f", FileReportColumns[6] - 1, 245 File.FunctionCoverage.getPercentCovered()) 246 << '%'; 247 else 248 OS << column("-", FileReportColumns[6], Column::RightAlignment); 249 250 if (Options.ShowInstantiationSummary) { 251 OS << format("%*u", FileReportColumns[7], 252 (unsigned)File.InstantiationCoverage.getNumFunctions()); 253 OS << format("%*u", FileReportColumns[8], 254 (unsigned)(File.InstantiationCoverage.getNumFunctions() - 255 File.InstantiationCoverage.getExecuted())); 256 if (File.InstantiationCoverage.getNumFunctions()) 257 Options.colored_ostream(OS, InstantiationCoverageColor) 258 << format("%*.2f", FileReportColumns[9] - 1, 259 File.InstantiationCoverage.getPercentCovered()) 260 << '%'; 261 else 262 OS << column("-", FileReportColumns[9], Column::RightAlignment); 263 } 264 265 OS << format("%*u", FileReportColumns[10], 266 (unsigned)File.LineCoverage.getNumLines()); 267 Options.colored_ostream(OS, LineCoverageColor) << format( 268 "%*u", FileReportColumns[11], (unsigned)(File.LineCoverage.getNumLines() - 269 File.LineCoverage.getCovered())); 270 if (File.LineCoverage.getNumLines()) 271 Options.colored_ostream(OS, LineCoverageColor) 272 << format("%*.2f", FileReportColumns[12] - 1, 273 File.LineCoverage.getPercentCovered()) 274 << '%'; 275 else 276 OS << column("-", FileReportColumns[12], Column::RightAlignment); 277 278 if (Options.ShowBranchSummary) { 279 OS << format("%*u", FileReportColumns[13], 280 (unsigned)File.BranchCoverage.getNumBranches()); 281 Options.colored_ostream(OS, LineCoverageColor) 282 << format("%*u", FileReportColumns[14], 283 (unsigned)(File.BranchCoverage.getNumBranches() - 284 File.BranchCoverage.getCovered())); 285 if (File.BranchCoverage.getNumBranches()) 286 Options.colored_ostream(OS, LineCoverageColor) 287 << format("%*.2f", FileReportColumns[15] - 1, 288 File.BranchCoverage.getPercentCovered()) 289 << '%'; 290 else 291 OS << column("-", FileReportColumns[15], Column::RightAlignment); 292 } 293 294 OS << "\n"; 295 } 296 297 void CoverageReport::render(const FunctionCoverageSummary &Function, 298 const DemangleCache &DC, 299 raw_ostream &OS) const { 300 auto FuncCoverageColor = 301 determineCoveragePercentageColor(Function.RegionCoverage); 302 auto LineCoverageColor = 303 determineCoveragePercentageColor(Function.LineCoverage); 304 OS << column(DC.demangle(Function.Name), FunctionReportColumns[0], 305 Column::RightTrim) 306 << format("%*u", FunctionReportColumns[1], 307 (unsigned)Function.RegionCoverage.getNumRegions()); 308 Options.colored_ostream(OS, FuncCoverageColor) 309 << format("%*u", FunctionReportColumns[2], 310 (unsigned)(Function.RegionCoverage.getNumRegions() - 311 Function.RegionCoverage.getCovered())); 312 Options.colored_ostream( 313 OS, determineCoveragePercentageColor(Function.RegionCoverage)) 314 << format("%*.2f", FunctionReportColumns[3] - 1, 315 Function.RegionCoverage.getPercentCovered()) 316 << '%'; 317 OS << format("%*u", FunctionReportColumns[4], 318 (unsigned)Function.LineCoverage.getNumLines()); 319 Options.colored_ostream(OS, LineCoverageColor) 320 << format("%*u", FunctionReportColumns[5], 321 (unsigned)(Function.LineCoverage.getNumLines() - 322 Function.LineCoverage.getCovered())); 323 Options.colored_ostream( 324 OS, determineCoveragePercentageColor(Function.LineCoverage)) 325 << format("%*.2f", FunctionReportColumns[6] - 1, 326 Function.LineCoverage.getPercentCovered()) 327 << '%'; 328 if (Options.ShowBranchSummary) { 329 OS << format("%*u", FunctionReportColumns[7], 330 (unsigned)Function.BranchCoverage.getNumBranches()); 331 Options.colored_ostream(OS, LineCoverageColor) 332 << format("%*u", FunctionReportColumns[8], 333 (unsigned)(Function.BranchCoverage.getNumBranches() - 334 Function.BranchCoverage.getCovered())); 335 Options.colored_ostream( 336 OS, determineCoveragePercentageColor(Function.BranchCoverage)) 337 << format("%*.2f", FunctionReportColumns[9] - 1, 338 Function.BranchCoverage.getPercentCovered()) 339 << '%'; 340 } 341 OS << "\n"; 342 } 343 344 void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files, 345 const DemangleCache &DC, 346 raw_ostream &OS) { 347 bool isFirst = true; 348 for (StringRef Filename : Files) { 349 auto Functions = Coverage.getCoveredFunctions(Filename); 350 351 if (isFirst) 352 isFirst = false; 353 else 354 OS << "\n"; 355 356 std::vector<StringRef> Funcnames; 357 for (const auto &F : Functions) 358 Funcnames.emplace_back(DC.demangle(F.Name)); 359 adjustColumnWidths({}, Funcnames); 360 361 OS << "File '" << Filename << "':\n"; 362 OS << column("Name", FunctionReportColumns[0]) 363 << column("Regions", FunctionReportColumns[1], Column::RightAlignment) 364 << column("Miss", FunctionReportColumns[2], Column::RightAlignment) 365 << column("Cover", FunctionReportColumns[3], Column::RightAlignment) 366 << column("Lines", FunctionReportColumns[4], Column::RightAlignment) 367 << column("Miss", FunctionReportColumns[5], Column::RightAlignment) 368 << column("Cover", FunctionReportColumns[6], Column::RightAlignment); 369 if (Options.ShowBranchSummary) 370 OS << column("Branches", FunctionReportColumns[7], Column::RightAlignment) 371 << column("Miss", FunctionReportColumns[8], Column::RightAlignment) 372 << column("Cover", FunctionReportColumns[9], Column::RightAlignment); 373 OS << "\n"; 374 renderDivider(FunctionReportColumns, OS); 375 OS << "\n"; 376 FunctionCoverageSummary Totals("TOTAL"); 377 for (const auto &F : Functions) { 378 auto Function = FunctionCoverageSummary::get(Coverage, F); 379 ++Totals.ExecutionCount; 380 Totals.RegionCoverage += Function.RegionCoverage; 381 Totals.LineCoverage += Function.LineCoverage; 382 Totals.BranchCoverage += Function.BranchCoverage; 383 render(Function, DC, OS); 384 } 385 if (Totals.ExecutionCount) { 386 renderDivider(FunctionReportColumns, OS); 387 OS << "\n"; 388 render(Totals, DC, OS); 389 } 390 } 391 } 392 393 void CoverageReport::prepareSingleFileReport(const StringRef Filename, 394 const coverage::CoverageMapping *Coverage, 395 const CoverageViewOptions &Options, const unsigned LCP, 396 FileCoverageSummary *FileReport, const CoverageFilter *Filters) { 397 for (const auto &Group : Coverage->getInstantiationGroups(Filename)) { 398 std::vector<FunctionCoverageSummary> InstantiationSummaries; 399 for (const coverage::FunctionRecord *F : Group.getInstantiations()) { 400 if (!Filters->matches(*Coverage, *F)) 401 continue; 402 auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F); 403 FileReport->addInstantiation(InstantiationSummary); 404 InstantiationSummaries.push_back(InstantiationSummary); 405 } 406 if (InstantiationSummaries.empty()) 407 continue; 408 409 auto GroupSummary = 410 FunctionCoverageSummary::get(Group, InstantiationSummaries); 411 412 if (Options.Debug) 413 outs() << "InstantiationGroup: " << GroupSummary.Name << " with " 414 << "size = " << Group.size() << "\n"; 415 416 FileReport->addFunction(GroupSummary); 417 } 418 } 419 420 std::vector<FileCoverageSummary> CoverageReport::prepareFileReports( 421 const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals, 422 ArrayRef<std::string> Files, const CoverageViewOptions &Options, 423 const CoverageFilter &Filters) { 424 unsigned LCP = getRedundantPrefixLen(Files); 425 426 ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads); 427 if (Options.NumThreads == 0) { 428 // If NumThreads is not specified, create one thread for each input, up to 429 // the number of hardware cores. 430 S = heavyweight_hardware_concurrency(Files.size()); 431 S.Limit = true; 432 } 433 ThreadPool Pool(S); 434 435 std::vector<FileCoverageSummary> FileReports; 436 FileReports.reserve(Files.size()); 437 438 for (StringRef Filename : Files) { 439 FileReports.emplace_back(Filename.drop_front(LCP)); 440 Pool.async(&CoverageReport::prepareSingleFileReport, Filename, 441 &Coverage, Options, LCP, &FileReports.back(), &Filters); 442 } 443 Pool.wait(); 444 445 for (const auto &FileReport : FileReports) 446 Totals += FileReport; 447 448 return FileReports; 449 } 450 451 void CoverageReport::renderFileReports( 452 raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const { 453 std::vector<std::string> UniqueSourceFiles; 454 for (StringRef SF : Coverage.getUniqueSourceFiles()) { 455 // Apply ignore source files filters. 456 if (!IgnoreFilenameFilters.matchesFilename(SF)) 457 UniqueSourceFiles.emplace_back(SF.str()); 458 } 459 renderFileReports(OS, UniqueSourceFiles); 460 } 461 462 void CoverageReport::renderFileReports( 463 raw_ostream &OS, ArrayRef<std::string> Files) const { 464 renderFileReports(OS, Files, CoverageFiltersMatchAll()); 465 } 466 467 void CoverageReport::renderFileReports( 468 raw_ostream &OS, ArrayRef<std::string> Files, 469 const CoverageFiltersMatchAll &Filters) const { 470 FileCoverageSummary Totals("TOTAL"); 471 auto FileReports = 472 prepareFileReports(Coverage, Totals, Files, Options, Filters); 473 renderFileReports(OS, FileReports, Totals, Filters.empty()); 474 } 475 476 void CoverageReport::renderFileReports( 477 raw_ostream &OS, const std::vector<FileCoverageSummary> &FileReports, 478 const FileCoverageSummary &Totals, bool ShowEmptyFiles) const { 479 std::vector<StringRef> Filenames; 480 Filenames.reserve(FileReports.size()); 481 for (const FileCoverageSummary &FCS : FileReports) 482 Filenames.emplace_back(FCS.Name); 483 adjustColumnWidths(Filenames, {}); 484 485 OS << column("Filename", FileReportColumns[0]); 486 if (Options.ShowRegionSummary) 487 OS << column("Regions", FileReportColumns[1], Column::RightAlignment) 488 << column("Missed Regions", FileReportColumns[2], Column::RightAlignment) 489 << column("Cover", FileReportColumns[3], Column::RightAlignment); 490 OS << column("Functions", FileReportColumns[4], Column::RightAlignment) 491 << column("Missed Functions", FileReportColumns[5], Column::RightAlignment) 492 << column("Executed", FileReportColumns[6], Column::RightAlignment); 493 if (Options.ShowInstantiationSummary) 494 OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment) 495 << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment) 496 << column("Executed", FileReportColumns[9], Column::RightAlignment); 497 OS << column("Lines", FileReportColumns[10], Column::RightAlignment) 498 << column("Missed Lines", FileReportColumns[11], Column::RightAlignment) 499 << column("Cover", FileReportColumns[12], Column::RightAlignment); 500 if (Options.ShowBranchSummary) 501 OS << column("Branches", FileReportColumns[13], Column::RightAlignment) 502 << column("Missed Branches", FileReportColumns[14], 503 Column::RightAlignment) 504 << column("Cover", FileReportColumns[15], Column::RightAlignment); 505 OS << "\n"; 506 renderDivider(FileReportColumns, OS); 507 OS << "\n"; 508 509 std::vector<const FileCoverageSummary *> EmptyFiles; 510 for (const FileCoverageSummary &FCS : FileReports) { 511 if (FCS.FunctionCoverage.getNumFunctions()) 512 render(FCS, OS); 513 else 514 EmptyFiles.push_back(&FCS); 515 } 516 517 if (!EmptyFiles.empty() && ShowEmptyFiles) { 518 OS << "\n" 519 << "Files which contain no functions:\n"; 520 521 for (auto FCS : EmptyFiles) 522 render(*FCS, OS); 523 } 524 525 renderDivider(FileReportColumns, OS); 526 OS << "\n"; 527 render(Totals, OS); 528 } 529 530 Expected<FileCoverageSummary> DirectoryCoverageReport::prepareDirectoryReports( 531 ArrayRef<std::string> SourceFiles) { 532 std::vector<StringRef> Files(SourceFiles.begin(), SourceFiles.end()); 533 534 unsigned RootLCP = getRedundantPrefixLen(Files, 0); 535 auto LCPath = Files.front().substr(0, RootLCP); 536 537 ThreadPoolStrategy PoolS = hardware_concurrency(Options.NumThreads); 538 if (Options.NumThreads == 0) { 539 PoolS = heavyweight_hardware_concurrency(Files.size()); 540 PoolS.Limit = true; 541 } 542 ThreadPool Pool(PoolS); 543 544 TPool = &Pool; 545 LCPStack = {RootLCP}; 546 FileCoverageSummary RootTotals(LCPath); 547 if (auto E = prepareSubDirectoryReports(Files, &RootTotals)) 548 return {std::move(E)}; 549 return {std::move(RootTotals)}; 550 } 551 552 /// Filter out files in LCPStack.back(), group others by subdirectory name 553 /// and recurse on them. After returning from all subdirectories, call 554 /// generateSubDirectoryReport(). \p Files must be non-empty. The 555 /// FileCoverageSummary of this directory will be added to \p Totals. 556 Error DirectoryCoverageReport::prepareSubDirectoryReports( 557 const ArrayRef<StringRef> &Files, FileCoverageSummary *Totals) { 558 assert(!Files.empty() && "Files must have at least one element"); 559 560 auto LCP = LCPStack.back(); 561 auto LCPath = Files.front().substr(0, LCP).str(); 562 563 // Use ordered map to keep entries in order. 564 SubFileReports SubFiles; 565 SubDirReports SubDirs; 566 for (auto &&File : Files) { 567 auto SubPath = File.substr(LCPath.size()); 568 SmallVector<char, 128> NativeSubPath; 569 sys::path::native(SubPath, NativeSubPath); 570 StringRef NativeSubPathRef(NativeSubPath.data(), NativeSubPath.size()); 571 572 auto I = sys::path::begin(NativeSubPathRef); 573 auto E = sys::path::end(NativeSubPathRef); 574 assert(I != E && "Such case should have been filtered out in the caller"); 575 576 auto Name = SubPath.substr(0, I->size()); 577 if (++I == E) { 578 auto Iter = SubFiles.insert_or_assign(Name, SubPath).first; 579 // Makes files reporting overlap with subdir reporting. 580 TPool->async(&CoverageReport::prepareSingleFileReport, File, &Coverage, 581 Options, LCP, &Iter->second, &Filters); 582 } else { 583 SubDirs[Name].second.push_back(File); 584 } 585 } 586 587 // Call recursively on subdirectories. 588 for (auto &&KV : SubDirs) { 589 auto &V = KV.second; 590 if (V.second.size() == 1) { 591 // If there's only one file in that subdirectory, we don't bother to 592 // recurse on it further. 593 V.first.Name = V.second.front().substr(LCP); 594 TPool->async(&CoverageReport::prepareSingleFileReport, V.second.front(), 595 &Coverage, Options, LCP, &V.first, &Filters); 596 } else { 597 auto SubDirLCP = getRedundantPrefixLen(V.second, LCP); 598 V.first.Name = V.second.front().substr(LCP, SubDirLCP); 599 LCPStack.push_back(LCP + SubDirLCP); 600 if (auto E = prepareSubDirectoryReports(V.second, &V.first)) 601 return E; 602 } 603 } 604 605 TPool->wait(); 606 607 FileCoverageSummary CurrentTotals(LCPath); 608 for (auto &&KV : SubFiles) 609 CurrentTotals += KV.second; 610 for (auto &&KV : SubDirs) 611 CurrentTotals += KV.second.first; 612 *Totals += CurrentTotals; 613 614 if (auto E = generateSubDirectoryReport( 615 std::move(SubFiles), std::move(SubDirs), std::move(CurrentTotals))) 616 return E; 617 618 LCPStack.pop_back(); 619 return Error::success(); 620 } 621 622 } // end namespace llvm 623