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/Optional.h" 16 #include "llvm/ADT/SmallString.h" 17 #include "llvm/ADT/StringExtras.h" 18 #include "llvm/Support/Format.h" 19 #include <optional> 20 21 using namespace llvm; 22 23 Expected<CoveragePrinter::OwnedStream> 24 CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) { 25 return createOutputStream(Path, "txt", InToplevel); 26 } 27 28 void CoveragePrinterText::closeViewFile(OwnedStream OS) { 29 OS->operator<<('\n'); 30 } 31 32 Error CoveragePrinterText::createIndexFile( 33 ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage, 34 const CoverageFiltersMatchAll &Filters) { 35 auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true); 36 if (Error E = OSOrErr.takeError()) 37 return E; 38 auto OS = std::move(OSOrErr.get()); 39 raw_ostream &OSRef = *OS.get(); 40 41 CoverageReport Report(Opts, Coverage); 42 Report.renderFileReports(OSRef, SourceFiles, Filters); 43 44 Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n" 45 << Opts.getLLVMVersionString(); 46 47 return Error::success(); 48 } 49 50 namespace { 51 52 static const unsigned LineCoverageColumnWidth = 7; 53 static const unsigned LineNumberColumnWidth = 5; 54 55 /// Get the width of the leading columns. 56 unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) { 57 return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) + 58 (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0); 59 } 60 61 /// The width of the line that is used to divide between the view and 62 /// the subviews. 63 unsigned getDividerWidth(const CoverageViewOptions &Opts) { 64 return getCombinedColumnWidth(Opts) + 4; 65 } 66 67 } // anonymous namespace 68 69 void SourceCoverageViewText::renderViewHeader(raw_ostream &) {} 70 71 void SourceCoverageViewText::renderViewFooter(raw_ostream &) {} 72 73 void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) { 74 getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName() 75 << ":\n"; 76 } 77 78 void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS, 79 unsigned ViewDepth) { 80 for (unsigned I = 0; I < ViewDepth; ++I) 81 OS << " |"; 82 } 83 84 void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {} 85 86 void SourceCoverageViewText::renderViewDivider(raw_ostream &OS, 87 unsigned ViewDepth) { 88 assert(ViewDepth != 0 && "Cannot render divider at top level"); 89 renderLinePrefix(OS, ViewDepth - 1); 90 OS.indent(2); 91 unsigned Length = getDividerWidth(getOptions()); 92 for (unsigned I = 0; I < Length; ++I) 93 OS << '-'; 94 OS << '\n'; 95 } 96 97 void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L, 98 const LineCoverageStats &LCS, 99 unsigned ExpansionCol, 100 unsigned ViewDepth) { 101 StringRef Line = L.Line; 102 unsigned LineNumber = L.LineNo; 103 auto *WrappedSegment = LCS.getWrappedSegment(); 104 CoverageSegmentArray Segments = LCS.getLineSegments(); 105 106 std::optional<raw_ostream::Colors> Highlight; 107 SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges; 108 109 // The first segment overlaps from a previous line, so we treat it specially. 110 if (WrappedSegment && !WrappedSegment->IsGapRegion && 111 WrappedSegment->HasCount && WrappedSegment->Count == 0) 112 Highlight = raw_ostream::RED; 113 114 // Output each segment of the line, possibly highlighted. 115 unsigned Col = 1; 116 for (const auto *S : Segments) { 117 unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1); 118 colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, 119 getOptions().Colors && Highlight, /*Bold=*/false, 120 /*BG=*/true) 121 << Line.substr(Col - 1, End - Col); 122 if (getOptions().Debug && Highlight) 123 HighlightedRanges.push_back(std::make_pair(Col, End)); 124 Col = End; 125 if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) && 126 S->HasCount && S->Count == 0) 127 Highlight = raw_ostream::RED; 128 else if (Col == ExpansionCol) 129 Highlight = raw_ostream::CYAN; 130 else 131 Highlight = std::nullopt; 132 } 133 134 // Show the rest of the line. 135 colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, 136 getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true) 137 << Line.substr(Col - 1, Line.size() - Col + 1); 138 OS << '\n'; 139 140 if (getOptions().Debug) { 141 for (const auto &Range : HighlightedRanges) 142 errs() << "Highlighted line " << LineNumber << ", " << Range.first 143 << " -> " << Range.second << '\n'; 144 if (Highlight) 145 errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n"; 146 } 147 } 148 149 void SourceCoverageViewText::renderLineCoverageColumn( 150 raw_ostream &OS, const LineCoverageStats &Line) { 151 if (!Line.isMapped()) { 152 OS.indent(LineCoverageColumnWidth) << '|'; 153 return; 154 } 155 std::string C = formatCount(Line.getExecutionCount()); 156 OS.indent(LineCoverageColumnWidth - C.size()); 157 colored_ostream(OS, raw_ostream::MAGENTA, 158 Line.hasMultipleRegions() && getOptions().Colors) 159 << C; 160 OS << '|'; 161 } 162 163 void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS, 164 unsigned LineNo) { 165 SmallString<32> Buffer; 166 raw_svector_ostream BufferOS(Buffer); 167 BufferOS << LineNo; 168 auto Str = BufferOS.str(); 169 // Trim and align to the right. 170 Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth)); 171 OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|'; 172 } 173 174 void SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS, 175 const LineCoverageStats &Line, 176 unsigned ViewDepth) { 177 renderLinePrefix(OS, ViewDepth); 178 OS.indent(getCombinedColumnWidth(getOptions())); 179 180 CoverageSegmentArray Segments = Line.getLineSegments(); 181 182 // Just consider the segments which start *and* end on this line. 183 if (Segments.size() > 1) 184 Segments = Segments.drop_back(); 185 186 unsigned PrevColumn = 1; 187 for (const auto *S : Segments) { 188 if (!S->IsRegionEntry) 189 continue; 190 if (S->Count == Line.getExecutionCount()) 191 continue; 192 // Skip to the new region. 193 if (S->Col > PrevColumn) 194 OS.indent(S->Col - PrevColumn); 195 PrevColumn = S->Col + 1; 196 std::string C = formatCount(S->Count); 197 PrevColumn += C.size(); 198 OS << '^' << C; 199 200 if (getOptions().Debug) 201 errs() << "Marker at " << S->Line << ":" << S->Col << " = " 202 << formatCount(S->Count) << "\n"; 203 } 204 OS << '\n'; 205 } 206 207 void SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L, 208 const LineCoverageStats &LCS, 209 unsigned ExpansionCol, 210 unsigned ViewDepth) { 211 renderLinePrefix(OS, ViewDepth); 212 OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1)); 213 renderLine(OS, L, LCS, ExpansionCol, ViewDepth); 214 } 215 216 void SourceCoverageViewText::renderExpansionView(raw_ostream &OS, 217 ExpansionView &ESV, 218 unsigned ViewDepth) { 219 // Render the child subview. 220 if (getOptions().Debug) 221 errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol() 222 << " -> " << ESV.getEndCol() << '\n'; 223 ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false, 224 /*ShowTitle=*/false, ViewDepth + 1); 225 } 226 227 void SourceCoverageViewText::renderBranchView(raw_ostream &OS, BranchView &BRV, 228 unsigned ViewDepth) { 229 // Render the child subview. 230 if (getOptions().Debug) 231 errs() << "Branch at line " << BRV.getLine() << '\n'; 232 233 for (const auto &R : BRV.Regions) { 234 double TruePercent = 0.0; 235 double FalsePercent = 0.0; 236 unsigned Total = R.ExecutionCount + R.FalseExecutionCount; 237 238 if (!getOptions().ShowBranchCounts && Total != 0) { 239 TruePercent = ((double)(R.ExecutionCount) / (double)Total) * 100.0; 240 FalsePercent = ((double)(R.FalseExecutionCount) / (double)Total) * 100.0; 241 } 242 243 renderLinePrefix(OS, ViewDepth); 244 OS << " Branch (" << R.LineStart << ":" << R.ColumnStart << "): ["; 245 246 if (R.Folded) { 247 OS << "Folded - Ignored]\n"; 248 continue; 249 } 250 251 colored_ostream(OS, raw_ostream::RED, 252 getOptions().Colors && !R.ExecutionCount, 253 /*Bold=*/false, /*BG=*/true) 254 << "True"; 255 256 if (getOptions().ShowBranchCounts) 257 OS << ": " << formatCount(R.ExecutionCount) << ", "; 258 else 259 OS << ": " << format("%0.2f", TruePercent) << "%, "; 260 261 colored_ostream(OS, raw_ostream::RED, 262 getOptions().Colors && !R.FalseExecutionCount, 263 /*Bold=*/false, /*BG=*/true) 264 << "False"; 265 266 if (getOptions().ShowBranchCounts) 267 OS << ": " << formatCount(R.FalseExecutionCount); 268 else 269 OS << ": " << format("%0.2f", FalsePercent) << "%"; 270 OS << "]\n"; 271 } 272 } 273 274 void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS, 275 InstantiationView &ISV, 276 unsigned ViewDepth) { 277 renderLinePrefix(OS, ViewDepth); 278 OS << ' '; 279 if (!ISV.View) 280 getOptions().colored_ostream(OS, raw_ostream::RED) 281 << "Unexecuted instantiation: " << ISV.FunctionName << "\n"; 282 else 283 ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, 284 /*ShowTitle=*/false, ViewDepth); 285 } 286 287 void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) { 288 if (getOptions().hasProjectTitle()) 289 getOptions().colored_ostream(OS, raw_ostream::CYAN) 290 << getOptions().ProjectTitle << "\n"; 291 292 getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n"; 293 294 if (getOptions().hasCreatedTime()) 295 getOptions().colored_ostream(OS, raw_ostream::CYAN) 296 << getOptions().CreatedTimeStr << "\n"; 297 } 298 299 void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned, 300 unsigned) {} 301