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