llvm-cov: Simplify coverage reports, fixing PR22575 in the process
[oota-llvm.git] / tools / llvm-cov / CoverageReport.cpp
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
19 using namespace llvm;
20 namespace {
21 /// \brief Helper struct which prints trimmed and aligned columns.
22 struct 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 };
47 raw_ostream &operator<<(raw_ostream &OS, const Column &Value) {
48   Value.render(OS);
49   return OS;
50 }
51 }
52
53 void 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
78 static Column column(StringRef Str, unsigned Width) {
79   return Column(Str, Width);
80 }
81
82 template <typename T>
83 static Column column(StringRef Str, unsigned Width, const T &Value) {
84   return Column(Str, Width).set(Value);
85 }
86
87 static const unsigned FileReportColumns[] = {25, 10, 8, 8, 10, 10};
88 static const unsigned FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8};
89
90 /// \brief Prints a horizontal divider which spans across the given columns.
91 template <typename T, size_t N>
92 static 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.
102 template <typename T>
103 static 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
110 void 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
130 void 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
158 void CoverageReport::renderFunctionReports(raw_ostream &OS) {
159   bool isFirst = true;
160   for (StringRef Filename : Coverage->getUniqueSourceFiles()) {
161     if (isFirst)
162       isFirst = false;
163     else
164       OS << "\n";
165     OS << "File '" << Filename << "':\n";
166     OS << column("Name", FunctionReportColumns[0])
167        << column("Regions", FunctionReportColumns[1], Column::RightAlignment)
168        << column("Miss", FunctionReportColumns[2], Column::RightAlignment)
169        << column("Cover", FunctionReportColumns[3], Column::RightAlignment)
170        << column("Lines", FunctionReportColumns[4], Column::RightAlignment)
171        << column("Miss", FunctionReportColumns[5], Column::RightAlignment)
172        << column("Cover", FunctionReportColumns[6], Column::RightAlignment);
173     OS << "\n";
174     renderDivider(FunctionReportColumns, OS);
175     OS << "\n";
176     FunctionCoverageSummary Totals("TOTAL");
177     for (const auto &F : Coverage->getCoveredFunctions(Filename)) {
178       FunctionCoverageSummary Function = FunctionCoverageSummary::get(F);
179       ++Totals.ExecutionCount;
180       Totals.RegionCoverage += Function.RegionCoverage;
181       Totals.LineCoverage += Function.LineCoverage;
182       render(Function, OS);
183     }
184     if (Totals.ExecutionCount) {
185       renderDivider(FunctionReportColumns, OS);
186       OS << "\n";
187       render(Totals, OS);
188     }
189   }
190 }
191
192 void CoverageReport::renderFileReports(raw_ostream &OS) {
193   OS << column("Filename", FileReportColumns[0])
194      << column("Regions", FileReportColumns[1], Column::RightAlignment)
195      << column("Miss", FileReportColumns[2], Column::RightAlignment)
196      << column("Cover", FileReportColumns[3], Column::RightAlignment)
197      << column("Functions", FileReportColumns[4], Column::RightAlignment)
198      << column("Executed", FileReportColumns[5], Column::RightAlignment)
199      << "\n";
200   renderDivider(FileReportColumns, OS);
201   OS << "\n";
202   FileCoverageSummary Totals("TOTAL");
203   for (StringRef Filename : Coverage->getUniqueSourceFiles()) {
204     FileCoverageSummary Summary(Filename);
205     for (const auto &F : Coverage->getCoveredFunctions(Filename)) {
206       FunctionCoverageSummary Function = FunctionCoverageSummary::get(F);
207       Summary.addFunction(Function);
208       Totals.addFunction(Function);
209     }
210     render(Summary, OS);
211   }
212   renderDivider(FileReportColumns, OS);
213   OS << "\n";
214   render(Totals, OS);
215 }