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