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, 90 16, 16, 10, 12, 18, 10}; 91 size_t FunctionReportColumns[] = {25, 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_dots=*/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 OS << "\n"; 243 } 244 245 void CoverageReport::render(const FunctionCoverageSummary &Function, 246 const DemangleCache &DC, 247 raw_ostream &OS) const { 248 auto FuncCoverageColor = 249 determineCoveragePercentageColor(Function.RegionCoverage); 250 auto LineCoverageColor = 251 determineCoveragePercentageColor(Function.LineCoverage); 252 OS << column(DC.demangle(Function.Name), FunctionReportColumns[0], 253 Column::RightTrim) 254 << format("%*u", FunctionReportColumns[1], 255 (unsigned)Function.RegionCoverage.getNumRegions()); 256 Options.colored_ostream(OS, FuncCoverageColor) 257 << format("%*u", FunctionReportColumns[2], 258 (unsigned)(Function.RegionCoverage.getNumRegions() - 259 Function.RegionCoverage.getCovered())); 260 Options.colored_ostream( 261 OS, determineCoveragePercentageColor(Function.RegionCoverage)) 262 << format("%*.2f", FunctionReportColumns[3] - 1, 263 Function.RegionCoverage.getPercentCovered()) 264 << '%'; 265 OS << format("%*u", FunctionReportColumns[4], 266 (unsigned)Function.LineCoverage.getNumLines()); 267 Options.colored_ostream(OS, LineCoverageColor) 268 << format("%*u", FunctionReportColumns[5], 269 (unsigned)(Function.LineCoverage.getNumLines() - 270 Function.LineCoverage.getCovered())); 271 Options.colored_ostream( 272 OS, determineCoveragePercentageColor(Function.LineCoverage)) 273 << format("%*.2f", FunctionReportColumns[6] - 1, 274 Function.LineCoverage.getPercentCovered()) 275 << '%'; 276 OS << "\n"; 277 } 278 279 void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files, 280 const DemangleCache &DC, 281 raw_ostream &OS) { 282 bool isFirst = true; 283 for (StringRef Filename : Files) { 284 auto Functions = Coverage.getCoveredFunctions(Filename); 285 286 if (isFirst) 287 isFirst = false; 288 else 289 OS << "\n"; 290 291 std::vector<StringRef> Funcnames; 292 for (const auto &F : Functions) 293 Funcnames.emplace_back(DC.demangle(F.Name)); 294 adjustColumnWidths({}, Funcnames); 295 296 OS << "File '" << Filename << "':\n"; 297 OS << column("Name", FunctionReportColumns[0]) 298 << column("Regions", FunctionReportColumns[1], Column::RightAlignment) 299 << column("Miss", FunctionReportColumns[2], Column::RightAlignment) 300 << column("Cover", FunctionReportColumns[3], Column::RightAlignment) 301 << column("Lines", FunctionReportColumns[4], Column::RightAlignment) 302 << column("Miss", FunctionReportColumns[5], Column::RightAlignment) 303 << column("Cover", FunctionReportColumns[6], Column::RightAlignment); 304 OS << "\n"; 305 renderDivider(FunctionReportColumns, OS); 306 OS << "\n"; 307 FunctionCoverageSummary Totals("TOTAL"); 308 for (const auto &F : Functions) { 309 auto Function = FunctionCoverageSummary::get(Coverage, F); 310 ++Totals.ExecutionCount; 311 Totals.RegionCoverage += Function.RegionCoverage; 312 Totals.LineCoverage += Function.LineCoverage; 313 render(Function, DC, OS); 314 } 315 if (Totals.ExecutionCount) { 316 renderDivider(FunctionReportColumns, OS); 317 OS << "\n"; 318 render(Totals, DC, OS); 319 } 320 } 321 } 322 323 void CoverageReport::prepareSingleFileReport(const StringRef Filename, 324 const coverage::CoverageMapping *Coverage, 325 const CoverageViewOptions &Options, const unsigned LCP, 326 FileCoverageSummary *FileReport, const CoverageFilter *Filters) { 327 for (const auto &Group : Coverage->getInstantiationGroups(Filename)) { 328 std::vector<FunctionCoverageSummary> InstantiationSummaries; 329 for (const coverage::FunctionRecord *F : Group.getInstantiations()) { 330 if (!Filters->matches(*Coverage, *F)) 331 continue; 332 auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F); 333 FileReport->addInstantiation(InstantiationSummary); 334 InstantiationSummaries.push_back(InstantiationSummary); 335 } 336 if (InstantiationSummaries.empty()) 337 continue; 338 339 auto GroupSummary = 340 FunctionCoverageSummary::get(Group, InstantiationSummaries); 341 342 if (Options.Debug) 343 outs() << "InstantiationGroup: " << GroupSummary.Name << " with " 344 << "size = " << Group.size() << "\n"; 345 346 FileReport->addFunction(GroupSummary); 347 } 348 } 349 350 std::vector<FileCoverageSummary> CoverageReport::prepareFileReports( 351 const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals, 352 ArrayRef<std::string> Files, const CoverageViewOptions &Options, 353 const CoverageFilter &Filters) { 354 unsigned LCP = getRedundantPrefixLen(Files); 355 356 ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads); 357 if (Options.NumThreads == 0) { 358 // If NumThreads is not specified, create one thread for each input, up to 359 // the number of hardware cores. 360 S = heavyweight_hardware_concurrency(Files.size()); 361 S.Limit = true; 362 } 363 ThreadPool Pool(S); 364 365 std::vector<FileCoverageSummary> FileReports; 366 FileReports.reserve(Files.size()); 367 368 for (StringRef Filename : Files) { 369 FileReports.emplace_back(Filename.drop_front(LCP)); 370 Pool.async(&CoverageReport::prepareSingleFileReport, Filename, 371 &Coverage, Options, LCP, &FileReports.back(), &Filters); 372 } 373 Pool.wait(); 374 375 for (const auto &FileReport : FileReports) 376 Totals += FileReport; 377 378 return FileReports; 379 } 380 381 void CoverageReport::renderFileReports( 382 raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const { 383 std::vector<std::string> UniqueSourceFiles; 384 for (StringRef SF : Coverage.getUniqueSourceFiles()) { 385 // Apply ignore source files filters. 386 if (!IgnoreFilenameFilters.matchesFilename(SF)) 387 UniqueSourceFiles.emplace_back(SF.str()); 388 } 389 renderFileReports(OS, UniqueSourceFiles); 390 } 391 392 void CoverageReport::renderFileReports( 393 raw_ostream &OS, ArrayRef<std::string> Files) const { 394 renderFileReports(OS, Files, CoverageFiltersMatchAll()); 395 } 396 397 void CoverageReport::renderFileReports( 398 raw_ostream &OS, ArrayRef<std::string> Files, 399 const CoverageFiltersMatchAll &Filters) const { 400 FileCoverageSummary Totals("TOTAL"); 401 auto FileReports = 402 prepareFileReports(Coverage, Totals, Files, Options, Filters); 403 404 std::vector<StringRef> Filenames; 405 for (const FileCoverageSummary &FCS : FileReports) 406 Filenames.emplace_back(FCS.Name); 407 adjustColumnWidths(Filenames, {}); 408 409 OS << column("Filename", FileReportColumns[0]); 410 if (Options.ShowRegionSummary) 411 OS << column("Regions", FileReportColumns[1], Column::RightAlignment) 412 << column("Missed Regions", FileReportColumns[2], Column::RightAlignment) 413 << column("Cover", FileReportColumns[3], Column::RightAlignment); 414 OS << column("Functions", FileReportColumns[4], Column::RightAlignment) 415 << column("Missed Functions", FileReportColumns[5], Column::RightAlignment) 416 << column("Executed", FileReportColumns[6], Column::RightAlignment); 417 if (Options.ShowInstantiationSummary) 418 OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment) 419 << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment) 420 << column("Executed", FileReportColumns[9], Column::RightAlignment); 421 OS << column("Lines", FileReportColumns[10], Column::RightAlignment) 422 << column("Missed Lines", FileReportColumns[11], Column::RightAlignment) 423 << column("Cover", FileReportColumns[12], Column::RightAlignment) << "\n"; 424 renderDivider(FileReportColumns, OS); 425 OS << "\n"; 426 427 bool EmptyFiles = false; 428 for (const FileCoverageSummary &FCS : FileReports) { 429 if (FCS.FunctionCoverage.getNumFunctions()) 430 render(FCS, OS); 431 else 432 EmptyFiles = true; 433 } 434 435 if (EmptyFiles && Filters.empty()) { 436 OS << "\n" 437 << "Files which contain no functions:\n"; 438 439 for (const FileCoverageSummary &FCS : FileReports) 440 if (!FCS.FunctionCoverage.getNumFunctions()) 441 render(FCS, OS); 442 } 443 444 renderDivider(FileReportColumns, OS); 445 OS << "\n"; 446 render(Totals, OS); 447 } 448 449 } // end namespace llvm 450