1 //===- SourceCoverageViewText.cpp - A text-based code coverage view -------===// 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 /// \file This file implements the text-based coverage renderer. 10 /// 11 //===----------------------------------------------------------------------===// 12 13 #include "SourceCoverageViewText.h" 14 #include "CoverageReport.h" 15 #include "llvm/ADT/SmallString.h" 16 #include "llvm/ADT/StringExtras.h" 17 #include "llvm/Support/FileSystem.h" 18 #include "llvm/Support/Format.h" 19 #include "llvm/Support/Path.h" 20 #include <optional> 21 22 using namespace llvm; 23 24 Expected<CoveragePrinter::OwnedStream> 25 CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) { 26 return createOutputStream(Path, "txt", InToplevel); 27 } 28 29 void CoveragePrinterText::closeViewFile(OwnedStream OS) { 30 OS->operator<<('\n'); 31 } 32 33 Error CoveragePrinterText::createIndexFile( 34 ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage, 35 const CoverageFiltersMatchAll &Filters) { 36 auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true); 37 if (Error E = OSOrErr.takeError()) 38 return E; 39 auto OS = std::move(OSOrErr.get()); 40 raw_ostream &OSRef = *OS.get(); 41 42 CoverageReport Report(Opts, Coverage); 43 Report.renderFileReports(OSRef, SourceFiles, Filters); 44 45 Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n" 46 << Opts.getLLVMVersionString(); 47 48 return Error::success(); 49 } 50 51 struct CoveragePrinterTextDirectory::Reporter : public DirectoryCoverageReport { 52 CoveragePrinterTextDirectory &Printer; 53 54 Reporter(CoveragePrinterTextDirectory &Printer, 55 const coverage::CoverageMapping &Coverage, 56 const CoverageFiltersMatchAll &Filters) 57 : DirectoryCoverageReport(Printer.Opts, Coverage, Filters), 58 Printer(Printer) {} 59 60 Error generateSubDirectoryReport(SubFileReports &&SubFiles, 61 SubDirReports &&SubDirs, 62 FileCoverageSummary &&SubTotals) override { 63 auto &LCPath = SubTotals.Name; 64 assert(Options.hasOutputDirectory() && 65 "No output directory for index file"); 66 67 SmallString<128> OSPath = LCPath; 68 sys::path::append(OSPath, "index"); 69 auto OSOrErr = Printer.createOutputStream(OSPath, "txt", 70 /*InToplevel=*/false); 71 if (auto E = OSOrErr.takeError()) 72 return E; 73 auto OS = std::move(OSOrErr.get()); 74 raw_ostream &OSRef = *OS.get(); 75 76 std::vector<FileCoverageSummary> Reports; 77 for (auto &&SubDir : SubDirs) 78 Reports.push_back(std::move(SubDir.second.first)); 79 for (auto &&SubFile : SubFiles) 80 Reports.push_back(std::move(SubFile.second)); 81 82 CoverageReport Report(Options, Coverage); 83 Report.renderFileReports(OSRef, Reports, SubTotals, Filters.empty()); 84 85 Options.colored_ostream(OSRef, raw_ostream::CYAN) 86 << "\n" 87 << Options.getLLVMVersionString(); 88 89 return Error::success(); 90 } 91 }; 92 93 Error CoveragePrinterTextDirectory::createIndexFile( 94 ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage, 95 const CoverageFiltersMatchAll &Filters) { 96 if (SourceFiles.size() <= 1) 97 return CoveragePrinterText::createIndexFile(SourceFiles, Coverage, Filters); 98 99 Reporter Report(*this, Coverage, Filters); 100 auto TotalsOrErr = Report.prepareDirectoryReports(SourceFiles); 101 if (auto E = TotalsOrErr.takeError()) 102 return E; 103 auto &LCPath = TotalsOrErr->Name; 104 105 auto TopIndexFilePath = 106 getOutputPath("index", "txt", /*InToplevel=*/true, /*Relative=*/false); 107 auto LCPIndexFilePath = 108 getOutputPath((LCPath + "index").str(), "txt", /*InToplevel=*/false, 109 /*Relative=*/false); 110 return errorCodeToError( 111 sys::fs::copy_file(LCPIndexFilePath, TopIndexFilePath)); 112 } 113 114 namespace { 115 116 static const unsigned LineCoverageColumnWidth = 7; 117 static const unsigned LineNumberColumnWidth = 5; 118 119 /// Get the width of the leading columns. 120 unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) { 121 return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) + 122 (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0); 123 } 124 125 /// The width of the line that is used to divide between the view and 126 /// the subviews. 127 unsigned getDividerWidth(const CoverageViewOptions &Opts) { 128 return getCombinedColumnWidth(Opts) + 4; 129 } 130 131 } // anonymous namespace 132 133 void SourceCoverageViewText::renderViewHeader(raw_ostream &) {} 134 135 void SourceCoverageViewText::renderViewFooter(raw_ostream &) {} 136 137 void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) { 138 getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName() 139 << ":\n"; 140 } 141 142 void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS, 143 unsigned ViewDepth) { 144 for (unsigned I = 0; I < ViewDepth; ++I) 145 OS << " |"; 146 } 147 148 void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {} 149 150 void SourceCoverageViewText::renderViewDivider(raw_ostream &OS, 151 unsigned ViewDepth) { 152 assert(ViewDepth != 0 && "Cannot render divider at top level"); 153 renderLinePrefix(OS, ViewDepth - 1); 154 OS.indent(2); 155 unsigned Length = getDividerWidth(getOptions()); 156 for (unsigned I = 0; I < Length; ++I) 157 OS << '-'; 158 OS << '\n'; 159 } 160 161 void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L, 162 const LineCoverageStats &LCS, 163 unsigned ExpansionCol, 164 unsigned ViewDepth) { 165 StringRef Line = L.Line; 166 unsigned LineNumber = L.LineNo; 167 auto *WrappedSegment = LCS.getWrappedSegment(); 168 CoverageSegmentArray Segments = LCS.getLineSegments(); 169 170 std::optional<raw_ostream::Colors> Highlight; 171 SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges; 172 173 // The first segment overlaps from a previous line, so we treat it specially. 174 if (WrappedSegment && !WrappedSegment->IsGapRegion && 175 WrappedSegment->HasCount && WrappedSegment->Count == 0) 176 Highlight = raw_ostream::RED; 177 178 // Output each segment of the line, possibly highlighted. 179 unsigned Col = 1; 180 for (const auto *S : Segments) { 181 unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1); 182 colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, 183 getOptions().Colors && Highlight, /*Bold=*/false, 184 /*BG=*/true) 185 << Line.substr(Col - 1, End - Col); 186 if (getOptions().Debug && Highlight) 187 HighlightedRanges.push_back(std::make_pair(Col, End)); 188 Col = End; 189 if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) && 190 S->HasCount && S->Count == 0) 191 Highlight = raw_ostream::RED; 192 else if (Col == ExpansionCol) 193 Highlight = raw_ostream::CYAN; 194 else 195 Highlight = std::nullopt; 196 } 197 198 // Show the rest of the line. 199 colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, 200 getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true) 201 << Line.substr(Col - 1, Line.size() - Col + 1); 202 OS << '\n'; 203 204 if (getOptions().Debug) { 205 for (const auto &Range : HighlightedRanges) 206 errs() << "Highlighted line " << LineNumber << ", " << Range.first 207 << " -> " << Range.second << '\n'; 208 if (Highlight) 209 errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n"; 210 } 211 } 212 213 void SourceCoverageViewText::renderLineCoverageColumn( 214 raw_ostream &OS, const LineCoverageStats &Line) { 215 if (!Line.isMapped()) { 216 OS.indent(LineCoverageColumnWidth) << '|'; 217 return; 218 } 219 std::string C = formatCount(Line.getExecutionCount()); 220 OS.indent(LineCoverageColumnWidth - C.size()); 221 colored_ostream(OS, raw_ostream::MAGENTA, 222 Line.hasMultipleRegions() && getOptions().Colors) 223 << C; 224 OS << '|'; 225 } 226 227 void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS, 228 unsigned LineNo) { 229 SmallString<32> Buffer; 230 raw_svector_ostream BufferOS(Buffer); 231 BufferOS << LineNo; 232 auto Str = BufferOS.str(); 233 // Trim and align to the right. 234 Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth)); 235 OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|'; 236 } 237 238 void SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS, 239 const LineCoverageStats &Line, 240 unsigned ViewDepth) { 241 renderLinePrefix(OS, ViewDepth); 242 OS.indent(getCombinedColumnWidth(getOptions())); 243 244 CoverageSegmentArray Segments = Line.getLineSegments(); 245 246 // Just consider the segments which start *and* end on this line. 247 if (Segments.size() > 1) 248 Segments = Segments.drop_back(); 249 250 unsigned PrevColumn = 1; 251 for (const auto *S : Segments) { 252 if (!S->IsRegionEntry) 253 continue; 254 if (S->Count == Line.getExecutionCount()) 255 continue; 256 // Skip to the new region. 257 if (S->Col > PrevColumn) 258 OS.indent(S->Col - PrevColumn); 259 PrevColumn = S->Col + 1; 260 std::string C = formatCount(S->Count); 261 PrevColumn += C.size(); 262 OS << '^' << C; 263 264 if (getOptions().Debug) 265 errs() << "Marker at " << S->Line << ":" << S->Col << " = " 266 << formatCount(S->Count) << "\n"; 267 } 268 OS << '\n'; 269 } 270 271 void SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L, 272 const LineCoverageStats &LCS, 273 unsigned ExpansionCol, 274 unsigned ViewDepth) { 275 renderLinePrefix(OS, ViewDepth); 276 OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1)); 277 renderLine(OS, L, LCS, ExpansionCol, ViewDepth); 278 } 279 280 void SourceCoverageViewText::renderExpansionView(raw_ostream &OS, 281 ExpansionView &ESV, 282 unsigned ViewDepth) { 283 // Render the child subview. 284 if (getOptions().Debug) 285 errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol() 286 << " -> " << ESV.getEndCol() << '\n'; 287 ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false, 288 /*ShowTitle=*/false, ViewDepth + 1); 289 } 290 291 void SourceCoverageViewText::renderBranchView(raw_ostream &OS, BranchView &BRV, 292 unsigned ViewDepth) { 293 // Render the child subview. 294 if (getOptions().Debug) 295 errs() << "Branch at line " << BRV.getLine() << '\n'; 296 297 for (const auto &R : BRV.Regions) { 298 double TruePercent = 0.0; 299 double FalsePercent = 0.0; 300 // FIXME: It may overflow when the data is too large, but I have not 301 // encountered it in actual use, and not sure whether to use __uint128_t. 302 uint64_t Total = R.ExecutionCount + R.FalseExecutionCount; 303 304 if (!getOptions().ShowBranchCounts && Total != 0) { 305 TruePercent = ((double)(R.ExecutionCount) / (double)Total) * 100.0; 306 FalsePercent = ((double)(R.FalseExecutionCount) / (double)Total) * 100.0; 307 } 308 309 renderLinePrefix(OS, ViewDepth); 310 OS << " Branch (" << R.LineStart << ":" << R.ColumnStart << "): ["; 311 312 if (R.Folded) { 313 OS << "Folded - Ignored]\n"; 314 continue; 315 } 316 317 colored_ostream(OS, raw_ostream::RED, 318 getOptions().Colors && !R.ExecutionCount, 319 /*Bold=*/false, /*BG=*/true) 320 << "True"; 321 322 if (getOptions().ShowBranchCounts) 323 OS << ": " << formatCount(R.ExecutionCount) << ", "; 324 else 325 OS << ": " << format("%0.2f", TruePercent) << "%, "; 326 327 colored_ostream(OS, raw_ostream::RED, 328 getOptions().Colors && !R.FalseExecutionCount, 329 /*Bold=*/false, /*BG=*/true) 330 << "False"; 331 332 if (getOptions().ShowBranchCounts) 333 OS << ": " << formatCount(R.FalseExecutionCount); 334 else 335 OS << ": " << format("%0.2f", FalsePercent) << "%"; 336 OS << "]\n"; 337 } 338 } 339 340 void SourceCoverageViewText::renderMCDCView(raw_ostream &OS, MCDCView &MRV, 341 unsigned ViewDepth) { 342 for (auto &Record : MRV.Records) { 343 renderLinePrefix(OS, ViewDepth); 344 OS << "---> MC/DC Decision Region ("; 345 // Display Line + Column information. 346 const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion(); 347 OS << DecisionRegion.LineStart << ":"; 348 OS << DecisionRegion.ColumnStart << ") to ("; 349 OS << DecisionRegion.LineEnd << ":"; 350 OS << DecisionRegion.ColumnEnd << ")\n"; 351 renderLinePrefix(OS, ViewDepth); 352 OS << "\n"; 353 354 // Display MC/DC Information. 355 renderLinePrefix(OS, ViewDepth); 356 OS << " Number of Conditions: " << Record.getNumConditions() << "\n"; 357 for (unsigned i = 0; i < Record.getNumConditions(); i++) { 358 renderLinePrefix(OS, ViewDepth); 359 OS << " " << Record.getConditionHeaderString(i); 360 } 361 renderLinePrefix(OS, ViewDepth); 362 OS << "\n"; 363 renderLinePrefix(OS, ViewDepth); 364 OS << " Executed MC/DC Test Vectors:\n"; 365 renderLinePrefix(OS, ViewDepth); 366 OS << "\n"; 367 renderLinePrefix(OS, ViewDepth); 368 OS << " "; 369 OS << Record.getTestVectorHeaderString(); 370 for (unsigned i = 0; i < Record.getNumTestVectors(); i++) { 371 renderLinePrefix(OS, ViewDepth); 372 OS << Record.getTestVectorString(i); 373 } 374 renderLinePrefix(OS, ViewDepth); 375 OS << "\n"; 376 for (unsigned i = 0; i < Record.getNumConditions(); i++) { 377 renderLinePrefix(OS, ViewDepth); 378 OS << Record.getConditionCoverageString(i); 379 } 380 renderLinePrefix(OS, ViewDepth); 381 OS << " MC/DC Coverage for Decision: "; 382 colored_ostream(OS, raw_ostream::RED, 383 getOptions().Colors && Record.getPercentCovered() < 100.0, 384 /*Bold=*/false, /*BG=*/true) 385 << format("%0.2f", Record.getPercentCovered()) << "%"; 386 OS << "\n"; 387 renderLinePrefix(OS, ViewDepth); 388 OS << "\n"; 389 } 390 } 391 392 void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS, 393 InstantiationView &ISV, 394 unsigned ViewDepth) { 395 renderLinePrefix(OS, ViewDepth); 396 OS << ' '; 397 if (!ISV.View) 398 getOptions().colored_ostream(OS, raw_ostream::RED) 399 << "Unexecuted instantiation: " << ISV.FunctionName << "\n"; 400 else 401 ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, 402 /*ShowTitle=*/false, ViewDepth); 403 } 404 405 void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) { 406 if (getOptions().hasProjectTitle()) 407 getOptions().colored_ostream(OS, raw_ostream::CYAN) 408 << getOptions().ProjectTitle << "\n"; 409 410 getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n"; 411 412 if (getOptions().hasCreatedTime()) 413 getOptions().colored_ostream(OS, raw_ostream::CYAN) 414 << getOptions().CreatedTimeStr << "\n"; 415 } 416 417 void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned) {} 418