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