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