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/Support/FileSystem.h"
17#include "llvm/Support/Format.h"
18
19using namespace llvm;
20namespace {
21/// \brief Helper struct which prints trimmed and aligned columns.
22struct Column {
23  enum TrimKind { NoTrim, LeftTrim, RightTrim };
24
25  enum AlignmentKind { LeftAlignment, RightAlignment };
26
27  StringRef Str;
28  unsigned Width;
29  TrimKind Trim;
30  AlignmentKind Alignment;
31
32  Column(StringRef Str, unsigned Width)
33      : Str(Str), Width(Width), Trim(NoTrim), Alignment(LeftAlignment) {}
34
35  Column &set(TrimKind Value) {
36    Trim = Value;
37    return *this;
38  }
39
40  Column &set(AlignmentKind Value) {
41    Alignment = Value;
42    return *this;
43  }
44
45  void render(raw_ostream &OS) const;
46};
47raw_ostream &operator<<(raw_ostream &OS, const Column &Value) {
48  Value.render(OS);
49  return OS;
50}
51}
52
53void Column::render(raw_ostream &OS) const {
54  if (Str.size() <= Width) {
55    if (Alignment == RightAlignment) {
56      OS.indent(Width - Str.size());
57      OS << Str;
58      return;
59    }
60    OS << Str;
61    OS.indent(Width - Str.size());
62    return;
63  }
64
65  switch (Trim) {
66  case NoTrim:
67    OS << Str.substr(0, Width);
68    break;
69  case LeftTrim:
70    OS << "..." << Str.substr(Str.size() - Width + 3);
71    break;
72  case RightTrim:
73    OS << Str.substr(0, Width - 3) << "...";
74    break;
75  }
76}
77
78static Column column(StringRef Str, unsigned Width) {
79  return Column(Str, Width);
80}
81
82template <typename T>
83static Column column(StringRef Str, unsigned Width, const T &Value) {
84  return Column(Str, Width).set(Value);
85}
86
87static const unsigned FileReportColumns[] = {25, 10, 8, 8, 10, 10};
88static const unsigned FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8};
89
90/// \brief Prints a horizontal divider which spans across the given columns.
91template <typename T, size_t N>
92static void renderDivider(T (&Columns)[N], raw_ostream &OS) {
93  unsigned Length = 0;
94  for (unsigned I = 0; I < N; ++I)
95    Length += Columns[I];
96  for (unsigned I = 0; I < Length; ++I)
97    OS << '-';
98}
99
100/// \brief Return the color which correponds to the coverage
101/// percentage of a certain metric.
102template <typename T>
103static raw_ostream::Colors determineCoveragePercentageColor(const T &Info) {
104  if (Info.isFullyCovered())
105    return raw_ostream::GREEN;
106  return Info.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW
107                                          : raw_ostream::RED;
108}
109
110void CoverageReport::render(const FileCoverageSummary &File, raw_ostream &OS) {
111  OS << column(File.Name, FileReportColumns[0], Column::LeftTrim)
112     << format("%*u", FileReportColumns[1], (unsigned)File.RegionCoverage.NumRegions);
113  Options.colored_ostream(OS, File.RegionCoverage.isFullyCovered()
114                                  ? raw_ostream::GREEN
115                                  : raw_ostream::RED)
116      << format("%*u", FileReportColumns[2], (unsigned)File.RegionCoverage.NotCovered);
117  Options.colored_ostream(OS,
118                          determineCoveragePercentageColor(File.RegionCoverage))
119      << format("%*.2f", FileReportColumns[3] - 1,
120                File.RegionCoverage.getPercentCovered()) << '%';
121  OS << format("%*u", FileReportColumns[4],
122               (unsigned)File.FunctionCoverage.NumFunctions);
123  Options.colored_ostream(
124      OS, determineCoveragePercentageColor(File.FunctionCoverage))
125      << format("%*.2f", FileReportColumns[5] - 1,
126                File.FunctionCoverage.getPercentCovered()) << '%';
127  OS << "\n";
128}
129
130void CoverageReport::render(const FunctionCoverageSummary &Function,
131                            raw_ostream &OS) {
132  OS << column(Function.Name, FunctionReportColumns[0], Column::RightTrim)
133     << format("%*u", FunctionReportColumns[1],
134               (unsigned)Function.RegionCoverage.NumRegions);
135  Options.colored_ostream(OS, Function.RegionCoverage.isFullyCovered()
136                                  ? raw_ostream::GREEN
137                                  : raw_ostream::RED)
138      << format("%*u", FunctionReportColumns[2],
139                (unsigned)Function.RegionCoverage.NotCovered);
140  Options.colored_ostream(
141      OS, determineCoveragePercentageColor(Function.RegionCoverage))
142      << format("%*.2f", FunctionReportColumns[3] - 1,
143                Function.RegionCoverage.getPercentCovered()) << '%';
144  OS << format("%*u", FunctionReportColumns[4],
145               (unsigned)Function.LineCoverage.NumLines);
146  Options.colored_ostream(OS, Function.LineCoverage.isFullyCovered()
147                                  ? raw_ostream::GREEN
148                                  : raw_ostream::RED)
149      << format("%*u", FunctionReportColumns[5],
150                (unsigned)Function.LineCoverage.NotCovered);
151  Options.colored_ostream(
152      OS, determineCoveragePercentageColor(Function.LineCoverage))
153      << format("%*.2f", FunctionReportColumns[6] - 1,
154                Function.LineCoverage.getPercentCovered()) << '%';
155  OS << "\n";
156}
157
158void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
159                                           raw_ostream &OS) {
160  bool isFirst = true;
161  for (StringRef Filename : Files) {
162    if (isFirst)
163      isFirst = false;
164    else
165      OS << "\n";
166    OS << "File '" << Filename << "':\n";
167    OS << column("Name", FunctionReportColumns[0])
168       << column("Regions", FunctionReportColumns[1], Column::RightAlignment)
169       << column("Miss", FunctionReportColumns[2], Column::RightAlignment)
170       << column("Cover", FunctionReportColumns[3], Column::RightAlignment)
171       << column("Lines", FunctionReportColumns[4], Column::RightAlignment)
172       << column("Miss", FunctionReportColumns[5], Column::RightAlignment)
173       << column("Cover", FunctionReportColumns[6], Column::RightAlignment);
174    OS << "\n";
175    renderDivider(FunctionReportColumns, OS);
176    OS << "\n";
177    FunctionCoverageSummary Totals("TOTAL");
178    for (const auto &F : Coverage->getCoveredFunctions(Filename)) {
179      FunctionCoverageSummary Function = FunctionCoverageSummary::get(F);
180      ++Totals.ExecutionCount;
181      Totals.RegionCoverage += Function.RegionCoverage;
182      Totals.LineCoverage += Function.LineCoverage;
183      render(Function, OS);
184    }
185    if (Totals.ExecutionCount) {
186      renderDivider(FunctionReportColumns, OS);
187      OS << "\n";
188      render(Totals, OS);
189    }
190  }
191}
192
193void CoverageReport::renderFileReports(raw_ostream &OS) {
194  OS << column("Filename", FileReportColumns[0])
195     << column("Regions", FileReportColumns[1], Column::RightAlignment)
196     << column("Miss", FileReportColumns[2], Column::RightAlignment)
197     << column("Cover", FileReportColumns[3], Column::RightAlignment)
198     << column("Functions", FileReportColumns[4], Column::RightAlignment)
199     << column("Executed", FileReportColumns[5], Column::RightAlignment)
200     << "\n";
201  renderDivider(FileReportColumns, OS);
202  OS << "\n";
203  FileCoverageSummary Totals("TOTAL");
204  for (StringRef Filename : Coverage->getUniqueSourceFiles()) {
205    FileCoverageSummary Summary(Filename);
206    for (const auto &F : Coverage->getCoveredFunctions(Filename)) {
207      FunctionCoverageSummary Function = FunctionCoverageSummary::get(F);
208      Summary.addFunction(Function);
209      Totals.addFunction(Function);
210    }
211    render(Summary, OS);
212  }
213  renderDivider(FileReportColumns, OS);
214  OS << "\n";
215  render(Totals, OS);
216}
217