brintos

brintos / llvm-project-archived public Read only

0
0
Text · 25.6 KiB · 00aea40 Raw
681 lines · cpp
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-exception6//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 22using namespace llvm;23 24namespace {25 26/// Helper struct which prints trimmed and aligned columns.27struct 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 76raw_ostream &operator<<(raw_ostream &OS, const Column &Value) {77  Value.render(OS);78  return OS;79}80 81Column column(StringRef Str, unsigned Width) { return Column(Str, Width); }82 83template <typename T>84Column column(StringRef Str, unsigned Width, const T &Value) {85  return Column(Str, Width).set(Value);86}87 88// Specify the default column widths.89size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10, 16, 16, 10,90                              12, 18, 10, 12, 18, 10, 20, 21, 10};91size_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.94void 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 column104/// widths.105void 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 a129/// certain metric.130template <typename T>131raw_ostream::Colors determineCoveragePercentageColor(const T &Info) {132  if (Info.isFullyCovered())133    return raw_ostream::GREEN;134  return Info.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW135                                          : raw_ostream::RED;136}137 138/// Get the number of redundant path components in each path in \p Paths.139unsigned getNumRedundantPathComponents(ArrayRef<std::string> Paths) {140  // To start, set the number of redundant path components to the maximum141  // 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 remove151      // 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 mismatch156      // 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 in168/// \p Paths.169unsigned 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 starts186/// from \p LCP in \p Paths. \p Paths can't be empty. If there's only one187/// element in \p Paths, the length of the substr is returned. Note this is188/// differnet from the behavior of the function above.189unsigned 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 namespace215 216namespace llvm {217 218void 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    else251      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  else265    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    else279      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  else293    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    else308      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    else324      OS << column("-", FileReportColumns[18], Column::RightAlignment);325  }326 327  OS << "\n";328}329 330void 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 390void 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    else400      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 445void 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 472std::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 to481    // 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 503void 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 514void CoverageReport::renderFileReports(515    raw_ostream &OS, ArrayRef<std::string> Files) const {516  renderFileReports(OS, Files, CoverageFiltersMatchAll());517}518 519void 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 528void 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    else572      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 588Expected<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 name611/// and recurse on them. After returning from all subdirectories, call612/// generateSubDirectoryReport(). \p Files must be non-empty. The613/// FileCoverageSummary of this directory will be added to \p Totals.614Error 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 to650      // 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 llvm681