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, 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}; 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 if (Options.ShowMCDCSummary) { 295 OS << format("%*u", FileReportColumns[16], 296 (unsigned)File.MCDCCoverage.getNumPairs()); 297 Options.colored_ostream(OS, LineCoverageColor) 298 << format("%*u", FileReportColumns[17], 299 (unsigned)(File.MCDCCoverage.getNumPairs() - 300 File.MCDCCoverage.getCoveredPairs())); 301 if (File.MCDCCoverage.getNumPairs()) 302 Options.colored_ostream(OS, LineCoverageColor) 303 << format("%*.2f", FileReportColumns[18] - 1, 304 File.MCDCCoverage.getPercentCovered()) 305 << '%'; 306 else 307 OS << column("-", FileReportColumns[18], Column::RightAlignment); 308 } 309 310 OS << "\n"; 311 } 312 313 void CoverageReport::render(const FunctionCoverageSummary &Function, 314 const DemangleCache &DC, 315 raw_ostream &OS) const { 316 auto FuncCoverageColor = 317 determineCoveragePercentageColor(Function.RegionCoverage); 318 auto LineCoverageColor = 319 determineCoveragePercentageColor(Function.LineCoverage); 320 OS << column(DC.demangle(Function.Name), FunctionReportColumns[0], 321 Column::RightTrim) 322 << format("%*u", FunctionReportColumns[1], 323 (unsigned)Function.RegionCoverage.getNumRegions()); 324 Options.colored_ostream(OS, FuncCoverageColor) 325 << format("%*u", FunctionReportColumns[2], 326 (unsigned)(Function.RegionCoverage.getNumRegions() - 327 Function.RegionCoverage.getCovered())); 328 Options.colored_ostream( 329 OS, determineCoveragePercentageColor(Function.RegionCoverage)) 330 << format("%*.2f", FunctionReportColumns[3] - 1, 331 Function.RegionCoverage.getPercentCovered()) 332 << '%'; 333 OS << format("%*u", FunctionReportColumns[4], 334 (unsigned)Function.LineCoverage.getNumLines()); 335 Options.colored_ostream(OS, LineCoverageColor) 336 << format("%*u", FunctionReportColumns[5], 337 (unsigned)(Function.LineCoverage.getNumLines() - 338 Function.LineCoverage.getCovered())); 339 Options.colored_ostream( 340 OS, determineCoveragePercentageColor(Function.LineCoverage)) 341 << format("%*.2f", FunctionReportColumns[6] - 1, 342 Function.LineCoverage.getPercentCovered()) 343 << '%'; 344 if (Options.ShowBranchSummary) { 345 OS << format("%*u", FunctionReportColumns[7], 346 (unsigned)Function.BranchCoverage.getNumBranches()); 347 Options.colored_ostream(OS, LineCoverageColor) 348 << format("%*u", FunctionReportColumns[8], 349 (unsigned)(Function.BranchCoverage.getNumBranches() - 350 Function.BranchCoverage.getCovered())); 351 Options.colored_ostream( 352 OS, determineCoveragePercentageColor(Function.BranchCoverage)) 353 << format("%*.2f", FunctionReportColumns[9] - 1, 354 Function.BranchCoverage.getPercentCovered()) 355 << '%'; 356 } 357 if (Options.ShowMCDCSummary) { 358 OS << format("%*u", FunctionReportColumns[10], 359 (unsigned)Function.MCDCCoverage.getNumPairs()); 360 Options.colored_ostream(OS, LineCoverageColor) 361 << format("%*u", FunctionReportColumns[11], 362 (unsigned)(Function.MCDCCoverage.getNumPairs() - 363 Function.MCDCCoverage.getCoveredPairs())); 364 Options.colored_ostream( 365 OS, determineCoveragePercentageColor(Function.MCDCCoverage)) 366 << format("%*.2f", FunctionReportColumns[12] - 1, 367 Function.MCDCCoverage.getPercentCovered()) 368 << '%'; 369 } 370 OS << "\n"; 371 } 372 373 void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files, 374 const DemangleCache &DC, 375 raw_ostream &OS) { 376 bool isFirst = true; 377 for (StringRef Filename : Files) { 378 auto Functions = Coverage.getCoveredFunctions(Filename); 379 380 if (isFirst) 381 isFirst = false; 382 else 383 OS << "\n"; 384 385 std::vector<StringRef> Funcnames; 386 for (const auto &F : Functions) 387 Funcnames.emplace_back(DC.demangle(F.Name)); 388 adjustColumnWidths({}, Funcnames); 389 390 OS << "File '" << Filename << "':\n"; 391 OS << column("Name", FunctionReportColumns[0]) 392 << column("Regions", FunctionReportColumns[1], Column::RightAlignment) 393 << column("Miss", FunctionReportColumns[2], Column::RightAlignment) 394 << column("Cover", FunctionReportColumns[3], Column::RightAlignment) 395 << column("Lines", FunctionReportColumns[4], Column::RightAlignment) 396 << column("Miss", FunctionReportColumns[5], Column::RightAlignment) 397 << column("Cover", FunctionReportColumns[6], Column::RightAlignment); 398 if (Options.ShowBranchSummary) 399 OS << column("Branches", FunctionReportColumns[7], Column::RightAlignment) 400 << column("Miss", FunctionReportColumns[8], Column::RightAlignment) 401 << column("Cover", FunctionReportColumns[9], Column::RightAlignment); 402 if (Options.ShowMCDCSummary) 403 OS << column("MC/DC Conditions", FunctionReportColumns[10], 404 Column::RightAlignment) 405 << column("Miss", FunctionReportColumns[11], Column::RightAlignment) 406 << column("Cover", FunctionReportColumns[12], Column::RightAlignment); 407 OS << "\n"; 408 renderDivider(FunctionReportColumns, OS); 409 OS << "\n"; 410 FunctionCoverageSummary Totals("TOTAL"); 411 for (const auto &F : Functions) { 412 auto Function = FunctionCoverageSummary::get(Coverage, F); 413 ++Totals.ExecutionCount; 414 Totals.RegionCoverage += Function.RegionCoverage; 415 Totals.LineCoverage += Function.LineCoverage; 416 Totals.BranchCoverage += Function.BranchCoverage; 417 Totals.MCDCCoverage += Function.MCDCCoverage; 418 render(Function, DC, OS); 419 } 420 if (Totals.ExecutionCount) { 421 renderDivider(FunctionReportColumns, OS); 422 OS << "\n"; 423 render(Totals, DC, OS); 424 } 425 } 426 } 427 428 void CoverageReport::prepareSingleFileReport(const StringRef Filename, 429 const coverage::CoverageMapping *Coverage, 430 const CoverageViewOptions &Options, const unsigned LCP, 431 FileCoverageSummary *FileReport, const CoverageFilter *Filters) { 432 for (const auto &Group : Coverage->getInstantiationGroups(Filename)) { 433 std::vector<FunctionCoverageSummary> InstantiationSummaries; 434 for (const coverage::FunctionRecord *F : Group.getInstantiations()) { 435 if (!Filters->matches(*Coverage, *F)) 436 continue; 437 auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F); 438 FileReport->addInstantiation(InstantiationSummary); 439 InstantiationSummaries.push_back(InstantiationSummary); 440 } 441 if (InstantiationSummaries.empty()) 442 continue; 443 444 auto GroupSummary = 445 FunctionCoverageSummary::get(Group, InstantiationSummaries); 446 447 if (Options.Debug) 448 outs() << "InstantiationGroup: " << GroupSummary.Name << " with " 449 << "size = " << Group.size() << "\n"; 450 451 FileReport->addFunction(GroupSummary); 452 } 453 } 454 455 std::vector<FileCoverageSummary> CoverageReport::prepareFileReports( 456 const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals, 457 ArrayRef<std::string> Files, const CoverageViewOptions &Options, 458 const CoverageFilter &Filters) { 459 unsigned LCP = getRedundantPrefixLen(Files); 460 461 ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads); 462 if (Options.NumThreads == 0) { 463 // If NumThreads is not specified, create one thread for each input, up to 464 // the number of hardware cores. 465 S = heavyweight_hardware_concurrency(Files.size()); 466 S.Limit = true; 467 } 468 ThreadPool Pool(S); 469 470 std::vector<FileCoverageSummary> FileReports; 471 FileReports.reserve(Files.size()); 472 473 for (StringRef Filename : Files) { 474 FileReports.emplace_back(Filename.drop_front(LCP)); 475 Pool.async(&CoverageReport::prepareSingleFileReport, Filename, 476 &Coverage, Options, LCP, &FileReports.back(), &Filters); 477 } 478 Pool.wait(); 479 480 for (const auto &FileReport : FileReports) 481 Totals += FileReport; 482 483 return FileReports; 484 } 485 486 void CoverageReport::renderFileReports( 487 raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const { 488 std::vector<std::string> UniqueSourceFiles; 489 for (StringRef SF : Coverage.getUniqueSourceFiles()) { 490 // Apply ignore source files filters. 491 if (!IgnoreFilenameFilters.matchesFilename(SF)) 492 UniqueSourceFiles.emplace_back(SF.str()); 493 } 494 renderFileReports(OS, UniqueSourceFiles); 495 } 496 497 void CoverageReport::renderFileReports( 498 raw_ostream &OS, ArrayRef<std::string> Files) const { 499 renderFileReports(OS, Files, CoverageFiltersMatchAll()); 500 } 501 502 void CoverageReport::renderFileReports( 503 raw_ostream &OS, ArrayRef<std::string> Files, 504 const CoverageFiltersMatchAll &Filters) const { 505 FileCoverageSummary Totals("TOTAL"); 506 auto FileReports = 507 prepareFileReports(Coverage, Totals, Files, Options, Filters); 508 renderFileReports(OS, FileReports, Totals, Filters.empty()); 509 } 510 511 void CoverageReport::renderFileReports( 512 raw_ostream &OS, const std::vector<FileCoverageSummary> &FileReports, 513 const FileCoverageSummary &Totals, bool ShowEmptyFiles) const { 514 std::vector<StringRef> Filenames; 515 Filenames.reserve(FileReports.size()); 516 for (const FileCoverageSummary &FCS : FileReports) 517 Filenames.emplace_back(FCS.Name); 518 adjustColumnWidths(Filenames, {}); 519 520 OS << column("Filename", FileReportColumns[0]); 521 if (Options.ShowRegionSummary) 522 OS << column("Regions", FileReportColumns[1], Column::RightAlignment) 523 << column("Missed Regions", FileReportColumns[2], Column::RightAlignment) 524 << column("Cover", FileReportColumns[3], Column::RightAlignment); 525 OS << column("Functions", FileReportColumns[4], Column::RightAlignment) 526 << column("Missed Functions", FileReportColumns[5], Column::RightAlignment) 527 << column("Executed", FileReportColumns[6], Column::RightAlignment); 528 if (Options.ShowInstantiationSummary) 529 OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment) 530 << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment) 531 << column("Executed", FileReportColumns[9], Column::RightAlignment); 532 OS << column("Lines", FileReportColumns[10], Column::RightAlignment) 533 << column("Missed Lines", FileReportColumns[11], Column::RightAlignment) 534 << column("Cover", FileReportColumns[12], Column::RightAlignment); 535 if (Options.ShowBranchSummary) 536 OS << column("Branches", FileReportColumns[13], Column::RightAlignment) 537 << column("Missed Branches", FileReportColumns[14], 538 Column::RightAlignment) 539 << column("Cover", FileReportColumns[15], Column::RightAlignment); 540 if (Options.ShowMCDCSummary) 541 OS << column("MC/DC Conditions", FileReportColumns[16], 542 Column::RightAlignment) 543 << column("Missed Conditions", FileReportColumns[17], 544 Column::RightAlignment) 545 << column("Cover", FileReportColumns[18], Column::RightAlignment); 546 OS << "\n"; 547 renderDivider(FileReportColumns, OS); 548 OS << "\n"; 549 550 std::vector<const FileCoverageSummary *> EmptyFiles; 551 for (const FileCoverageSummary &FCS : FileReports) { 552 if (FCS.FunctionCoverage.getNumFunctions()) 553 render(FCS, OS); 554 else 555 EmptyFiles.push_back(&FCS); 556 } 557 558 if (!EmptyFiles.empty() && ShowEmptyFiles) { 559 OS << "\n" 560 << "Files which contain no functions:\n"; 561 562 for (auto FCS : EmptyFiles) 563 render(*FCS, OS); 564 } 565 566 renderDivider(FileReportColumns, OS); 567 OS << "\n"; 568 render(Totals, OS); 569 } 570 571 Expected<FileCoverageSummary> DirectoryCoverageReport::prepareDirectoryReports( 572 ArrayRef<std::string> SourceFiles) { 573 std::vector<StringRef> Files(SourceFiles.begin(), SourceFiles.end()); 574 575 unsigned RootLCP = getRedundantPrefixLen(Files, 0); 576 auto LCPath = Files.front().substr(0, RootLCP); 577 578 ThreadPoolStrategy PoolS = hardware_concurrency(Options.NumThreads); 579 if (Options.NumThreads == 0) { 580 PoolS = heavyweight_hardware_concurrency(Files.size()); 581 PoolS.Limit = true; 582 } 583 ThreadPool Pool(PoolS); 584 585 TPool = &Pool; 586 LCPStack = {RootLCP}; 587 FileCoverageSummary RootTotals(LCPath); 588 if (auto E = prepareSubDirectoryReports(Files, &RootTotals)) 589 return {std::move(E)}; 590 return {std::move(RootTotals)}; 591 } 592 593 /// Filter out files in LCPStack.back(), group others by subdirectory name 594 /// and recurse on them. After returning from all subdirectories, call 595 /// generateSubDirectoryReport(). \p Files must be non-empty. The 596 /// FileCoverageSummary of this directory will be added to \p Totals. 597 Error DirectoryCoverageReport::prepareSubDirectoryReports( 598 const ArrayRef<StringRef> &Files, FileCoverageSummary *Totals) { 599 assert(!Files.empty() && "Files must have at least one element"); 600 601 auto LCP = LCPStack.back(); 602 auto LCPath = Files.front().substr(0, LCP).str(); 603 604 // Use ordered map to keep entries in order. 605 SubFileReports SubFiles; 606 SubDirReports SubDirs; 607 for (auto &&File : Files) { 608 auto SubPath = File.substr(LCPath.size()); 609 SmallVector<char, 128> NativeSubPath; 610 sys::path::native(SubPath, NativeSubPath); 611 StringRef NativeSubPathRef(NativeSubPath.data(), NativeSubPath.size()); 612 613 auto I = sys::path::begin(NativeSubPathRef); 614 auto E = sys::path::end(NativeSubPathRef); 615 assert(I != E && "Such case should have been filtered out in the caller"); 616 617 auto Name = SubPath.substr(0, I->size()); 618 if (++I == E) { 619 auto Iter = SubFiles.insert_or_assign(Name, SubPath).first; 620 // Makes files reporting overlap with subdir reporting. 621 TPool->async(&CoverageReport::prepareSingleFileReport, File, &Coverage, 622 Options, LCP, &Iter->second, &Filters); 623 } else { 624 SubDirs[Name].second.push_back(File); 625 } 626 } 627 628 // Call recursively on subdirectories. 629 for (auto &&KV : SubDirs) { 630 auto &V = KV.second; 631 if (V.second.size() == 1) { 632 // If there's only one file in that subdirectory, we don't bother to 633 // recurse on it further. 634 V.first.Name = V.second.front().substr(LCP); 635 TPool->async(&CoverageReport::prepareSingleFileReport, V.second.front(), 636 &Coverage, Options, LCP, &V.first, &Filters); 637 } else { 638 auto SubDirLCP = getRedundantPrefixLen(V.second, LCP); 639 V.first.Name = V.second.front().substr(LCP, SubDirLCP); 640 LCPStack.push_back(LCP + SubDirLCP); 641 if (auto E = prepareSubDirectoryReports(V.second, &V.first)) 642 return E; 643 } 644 } 645 646 TPool->wait(); 647 648 FileCoverageSummary CurrentTotals(LCPath); 649 for (auto &&KV : SubFiles) 650 CurrentTotals += KV.second; 651 for (auto &&KV : SubDirs) 652 CurrentTotals += KV.second.first; 653 *Totals += CurrentTotals; 654 655 if (auto E = generateSubDirectoryReport( 656 std::move(SubFiles), std::move(SubDirs), std::move(CurrentTotals))) 657 return E; 658 659 LCPStack.pop_back(); 660 return Error::success(); 661 } 662 663 } // end namespace llvm 664