1*0b57cec5SDimitry Andric //===- SourceCoverageViewText.cpp - A text-based code coverage view -------===// 2*0b57cec5SDimitry Andric // 3*0b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*0b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 5*0b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*0b57cec5SDimitry Andric // 7*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 8*0b57cec5SDimitry Andric /// 9*0b57cec5SDimitry Andric /// \file This file implements the text-based coverage renderer. 10*0b57cec5SDimitry Andric /// 11*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 12*0b57cec5SDimitry Andric 13*0b57cec5SDimitry Andric #include "CoverageReport.h" 14*0b57cec5SDimitry Andric #include "SourceCoverageViewText.h" 15*0b57cec5SDimitry Andric #include "llvm/ADT/Optional.h" 16*0b57cec5SDimitry Andric #include "llvm/ADT/SmallString.h" 17*0b57cec5SDimitry Andric #include "llvm/ADT/StringExtras.h" 18*0b57cec5SDimitry Andric 19*0b57cec5SDimitry Andric using namespace llvm; 20*0b57cec5SDimitry Andric 21*0b57cec5SDimitry Andric Expected<CoveragePrinter::OwnedStream> 22*0b57cec5SDimitry Andric CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) { 23*0b57cec5SDimitry Andric return createOutputStream(Path, "txt", InToplevel); 24*0b57cec5SDimitry Andric } 25*0b57cec5SDimitry Andric 26*0b57cec5SDimitry Andric void CoveragePrinterText::closeViewFile(OwnedStream OS) { 27*0b57cec5SDimitry Andric OS->operator<<('\n'); 28*0b57cec5SDimitry Andric } 29*0b57cec5SDimitry Andric 30*0b57cec5SDimitry Andric Error CoveragePrinterText::createIndexFile( 31*0b57cec5SDimitry Andric ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage, 32*0b57cec5SDimitry Andric const CoverageFiltersMatchAll &Filters) { 33*0b57cec5SDimitry Andric auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true); 34*0b57cec5SDimitry Andric if (Error E = OSOrErr.takeError()) 35*0b57cec5SDimitry Andric return E; 36*0b57cec5SDimitry Andric auto OS = std::move(OSOrErr.get()); 37*0b57cec5SDimitry Andric raw_ostream &OSRef = *OS.get(); 38*0b57cec5SDimitry Andric 39*0b57cec5SDimitry Andric CoverageReport Report(Opts, Coverage); 40*0b57cec5SDimitry Andric Report.renderFileReports(OSRef, SourceFiles, Filters); 41*0b57cec5SDimitry Andric 42*0b57cec5SDimitry Andric Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n" 43*0b57cec5SDimitry Andric << Opts.getLLVMVersionString(); 44*0b57cec5SDimitry Andric 45*0b57cec5SDimitry Andric return Error::success(); 46*0b57cec5SDimitry Andric } 47*0b57cec5SDimitry Andric 48*0b57cec5SDimitry Andric namespace { 49*0b57cec5SDimitry Andric 50*0b57cec5SDimitry Andric static const unsigned LineCoverageColumnWidth = 7; 51*0b57cec5SDimitry Andric static const unsigned LineNumberColumnWidth = 5; 52*0b57cec5SDimitry Andric 53*0b57cec5SDimitry Andric /// Get the width of the leading columns. 54*0b57cec5SDimitry Andric unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) { 55*0b57cec5SDimitry Andric return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) + 56*0b57cec5SDimitry Andric (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0); 57*0b57cec5SDimitry Andric } 58*0b57cec5SDimitry Andric 59*0b57cec5SDimitry Andric /// The width of the line that is used to divide between the view and 60*0b57cec5SDimitry Andric /// the subviews. 61*0b57cec5SDimitry Andric unsigned getDividerWidth(const CoverageViewOptions &Opts) { 62*0b57cec5SDimitry Andric return getCombinedColumnWidth(Opts) + 4; 63*0b57cec5SDimitry Andric } 64*0b57cec5SDimitry Andric 65*0b57cec5SDimitry Andric } // anonymous namespace 66*0b57cec5SDimitry Andric 67*0b57cec5SDimitry Andric void SourceCoverageViewText::renderViewHeader(raw_ostream &) {} 68*0b57cec5SDimitry Andric 69*0b57cec5SDimitry Andric void SourceCoverageViewText::renderViewFooter(raw_ostream &) {} 70*0b57cec5SDimitry Andric 71*0b57cec5SDimitry Andric void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) { 72*0b57cec5SDimitry Andric getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName() 73*0b57cec5SDimitry Andric << ":\n"; 74*0b57cec5SDimitry Andric } 75*0b57cec5SDimitry Andric 76*0b57cec5SDimitry Andric void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS, 77*0b57cec5SDimitry Andric unsigned ViewDepth) { 78*0b57cec5SDimitry Andric for (unsigned I = 0; I < ViewDepth; ++I) 79*0b57cec5SDimitry Andric OS << " |"; 80*0b57cec5SDimitry Andric } 81*0b57cec5SDimitry Andric 82*0b57cec5SDimitry Andric void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {} 83*0b57cec5SDimitry Andric 84*0b57cec5SDimitry Andric void SourceCoverageViewText::renderViewDivider(raw_ostream &OS, 85*0b57cec5SDimitry Andric unsigned ViewDepth) { 86*0b57cec5SDimitry Andric assert(ViewDepth != 0 && "Cannot render divider at top level"); 87*0b57cec5SDimitry Andric renderLinePrefix(OS, ViewDepth - 1); 88*0b57cec5SDimitry Andric OS.indent(2); 89*0b57cec5SDimitry Andric unsigned Length = getDividerWidth(getOptions()); 90*0b57cec5SDimitry Andric for (unsigned I = 0; I < Length; ++I) 91*0b57cec5SDimitry Andric OS << '-'; 92*0b57cec5SDimitry Andric OS << '\n'; 93*0b57cec5SDimitry Andric } 94*0b57cec5SDimitry Andric 95*0b57cec5SDimitry Andric void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L, 96*0b57cec5SDimitry Andric const LineCoverageStats &LCS, 97*0b57cec5SDimitry Andric unsigned ExpansionCol, 98*0b57cec5SDimitry Andric unsigned ViewDepth) { 99*0b57cec5SDimitry Andric StringRef Line = L.Line; 100*0b57cec5SDimitry Andric unsigned LineNumber = L.LineNo; 101*0b57cec5SDimitry Andric auto *WrappedSegment = LCS.getWrappedSegment(); 102*0b57cec5SDimitry Andric CoverageSegmentArray Segments = LCS.getLineSegments(); 103*0b57cec5SDimitry Andric 104*0b57cec5SDimitry Andric Optional<raw_ostream::Colors> Highlight; 105*0b57cec5SDimitry Andric SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges; 106*0b57cec5SDimitry Andric 107*0b57cec5SDimitry Andric // The first segment overlaps from a previous line, so we treat it specially. 108*0b57cec5SDimitry Andric if (WrappedSegment && !WrappedSegment->IsGapRegion && 109*0b57cec5SDimitry Andric WrappedSegment->HasCount && WrappedSegment->Count == 0) 110*0b57cec5SDimitry Andric Highlight = raw_ostream::RED; 111*0b57cec5SDimitry Andric 112*0b57cec5SDimitry Andric // Output each segment of the line, possibly highlighted. 113*0b57cec5SDimitry Andric unsigned Col = 1; 114*0b57cec5SDimitry Andric for (const auto *S : Segments) { 115*0b57cec5SDimitry Andric unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1); 116*0b57cec5SDimitry Andric colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, 117*0b57cec5SDimitry Andric getOptions().Colors && Highlight, /*Bold=*/false, 118*0b57cec5SDimitry Andric /*BG=*/true) 119*0b57cec5SDimitry Andric << Line.substr(Col - 1, End - Col); 120*0b57cec5SDimitry Andric if (getOptions().Debug && Highlight) 121*0b57cec5SDimitry Andric HighlightedRanges.push_back(std::make_pair(Col, End)); 122*0b57cec5SDimitry Andric Col = End; 123*0b57cec5SDimitry Andric if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) && 124*0b57cec5SDimitry Andric S->HasCount && S->Count == 0) 125*0b57cec5SDimitry Andric Highlight = raw_ostream::RED; 126*0b57cec5SDimitry Andric else if (Col == ExpansionCol) 127*0b57cec5SDimitry Andric Highlight = raw_ostream::CYAN; 128*0b57cec5SDimitry Andric else 129*0b57cec5SDimitry Andric Highlight = None; 130*0b57cec5SDimitry Andric } 131*0b57cec5SDimitry Andric 132*0b57cec5SDimitry Andric // Show the rest of the line. 133*0b57cec5SDimitry Andric colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, 134*0b57cec5SDimitry Andric getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true) 135*0b57cec5SDimitry Andric << Line.substr(Col - 1, Line.size() - Col + 1); 136*0b57cec5SDimitry Andric OS << '\n'; 137*0b57cec5SDimitry Andric 138*0b57cec5SDimitry Andric if (getOptions().Debug) { 139*0b57cec5SDimitry Andric for (const auto &Range : HighlightedRanges) 140*0b57cec5SDimitry Andric errs() << "Highlighted line " << LineNumber << ", " << Range.first 141*0b57cec5SDimitry Andric << " -> " << Range.second << '\n'; 142*0b57cec5SDimitry Andric if (Highlight) 143*0b57cec5SDimitry Andric errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n"; 144*0b57cec5SDimitry Andric } 145*0b57cec5SDimitry Andric } 146*0b57cec5SDimitry Andric 147*0b57cec5SDimitry Andric void SourceCoverageViewText::renderLineCoverageColumn( 148*0b57cec5SDimitry Andric raw_ostream &OS, const LineCoverageStats &Line) { 149*0b57cec5SDimitry Andric if (!Line.isMapped()) { 150*0b57cec5SDimitry Andric OS.indent(LineCoverageColumnWidth) << '|'; 151*0b57cec5SDimitry Andric return; 152*0b57cec5SDimitry Andric } 153*0b57cec5SDimitry Andric std::string C = formatCount(Line.getExecutionCount()); 154*0b57cec5SDimitry Andric OS.indent(LineCoverageColumnWidth - C.size()); 155*0b57cec5SDimitry Andric colored_ostream(OS, raw_ostream::MAGENTA, 156*0b57cec5SDimitry Andric Line.hasMultipleRegions() && getOptions().Colors) 157*0b57cec5SDimitry Andric << C; 158*0b57cec5SDimitry Andric OS << '|'; 159*0b57cec5SDimitry Andric } 160*0b57cec5SDimitry Andric 161*0b57cec5SDimitry Andric void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS, 162*0b57cec5SDimitry Andric unsigned LineNo) { 163*0b57cec5SDimitry Andric SmallString<32> Buffer; 164*0b57cec5SDimitry Andric raw_svector_ostream BufferOS(Buffer); 165*0b57cec5SDimitry Andric BufferOS << LineNo; 166*0b57cec5SDimitry Andric auto Str = BufferOS.str(); 167*0b57cec5SDimitry Andric // Trim and align to the right. 168*0b57cec5SDimitry Andric Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth)); 169*0b57cec5SDimitry Andric OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|'; 170*0b57cec5SDimitry Andric } 171*0b57cec5SDimitry Andric 172*0b57cec5SDimitry Andric void SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS, 173*0b57cec5SDimitry Andric const LineCoverageStats &Line, 174*0b57cec5SDimitry Andric unsigned ViewDepth) { 175*0b57cec5SDimitry Andric renderLinePrefix(OS, ViewDepth); 176*0b57cec5SDimitry Andric OS.indent(getCombinedColumnWidth(getOptions())); 177*0b57cec5SDimitry Andric 178*0b57cec5SDimitry Andric CoverageSegmentArray Segments = Line.getLineSegments(); 179*0b57cec5SDimitry Andric 180*0b57cec5SDimitry Andric // Just consider the segments which start *and* end on this line. 181*0b57cec5SDimitry Andric if (Segments.size() > 1) 182*0b57cec5SDimitry Andric Segments = Segments.drop_back(); 183*0b57cec5SDimitry Andric 184*0b57cec5SDimitry Andric unsigned PrevColumn = 1; 185*0b57cec5SDimitry Andric for (const auto *S : Segments) { 186*0b57cec5SDimitry Andric if (!S->IsRegionEntry) 187*0b57cec5SDimitry Andric continue; 188*0b57cec5SDimitry Andric if (S->Count == Line.getExecutionCount()) 189*0b57cec5SDimitry Andric continue; 190*0b57cec5SDimitry Andric // Skip to the new region. 191*0b57cec5SDimitry Andric if (S->Col > PrevColumn) 192*0b57cec5SDimitry Andric OS.indent(S->Col - PrevColumn); 193*0b57cec5SDimitry Andric PrevColumn = S->Col + 1; 194*0b57cec5SDimitry Andric std::string C = formatCount(S->Count); 195*0b57cec5SDimitry Andric PrevColumn += C.size(); 196*0b57cec5SDimitry Andric OS << '^' << C; 197*0b57cec5SDimitry Andric 198*0b57cec5SDimitry Andric if (getOptions().Debug) 199*0b57cec5SDimitry Andric errs() << "Marker at " << S->Line << ":" << S->Col << " = " 200*0b57cec5SDimitry Andric << formatCount(S->Count) << "\n"; 201*0b57cec5SDimitry Andric } 202*0b57cec5SDimitry Andric OS << '\n'; 203*0b57cec5SDimitry Andric } 204*0b57cec5SDimitry Andric 205*0b57cec5SDimitry Andric void SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L, 206*0b57cec5SDimitry Andric const LineCoverageStats &LCS, 207*0b57cec5SDimitry Andric unsigned ExpansionCol, 208*0b57cec5SDimitry Andric unsigned ViewDepth) { 209*0b57cec5SDimitry Andric renderLinePrefix(OS, ViewDepth); 210*0b57cec5SDimitry Andric OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1)); 211*0b57cec5SDimitry Andric renderLine(OS, L, LCS, ExpansionCol, ViewDepth); 212*0b57cec5SDimitry Andric } 213*0b57cec5SDimitry Andric 214*0b57cec5SDimitry Andric void SourceCoverageViewText::renderExpansionView(raw_ostream &OS, 215*0b57cec5SDimitry Andric ExpansionView &ESV, 216*0b57cec5SDimitry Andric unsigned ViewDepth) { 217*0b57cec5SDimitry Andric // Render the child subview. 218*0b57cec5SDimitry Andric if (getOptions().Debug) 219*0b57cec5SDimitry Andric errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol() 220*0b57cec5SDimitry Andric << " -> " << ESV.getEndCol() << '\n'; 221*0b57cec5SDimitry Andric ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false, 222*0b57cec5SDimitry Andric /*ShowTitle=*/false, ViewDepth + 1); 223*0b57cec5SDimitry Andric } 224*0b57cec5SDimitry Andric 225*0b57cec5SDimitry Andric void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS, 226*0b57cec5SDimitry Andric InstantiationView &ISV, 227*0b57cec5SDimitry Andric unsigned ViewDepth) { 228*0b57cec5SDimitry Andric renderLinePrefix(OS, ViewDepth); 229*0b57cec5SDimitry Andric OS << ' '; 230*0b57cec5SDimitry Andric if (!ISV.View) 231*0b57cec5SDimitry Andric getOptions().colored_ostream(OS, raw_ostream::RED) 232*0b57cec5SDimitry Andric << "Unexecuted instantiation: " << ISV.FunctionName << "\n"; 233*0b57cec5SDimitry Andric else 234*0b57cec5SDimitry Andric ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, 235*0b57cec5SDimitry Andric /*ShowTitle=*/false, ViewDepth); 236*0b57cec5SDimitry Andric } 237*0b57cec5SDimitry Andric 238*0b57cec5SDimitry Andric void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) { 239*0b57cec5SDimitry Andric if (getOptions().hasProjectTitle()) 240*0b57cec5SDimitry Andric getOptions().colored_ostream(OS, raw_ostream::CYAN) 241*0b57cec5SDimitry Andric << getOptions().ProjectTitle << "\n"; 242*0b57cec5SDimitry Andric 243*0b57cec5SDimitry Andric getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n"; 244*0b57cec5SDimitry Andric 245*0b57cec5SDimitry Andric if (getOptions().hasCreatedTime()) 246*0b57cec5SDimitry Andric getOptions().colored_ostream(OS, raw_ostream::CYAN) 247*0b57cec5SDimitry Andric << getOptions().CreatedTimeStr << "\n"; 248*0b57cec5SDimitry Andric } 249*0b57cec5SDimitry Andric 250*0b57cec5SDimitry Andric void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned, 251*0b57cec5SDimitry Andric unsigned) {} 252