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