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