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.value_or(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.value_or(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 = formatBinaryCount(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 << formatBinaryCount(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 auto BranchCount = [&](StringRef Label, uint64_t Count, bool Folded, 298 double Total) { 299 if (Folded) 300 return std::string{"Folded"}; 301 302 std::string Str; 303 raw_string_ostream OS(Str); 304 305 colored_ostream(OS, raw_ostream::RED, getOptions().Colors && !Count, 306 /*Bold=*/false, /*BG=*/true) 307 << Label; 308 309 if (getOptions().ShowBranchCounts) 310 OS << ": " << formatBinaryCount(Count); 311 else 312 OS << ": " << format("%0.2f", (Total != 0 ? 100.0 * Count / Total : 0.0)) 313 << "%"; 314 315 return Str; 316 }; 317 318 for (const auto &R : BRV.Regions) { 319 // This can be `double` since it is only used as a denominator. 320 // FIXME: It is still inaccurate if Count is greater than (1LL << 53). 321 double Total = 322 static_cast<double>(R.ExecutionCount) + R.FalseExecutionCount; 323 324 renderLinePrefix(OS, ViewDepth); 325 OS << " Branch (" << R.LineStart << ":" << R.ColumnStart << "): ["; 326 327 if (R.TrueFolded && R.FalseFolded) { 328 OS << "Folded - Ignored]\n"; 329 continue; 330 } 331 332 OS << BranchCount("True", R.ExecutionCount, R.TrueFolded, Total) << ", " 333 << BranchCount("False", R.FalseExecutionCount, R.FalseFolded, Total) 334 << "]\n"; 335 } 336 } 337 338 void SourceCoverageViewText::renderMCDCView(raw_ostream &OS, MCDCView &MRV, 339 unsigned ViewDepth) { 340 for (auto &Record : MRV.Records) { 341 renderLinePrefix(OS, ViewDepth); 342 OS << "---> MC/DC Decision Region ("; 343 // Display Line + Column information. 344 const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion(); 345 OS << DecisionRegion.LineStart << ":"; 346 OS << DecisionRegion.ColumnStart << ") to ("; 347 OS << DecisionRegion.LineEnd << ":"; 348 OS << DecisionRegion.ColumnEnd << ")\n"; 349 renderLinePrefix(OS, ViewDepth); 350 OS << "\n"; 351 352 // Display MC/DC Information. 353 renderLinePrefix(OS, ViewDepth); 354 OS << " Number of Conditions: " << Record.getNumConditions() << "\n"; 355 for (unsigned i = 0; i < Record.getNumConditions(); i++) { 356 renderLinePrefix(OS, ViewDepth); 357 OS << " " << Record.getConditionHeaderString(i); 358 } 359 renderLinePrefix(OS, ViewDepth); 360 OS << "\n"; 361 renderLinePrefix(OS, ViewDepth); 362 OS << " Executed MC/DC Test Vectors:\n"; 363 renderLinePrefix(OS, ViewDepth); 364 OS << "\n"; 365 renderLinePrefix(OS, ViewDepth); 366 OS << " "; 367 OS << Record.getTestVectorHeaderString(); 368 for (unsigned i = 0; i < Record.getNumTestVectors(); i++) { 369 renderLinePrefix(OS, ViewDepth); 370 OS << Record.getTestVectorString(i); 371 } 372 renderLinePrefix(OS, ViewDepth); 373 OS << "\n"; 374 for (unsigned i = 0; i < Record.getNumConditions(); i++) { 375 renderLinePrefix(OS, ViewDepth); 376 OS << Record.getConditionCoverageString(i); 377 } 378 renderLinePrefix(OS, ViewDepth); 379 OS << " MC/DC Coverage for Decision: "; 380 colored_ostream(OS, raw_ostream::RED, 381 getOptions().Colors && Record.getPercentCovered() < 100.0, 382 /*Bold=*/false, /*BG=*/true) 383 << format("%0.2f", Record.getPercentCovered()) << "%"; 384 OS << "\n"; 385 renderLinePrefix(OS, ViewDepth); 386 OS << "\n"; 387 } 388 } 389 390 void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS, 391 InstantiationView &ISV, 392 unsigned ViewDepth) { 393 renderLinePrefix(OS, ViewDepth); 394 OS << ' '; 395 if (!ISV.View) 396 getOptions().colored_ostream(OS, raw_ostream::RED) 397 << "Unexecuted instantiation: " << ISV.FunctionName << "\n"; 398 else 399 ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, 400 /*ShowTitle=*/false, ViewDepth); 401 } 402 403 void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) { 404 if (getOptions().hasProjectTitle()) 405 getOptions().colored_ostream(OS, raw_ostream::CYAN) 406 << getOptions().ProjectTitle << "\n"; 407 408 getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n"; 409 410 if (getOptions().hasCreatedTime()) 411 getOptions().colored_ostream(OS, raw_ostream::CYAN) 412 << getOptions().CreatedTimeStr << "\n"; 413 } 414 415 void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned) {} 416