1 //===- CoverageReport.cpp - Code coverage report -------------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // This class implements rendering of a code coverage report.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "CoverageReport.h"
15 #include "RenderingSupport.h"
16 #include "llvm/ADT/DenseMap.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 
Column__anon3ba268350111::Column38   Column(StringRef Str, unsigned Width)
39       : Str(Str), Width(Width), Trim(WidthTrim), Alignment(LeftAlignment) {}
40 
set__anon3ba268350111::Column41   Column &set(TrimKind Value) {
42     Trim = Value;
43     return *this;
44   }
45 
set__anon3ba268350111::Column46   Column &set(AlignmentKind Value) {
47     Alignment = Value;
48     return *this;
49   }
50 
render__anon3ba268350111::Column51   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 
operator <<(raw_ostream & OS,const Column & Value)77 raw_ostream &operator<<(raw_ostream &OS, const Column &Value) {
78   Value.render(OS);
79   return OS;
80 }
81 
column(StringRef Str,unsigned Width)82 Column column(StringRef Str, unsigned Width) { return Column(Str, Width); }
83 
84 template <typename T>
column(StringRef Str,unsigned Width,const T & Value)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,
91                               16, 16, 10, 12, 18, 10};
92 size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8};
93 
94 /// Adjust column widths to fit long file paths and function names.
adjustColumnWidths(ArrayRef<StringRef> Files,ArrayRef<StringRef> Functions)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.
renderDivider(ArrayRef<size_t> ColumnWidths,raw_ostream & OS)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>
determineCoveragePercentageColor(const T & Info)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.
getNumRedundantPathComponents(ArrayRef<std::string> 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.
getRedundantPrefixLen(ArrayRef<std::string> 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 } // end anonymous namespace
170 
171 namespace llvm {
172 
render(const FileCoverageSummary & File,raw_ostream & OS) const173 void CoverageReport::render(const FileCoverageSummary &File,
174                             raw_ostream &OS) const {
175   auto FileCoverageColor =
176       determineCoveragePercentageColor(File.RegionCoverage);
177   auto FuncCoverageColor =
178       determineCoveragePercentageColor(File.FunctionCoverage);
179   auto InstantiationCoverageColor =
180       determineCoveragePercentageColor(File.InstantiationCoverage);
181   auto LineCoverageColor = determineCoveragePercentageColor(File.LineCoverage);
182   SmallString<256> FileName = File.Name;
183   sys::path::remove_dots(FileName, /*remove_dot_dots=*/true);
184   sys::path::native(FileName);
185   OS << column(FileName, FileReportColumns[0], Column::NoTrim);
186 
187   if (Options.ShowRegionSummary) {
188     OS << format("%*u", FileReportColumns[1],
189                  (unsigned)File.RegionCoverage.getNumRegions());
190     Options.colored_ostream(OS, FileCoverageColor)
191         << format("%*u", FileReportColumns[2],
192                   (unsigned)(File.RegionCoverage.getNumRegions() -
193                              File.RegionCoverage.getCovered()));
194     if (File.RegionCoverage.getNumRegions())
195       Options.colored_ostream(OS, FileCoverageColor)
196           << format("%*.2f", FileReportColumns[3] - 1,
197                     File.RegionCoverage.getPercentCovered())
198           << '%';
199     else
200       OS << column("-", FileReportColumns[3], Column::RightAlignment);
201   }
202 
203   OS << format("%*u", FileReportColumns[4],
204                (unsigned)File.FunctionCoverage.getNumFunctions());
205   OS << format("%*u", FileReportColumns[5],
206                (unsigned)(File.FunctionCoverage.getNumFunctions() -
207                           File.FunctionCoverage.getExecuted()));
208   if (File.FunctionCoverage.getNumFunctions())
209     Options.colored_ostream(OS, FuncCoverageColor)
210         << format("%*.2f", FileReportColumns[6] - 1,
211                   File.FunctionCoverage.getPercentCovered())
212         << '%';
213   else
214     OS << column("-", FileReportColumns[6], Column::RightAlignment);
215 
216   if (Options.ShowInstantiationSummary) {
217     OS << format("%*u", FileReportColumns[7],
218                  (unsigned)File.InstantiationCoverage.getNumFunctions());
219     OS << format("%*u", FileReportColumns[8],
220                  (unsigned)(File.InstantiationCoverage.getNumFunctions() -
221                             File.InstantiationCoverage.getExecuted()));
222     if (File.InstantiationCoverage.getNumFunctions())
223       Options.colored_ostream(OS, InstantiationCoverageColor)
224           << format("%*.2f", FileReportColumns[9] - 1,
225                     File.InstantiationCoverage.getPercentCovered())
226           << '%';
227     else
228       OS << column("-", FileReportColumns[9], Column::RightAlignment);
229   }
230 
231   OS << format("%*u", FileReportColumns[10],
232                (unsigned)File.LineCoverage.getNumLines());
233   Options.colored_ostream(OS, LineCoverageColor) << format(
234       "%*u", FileReportColumns[11], (unsigned)(File.LineCoverage.getNumLines() -
235                                                File.LineCoverage.getCovered()));
236   if (File.LineCoverage.getNumLines())
237     Options.colored_ostream(OS, LineCoverageColor)
238         << format("%*.2f", FileReportColumns[12] - 1,
239                   File.LineCoverage.getPercentCovered())
240         << '%';
241   else
242     OS << column("-", FileReportColumns[12], Column::RightAlignment);
243   OS << "\n";
244 }
245 
render(const FunctionCoverageSummary & Function,const DemangleCache & DC,raw_ostream & OS) const246 void CoverageReport::render(const FunctionCoverageSummary &Function,
247                             const DemangleCache &DC,
248                             raw_ostream &OS) const {
249   auto FuncCoverageColor =
250       determineCoveragePercentageColor(Function.RegionCoverage);
251   auto LineCoverageColor =
252       determineCoveragePercentageColor(Function.LineCoverage);
253   OS << column(DC.demangle(Function.Name), FunctionReportColumns[0],
254                Column::RightTrim)
255      << format("%*u", FunctionReportColumns[1],
256                (unsigned)Function.RegionCoverage.getNumRegions());
257   Options.colored_ostream(OS, FuncCoverageColor)
258       << format("%*u", FunctionReportColumns[2],
259                 (unsigned)(Function.RegionCoverage.getNumRegions() -
260                            Function.RegionCoverage.getCovered()));
261   Options.colored_ostream(
262       OS, determineCoveragePercentageColor(Function.RegionCoverage))
263       << format("%*.2f", FunctionReportColumns[3] - 1,
264                 Function.RegionCoverage.getPercentCovered())
265       << '%';
266   OS << format("%*u", FunctionReportColumns[4],
267                (unsigned)Function.LineCoverage.getNumLines());
268   Options.colored_ostream(OS, LineCoverageColor)
269       << format("%*u", FunctionReportColumns[5],
270                 (unsigned)(Function.LineCoverage.getNumLines() -
271                            Function.LineCoverage.getCovered()));
272   Options.colored_ostream(
273       OS, determineCoveragePercentageColor(Function.LineCoverage))
274       << format("%*.2f", FunctionReportColumns[6] - 1,
275                 Function.LineCoverage.getPercentCovered())
276       << '%';
277   OS << "\n";
278 }
279 
renderFunctionReports(ArrayRef<std::string> Files,const DemangleCache & DC,raw_ostream & OS)280 void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
281                                            const DemangleCache &DC,
282                                            raw_ostream &OS) {
283   bool isFirst = true;
284   for (StringRef Filename : Files) {
285     auto Functions = Coverage.getCoveredFunctions(Filename);
286 
287     if (isFirst)
288       isFirst = false;
289     else
290       OS << "\n";
291 
292     std::vector<StringRef> Funcnames;
293     for (const auto &F : Functions)
294       Funcnames.emplace_back(DC.demangle(F.Name));
295     adjustColumnWidths({}, Funcnames);
296 
297     OS << "File '" << Filename << "':\n";
298     OS << column("Name", FunctionReportColumns[0])
299        << column("Regions", FunctionReportColumns[1], Column::RightAlignment)
300        << column("Miss", FunctionReportColumns[2], Column::RightAlignment)
301        << column("Cover", FunctionReportColumns[3], Column::RightAlignment)
302        << column("Lines", FunctionReportColumns[4], Column::RightAlignment)
303        << column("Miss", FunctionReportColumns[5], Column::RightAlignment)
304        << column("Cover", FunctionReportColumns[6], Column::RightAlignment);
305     OS << "\n";
306     renderDivider(FunctionReportColumns, OS);
307     OS << "\n";
308     FunctionCoverageSummary Totals("TOTAL");
309     for (const auto &F : Functions) {
310       auto Function = FunctionCoverageSummary::get(Coverage, F);
311       ++Totals.ExecutionCount;
312       Totals.RegionCoverage += Function.RegionCoverage;
313       Totals.LineCoverage += Function.LineCoverage;
314       render(Function, DC, OS);
315     }
316     if (Totals.ExecutionCount) {
317       renderDivider(FunctionReportColumns, OS);
318       OS << "\n";
319       render(Totals, DC, OS);
320     }
321   }
322 }
323 
prepareSingleFileReport(const StringRef Filename,const coverage::CoverageMapping * Coverage,const CoverageViewOptions & Options,const unsigned LCP,FileCoverageSummary * FileReport,const CoverageFilter * Filters)324 void CoverageReport::prepareSingleFileReport(const StringRef Filename,
325     const coverage::CoverageMapping *Coverage,
326     const CoverageViewOptions &Options, const unsigned LCP,
327     FileCoverageSummary *FileReport, const CoverageFilter *Filters) {
328   for (const auto &Group : Coverage->getInstantiationGroups(Filename)) {
329     std::vector<FunctionCoverageSummary> InstantiationSummaries;
330     for (const coverage::FunctionRecord *F : Group.getInstantiations()) {
331       if (!Filters->matches(*Coverage, *F))
332         continue;
333       auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F);
334       FileReport->addInstantiation(InstantiationSummary);
335       InstantiationSummaries.push_back(InstantiationSummary);
336     }
337     if (InstantiationSummaries.empty())
338       continue;
339 
340     auto GroupSummary =
341         FunctionCoverageSummary::get(Group, InstantiationSummaries);
342 
343     if (Options.Debug)
344       outs() << "InstantiationGroup: " << GroupSummary.Name << " with "
345              << "size = " << Group.size() << "\n";
346 
347     FileReport->addFunction(GroupSummary);
348   }
349 }
350 
prepareFileReports(const coverage::CoverageMapping & Coverage,FileCoverageSummary & Totals,ArrayRef<std::string> Files,const CoverageViewOptions & Options,const CoverageFilter & Filters)351 std::vector<FileCoverageSummary> CoverageReport::prepareFileReports(
352     const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals,
353     ArrayRef<std::string> Files, const CoverageViewOptions &Options,
354     const CoverageFilter &Filters) {
355   unsigned LCP = getRedundantPrefixLen(Files);
356   auto NumThreads = Options.NumThreads;
357 
358   // If NumThreads is not specified, auto-detect a good default.
359   if (NumThreads == 0)
360     NumThreads =
361         std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(),
362                               unsigned(Files.size())));
363 
364   ThreadPool Pool(NumThreads);
365 
366   std::vector<FileCoverageSummary> FileReports;
367   FileReports.reserve(Files.size());
368 
369   for (StringRef Filename : Files) {
370     FileReports.emplace_back(Filename.drop_front(LCP));
371     Pool.async(&CoverageReport::prepareSingleFileReport, Filename,
372                &Coverage, Options, LCP, &FileReports.back(), &Filters);
373   }
374   Pool.wait();
375 
376   for (const auto &FileReport : FileReports)
377     Totals += FileReport;
378 
379   return FileReports;
380 }
381 
renderFileReports(raw_ostream & OS,const CoverageFilters & IgnoreFilenameFilters) const382 void CoverageReport::renderFileReports(
383     raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const {
384   std::vector<std::string> UniqueSourceFiles;
385   for (StringRef SF : Coverage.getUniqueSourceFiles()) {
386     // Apply ignore source files filters.
387     if (!IgnoreFilenameFilters.matchesFilename(SF))
388       UniqueSourceFiles.emplace_back(SF.str());
389   }
390   renderFileReports(OS, UniqueSourceFiles);
391 }
392 
renderFileReports(raw_ostream & OS,ArrayRef<std::string> Files) const393 void CoverageReport::renderFileReports(
394     raw_ostream &OS, ArrayRef<std::string> Files) const {
395   renderFileReports(OS, Files, CoverageFiltersMatchAll());
396 }
397 
renderFileReports(raw_ostream & OS,ArrayRef<std::string> Files,const CoverageFiltersMatchAll & Filters) const398 void CoverageReport::renderFileReports(
399     raw_ostream &OS, ArrayRef<std::string> Files,
400     const CoverageFiltersMatchAll &Filters) const {
401   FileCoverageSummary Totals("TOTAL");
402   auto FileReports =
403       prepareFileReports(Coverage, Totals, Files, Options, Filters);
404 
405   std::vector<StringRef> Filenames;
406   for (const FileCoverageSummary &FCS : FileReports)
407     Filenames.emplace_back(FCS.Name);
408   adjustColumnWidths(Filenames, {});
409 
410   OS << column("Filename", FileReportColumns[0]);
411   if (Options.ShowRegionSummary)
412     OS << column("Regions", FileReportColumns[1], Column::RightAlignment)
413        << column("Missed Regions", FileReportColumns[2], Column::RightAlignment)
414        << column("Cover", FileReportColumns[3], Column::RightAlignment);
415   OS << column("Functions", FileReportColumns[4], Column::RightAlignment)
416      << column("Missed Functions", FileReportColumns[5], Column::RightAlignment)
417      << column("Executed", FileReportColumns[6], Column::RightAlignment);
418   if (Options.ShowInstantiationSummary)
419     OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment)
420        << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment)
421        << column("Executed", FileReportColumns[9], Column::RightAlignment);
422   OS << column("Lines", FileReportColumns[10], Column::RightAlignment)
423      << column("Missed Lines", FileReportColumns[11], Column::RightAlignment)
424      << column("Cover", FileReportColumns[12], Column::RightAlignment) << "\n";
425   renderDivider(FileReportColumns, OS);
426   OS << "\n";
427 
428   bool EmptyFiles = false;
429   for (const FileCoverageSummary &FCS : FileReports) {
430     if (FCS.FunctionCoverage.getNumFunctions())
431       render(FCS, OS);
432     else
433       EmptyFiles = true;
434   }
435 
436   if (EmptyFiles && Filters.empty()) {
437     OS << "\n"
438        << "Files which contain no functions:\n";
439 
440     for (const FileCoverageSummary &FCS : FileReports)
441       if (!FCS.FunctionCoverage.getNumFunctions())
442         render(FCS, OS);
443   }
444 
445   renderDivider(FileReportColumns, OS);
446   OS << "\n";
447   render(Totals, OS);
448 }
449 
450 } // end namespace llvm
451