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