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/ADT/DenseMap.h" 17 #include "llvm/Support/Format.h" 18 #include "llvm/Support/Path.h" 19 #include <numeric> 20 21 using namespace llvm; 22 23 namespace { 24 25 /// \brief Helper struct which prints trimmed and aligned columns. 26 struct Column { 27 enum TrimKind { NoTrim, WidthTrim, RightTrim }; 28 29 enum AlignmentKind { LeftAlignment, RightAlignment }; 30 31 StringRef Str; 32 unsigned Width; 33 TrimKind Trim; 34 AlignmentKind Alignment; 35 36 Column(StringRef Str, unsigned Width) 37 : Str(Str), Width(Width), Trim(WidthTrim), Alignment(LeftAlignment) {} 38 39 Column &set(TrimKind Value) { 40 Trim = Value; 41 return *this; 42 } 43 44 Column &set(AlignmentKind Value) { 45 Alignment = Value; 46 return *this; 47 } 48 49 void render(raw_ostream &OS) const { 50 if (Str.size() <= Width) { 51 if (Alignment == RightAlignment) { 52 OS.indent(Width - Str.size()); 53 OS << Str; 54 return; 55 } 56 OS << Str; 57 OS.indent(Width - Str.size()); 58 return; 59 } 60 61 switch (Trim) { 62 case NoTrim: 63 OS << Str; 64 break; 65 case WidthTrim: 66 OS << Str.substr(0, Width); 67 break; 68 case RightTrim: 69 OS << Str.substr(0, Width - 3) << "..."; 70 break; 71 } 72 } 73 }; 74 75 raw_ostream &operator<<(raw_ostream &OS, const Column &Value) { 76 Value.render(OS); 77 return OS; 78 } 79 80 Column column(StringRef Str, unsigned Width) { return Column(Str, Width); } 81 82 template <typename T> 83 Column column(StringRef Str, unsigned Width, const T &Value) { 84 return Column(Str, Width).set(Value); 85 } 86 87 // Specify the default column widths. 88 size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10, 89 16, 16, 10, 12, 18, 10}; 90 size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8}; 91 92 /// \brief Adjust column widths to fit long file paths and function names. 93 void adjustColumnWidths(ArrayRef<StringRef> Files, 94 ArrayRef<StringRef> Functions) { 95 for (StringRef Filename : Files) 96 FileReportColumns[0] = std::max(FileReportColumns[0], Filename.size()); 97 for (StringRef Funcname : Functions) 98 FunctionReportColumns[0] = 99 std::max(FunctionReportColumns[0], Funcname.size()); 100 } 101 102 /// \brief Prints a horizontal divider long enough to cover the given column 103 /// widths. 104 void renderDivider(ArrayRef<size_t> ColumnWidths, raw_ostream &OS) { 105 size_t Length = std::accumulate(ColumnWidths.begin(), ColumnWidths.end(), 0); 106 for (size_t I = 0; I < Length; ++I) 107 OS << '-'; 108 } 109 110 /// \brief Return the color which correponds to the coverage percentage of a 111 /// certain metric. 112 template <typename T> 113 raw_ostream::Colors determineCoveragePercentageColor(const T &Info) { 114 if (Info.isFullyCovered()) 115 return raw_ostream::GREEN; 116 return Info.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW 117 : raw_ostream::RED; 118 } 119 120 /// \brief Get the number of redundant path components in each path in \p Paths. 121 unsigned getNumRedundantPathComponents(ArrayRef<std::string> Paths) { 122 // To start, set the number of redundant path components to the maximum 123 // possible value. 124 SmallVector<StringRef, 8> FirstPathComponents{sys::path::begin(Paths[0]), 125 sys::path::end(Paths[0])}; 126 unsigned NumRedundant = FirstPathComponents.size(); 127 128 for (unsigned I = 1, E = Paths.size(); NumRedundant > 0 && I < E; ++I) { 129 StringRef Path = Paths[I]; 130 for (const auto &Component : 131 enumerate(make_range(sys::path::begin(Path), sys::path::end(Path)))) { 132 // Do not increase the number of redundant components: that would remove 133 // useful parts of already-visited paths. 134 if (Component.index() >= NumRedundant) 135 break; 136 137 // Lower the number of redundant components when there's a mismatch 138 // between the first path, and the path under consideration. 139 if (FirstPathComponents[Component.index()] != Component.value()) { 140 NumRedundant = Component.index(); 141 break; 142 } 143 } 144 } 145 146 return NumRedundant; 147 } 148 149 /// \brief Determine the length of the longest redundant prefix of the paths in 150 /// \p Paths. 151 unsigned getRedundantPrefixLen(ArrayRef<std::string> Paths) { 152 // If there's at most one path, no path components are redundant. 153 if (Paths.size() <= 1) 154 return 0; 155 156 unsigned PrefixLen = 0; 157 unsigned NumRedundant = getNumRedundantPathComponents(Paths); 158 auto Component = sys::path::begin(Paths[0]); 159 for (unsigned I = 0; I < NumRedundant; ++I) { 160 auto LastComponent = Component; 161 ++Component; 162 PrefixLen += Component - LastComponent; 163 } 164 return PrefixLen; 165 } 166 167 } // end anonymous namespace 168 169 namespace llvm { 170 171 void CoverageReport::render(const FileCoverageSummary &File, 172 raw_ostream &OS) const { 173 auto FileCoverageColor = 174 determineCoveragePercentageColor(File.RegionCoverage); 175 auto FuncCoverageColor = 176 determineCoveragePercentageColor(File.FunctionCoverage); 177 auto InstantiationCoverageColor = 178 determineCoveragePercentageColor(File.InstantiationCoverage); 179 auto LineCoverageColor = determineCoveragePercentageColor(File.LineCoverage); 180 SmallString<256> FileName = File.Name; 181 sys::path::remove_dots(FileName, /*remove_dot_dots=*/true); 182 sys::path::native(FileName); 183 OS << column(FileName, FileReportColumns[0], Column::NoTrim); 184 185 if (Options.ShowRegionSummary) { 186 OS << format("%*u", FileReportColumns[1], 187 (unsigned)File.RegionCoverage.getNumRegions()); 188 Options.colored_ostream(OS, FileCoverageColor) 189 << format("%*u", FileReportColumns[2], 190 (unsigned)(File.RegionCoverage.getNumRegions() - 191 File.RegionCoverage.getCovered())); 192 if (File.RegionCoverage.getNumRegions()) 193 Options.colored_ostream(OS, FileCoverageColor) 194 << format("%*.2f", FileReportColumns[3] - 1, 195 File.RegionCoverage.getPercentCovered()) 196 << '%'; 197 else 198 OS << column("-", FileReportColumns[3], Column::RightAlignment); 199 } 200 201 OS << format("%*u", FileReportColumns[4], 202 (unsigned)File.FunctionCoverage.getNumFunctions()); 203 OS << format("%*u", FileReportColumns[5], 204 (unsigned)(File.FunctionCoverage.getNumFunctions() - 205 File.FunctionCoverage.getExecuted())); 206 if (File.FunctionCoverage.getNumFunctions()) 207 Options.colored_ostream(OS, FuncCoverageColor) 208 << format("%*.2f", FileReportColumns[6] - 1, 209 File.FunctionCoverage.getPercentCovered()) 210 << '%'; 211 else 212 OS << column("-", FileReportColumns[6], Column::RightAlignment); 213 214 if (Options.ShowInstantiationSummary) { 215 OS << format("%*u", FileReportColumns[7], 216 (unsigned)File.InstantiationCoverage.getNumFunctions()); 217 OS << format("%*u", FileReportColumns[8], 218 (unsigned)(File.InstantiationCoverage.getNumFunctions() - 219 File.InstantiationCoverage.getExecuted())); 220 if (File.InstantiationCoverage.getNumFunctions()) 221 Options.colored_ostream(OS, InstantiationCoverageColor) 222 << format("%*.2f", FileReportColumns[9] - 1, 223 File.InstantiationCoverage.getPercentCovered()) 224 << '%'; 225 else 226 OS << column("-", FileReportColumns[9], Column::RightAlignment); 227 } 228 229 OS << format("%*u", FileReportColumns[10], 230 (unsigned)File.LineCoverage.getNumLines()); 231 Options.colored_ostream(OS, LineCoverageColor) << format( 232 "%*u", FileReportColumns[11], (unsigned)(File.LineCoverage.getNumLines() - 233 File.LineCoverage.getCovered())); 234 if (File.LineCoverage.getNumLines()) 235 Options.colored_ostream(OS, LineCoverageColor) 236 << format("%*.2f", FileReportColumns[12] - 1, 237 File.LineCoverage.getPercentCovered()) 238 << '%'; 239 else 240 OS << column("-", FileReportColumns[12], Column::RightAlignment); 241 OS << "\n"; 242 } 243 244 void CoverageReport::render(const FunctionCoverageSummary &Function, 245 const DemangleCache &DC, 246 raw_ostream &OS) const { 247 auto FuncCoverageColor = 248 determineCoveragePercentageColor(Function.RegionCoverage); 249 auto LineCoverageColor = 250 determineCoveragePercentageColor(Function.LineCoverage); 251 OS << column(DC.demangle(Function.Name), FunctionReportColumns[0], 252 Column::RightTrim) 253 << format("%*u", FunctionReportColumns[1], 254 (unsigned)Function.RegionCoverage.getNumRegions()); 255 Options.colored_ostream(OS, FuncCoverageColor) 256 << format("%*u", FunctionReportColumns[2], 257 (unsigned)(Function.RegionCoverage.getNumRegions() - 258 Function.RegionCoverage.getCovered())); 259 Options.colored_ostream( 260 OS, determineCoveragePercentageColor(Function.RegionCoverage)) 261 << format("%*.2f", FunctionReportColumns[3] - 1, 262 Function.RegionCoverage.getPercentCovered()) 263 << '%'; 264 OS << format("%*u", FunctionReportColumns[4], 265 (unsigned)Function.LineCoverage.getNumLines()); 266 Options.colored_ostream(OS, LineCoverageColor) 267 << format("%*u", FunctionReportColumns[5], 268 (unsigned)(Function.LineCoverage.getNumLines() - 269 Function.LineCoverage.getCovered())); 270 Options.colored_ostream( 271 OS, determineCoveragePercentageColor(Function.LineCoverage)) 272 << format("%*.2f", FunctionReportColumns[6] - 1, 273 Function.LineCoverage.getPercentCovered()) 274 << '%'; 275 OS << "\n"; 276 } 277 278 void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files, 279 const DemangleCache &DC, 280 raw_ostream &OS) { 281 bool isFirst = true; 282 for (StringRef Filename : Files) { 283 auto Functions = Coverage.getCoveredFunctions(Filename); 284 285 if (isFirst) 286 isFirst = false; 287 else 288 OS << "\n"; 289 290 std::vector<StringRef> Funcnames; 291 for (const auto &F : Functions) 292 Funcnames.emplace_back(DC.demangle(F.Name)); 293 adjustColumnWidths({}, Funcnames); 294 295 OS << "File '" << Filename << "':\n"; 296 OS << column("Name", FunctionReportColumns[0]) 297 << column("Regions", FunctionReportColumns[1], Column::RightAlignment) 298 << column("Miss", FunctionReportColumns[2], Column::RightAlignment) 299 << column("Cover", FunctionReportColumns[3], Column::RightAlignment) 300 << column("Lines", FunctionReportColumns[4], Column::RightAlignment) 301 << column("Miss", FunctionReportColumns[5], Column::RightAlignment) 302 << column("Cover", FunctionReportColumns[6], Column::RightAlignment); 303 OS << "\n"; 304 renderDivider(FunctionReportColumns, OS); 305 OS << "\n"; 306 FunctionCoverageSummary Totals("TOTAL"); 307 for (const auto &F : Functions) { 308 auto Function = FunctionCoverageSummary::get(Coverage, F); 309 ++Totals.ExecutionCount; 310 Totals.RegionCoverage += Function.RegionCoverage; 311 Totals.LineCoverage += Function.LineCoverage; 312 render(Function, DC, OS); 313 } 314 if (Totals.ExecutionCount) { 315 renderDivider(FunctionReportColumns, OS); 316 OS << "\n"; 317 render(Totals, DC, OS); 318 } 319 } 320 } 321 322 std::vector<FileCoverageSummary> CoverageReport::prepareFileReports( 323 const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals, 324 ArrayRef<std::string> Files, const CoverageViewOptions &Options, 325 const CoverageFilter &Filters) { 326 std::vector<FileCoverageSummary> FileReports; 327 unsigned LCP = getRedundantPrefixLen(Files); 328 329 for (StringRef Filename : Files) { 330 FileCoverageSummary Summary(Filename.drop_front(LCP)); 331 332 for (const auto &Group : Coverage.getInstantiationGroups(Filename)) { 333 std::vector<FunctionCoverageSummary> InstantiationSummaries; 334 for (const coverage::FunctionRecord *F : Group.getInstantiations()) { 335 if (!Filters.matches(Coverage, *F)) 336 continue; 337 auto InstantiationSummary = FunctionCoverageSummary::get(Coverage, *F); 338 Summary.addInstantiation(InstantiationSummary); 339 Totals.addInstantiation(InstantiationSummary); 340 InstantiationSummaries.push_back(InstantiationSummary); 341 } 342 if (InstantiationSummaries.empty()) 343 continue; 344 345 auto GroupSummary = 346 FunctionCoverageSummary::get(Group, InstantiationSummaries); 347 348 if (Options.Debug) 349 outs() << "InstantiationGroup: " << GroupSummary.Name << " with " 350 << "size = " << Group.size() << "\n"; 351 352 Summary.addFunction(GroupSummary); 353 Totals.addFunction(GroupSummary); 354 } 355 356 FileReports.push_back(Summary); 357 } 358 359 return FileReports; 360 } 361 362 void CoverageReport::renderFileReports(raw_ostream &OS) const { 363 std::vector<std::string> UniqueSourceFiles; 364 for (StringRef SF : Coverage.getUniqueSourceFiles()) 365 UniqueSourceFiles.emplace_back(SF.str()); 366 renderFileReports(OS, UniqueSourceFiles); 367 } 368 369 void CoverageReport::renderFileReports( 370 raw_ostream &OS, ArrayRef<std::string> Files) const { 371 renderFileReports(OS, Files, CoverageFiltersMatchAll()); 372 } 373 374 void CoverageReport::renderFileReports( 375 raw_ostream &OS, ArrayRef<std::string> Files, 376 const CoverageFiltersMatchAll &Filters) const { 377 FileCoverageSummary Totals("TOTAL"); 378 auto FileReports = 379 prepareFileReports(Coverage, Totals, Files, Options, Filters); 380 381 std::vector<StringRef> Filenames; 382 for (const FileCoverageSummary &FCS : FileReports) 383 Filenames.emplace_back(FCS.Name); 384 adjustColumnWidths(Filenames, {}); 385 386 OS << column("Filename", FileReportColumns[0]); 387 if (Options.ShowRegionSummary) 388 OS << column("Regions", FileReportColumns[1], Column::RightAlignment) 389 << column("Missed Regions", FileReportColumns[2], Column::RightAlignment) 390 << column("Cover", FileReportColumns[3], Column::RightAlignment); 391 OS << column("Functions", FileReportColumns[4], Column::RightAlignment) 392 << column("Missed Functions", FileReportColumns[5], Column::RightAlignment) 393 << column("Executed", FileReportColumns[6], Column::RightAlignment); 394 if (Options.ShowInstantiationSummary) 395 OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment) 396 << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment) 397 << column("Executed", FileReportColumns[9], Column::RightAlignment); 398 OS << column("Lines", FileReportColumns[10], Column::RightAlignment) 399 << column("Missed Lines", FileReportColumns[11], Column::RightAlignment) 400 << column("Cover", FileReportColumns[12], Column::RightAlignment) << "\n"; 401 renderDivider(FileReportColumns, OS); 402 OS << "\n"; 403 404 bool EmptyFiles = false; 405 for (const FileCoverageSummary &FCS : FileReports) { 406 if (FCS.FunctionCoverage.getNumFunctions()) 407 render(FCS, OS); 408 else 409 EmptyFiles = true; 410 } 411 412 if (EmptyFiles && Filters.empty()) { 413 OS << "\n" 414 << "Files which contain no functions:\n"; 415 416 for (const FileCoverageSummary &FCS : FileReports) 417 if (!FCS.FunctionCoverage.getNumFunctions()) 418 render(FCS, OS); 419 } 420 421 renderDivider(FileReportColumns, OS); 422 OS << "\n"; 423 render(Totals, OS); 424 } 425 426 } // end namespace llvm 427