10b57cec5SDimitry Andric //===- SourceCoverageViewText.cpp - A text-based code coverage view -------===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric /// 90b57cec5SDimitry Andric /// \file This file implements the text-based coverage renderer. 100b57cec5SDimitry Andric /// 110b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 120b57cec5SDimitry Andric 130b57cec5SDimitry Andric #include "SourceCoverageViewText.h" 14*e8d8bef9SDimitry Andric #include "CoverageReport.h" 150b57cec5SDimitry Andric #include "llvm/ADT/Optional.h" 160b57cec5SDimitry Andric #include "llvm/ADT/SmallString.h" 170b57cec5SDimitry Andric #include "llvm/ADT/StringExtras.h" 18*e8d8bef9SDimitry Andric #include "llvm/Support/Format.h" 190b57cec5SDimitry Andric 200b57cec5SDimitry Andric using namespace llvm; 210b57cec5SDimitry Andric 220b57cec5SDimitry Andric Expected<CoveragePrinter::OwnedStream> 230b57cec5SDimitry Andric CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) { 240b57cec5SDimitry Andric return createOutputStream(Path, "txt", InToplevel); 250b57cec5SDimitry Andric } 260b57cec5SDimitry Andric 270b57cec5SDimitry Andric void CoveragePrinterText::closeViewFile(OwnedStream OS) { 280b57cec5SDimitry Andric OS->operator<<('\n'); 290b57cec5SDimitry Andric } 300b57cec5SDimitry Andric 310b57cec5SDimitry Andric Error CoveragePrinterText::createIndexFile( 320b57cec5SDimitry Andric ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage, 330b57cec5SDimitry Andric const CoverageFiltersMatchAll &Filters) { 340b57cec5SDimitry Andric auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true); 350b57cec5SDimitry Andric if (Error E = OSOrErr.takeError()) 360b57cec5SDimitry Andric return E; 370b57cec5SDimitry Andric auto OS = std::move(OSOrErr.get()); 380b57cec5SDimitry Andric raw_ostream &OSRef = *OS.get(); 390b57cec5SDimitry Andric 400b57cec5SDimitry Andric CoverageReport Report(Opts, Coverage); 410b57cec5SDimitry Andric Report.renderFileReports(OSRef, SourceFiles, Filters); 420b57cec5SDimitry Andric 430b57cec5SDimitry Andric Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n" 440b57cec5SDimitry Andric << Opts.getLLVMVersionString(); 450b57cec5SDimitry Andric 460b57cec5SDimitry Andric return Error::success(); 470b57cec5SDimitry Andric } 480b57cec5SDimitry Andric 490b57cec5SDimitry Andric namespace { 500b57cec5SDimitry Andric 510b57cec5SDimitry Andric static const unsigned LineCoverageColumnWidth = 7; 520b57cec5SDimitry Andric static const unsigned LineNumberColumnWidth = 5; 530b57cec5SDimitry Andric 540b57cec5SDimitry Andric /// Get the width of the leading columns. 550b57cec5SDimitry Andric unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) { 560b57cec5SDimitry Andric return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) + 570b57cec5SDimitry Andric (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0); 580b57cec5SDimitry Andric } 590b57cec5SDimitry Andric 600b57cec5SDimitry Andric /// The width of the line that is used to divide between the view and 610b57cec5SDimitry Andric /// the subviews. 620b57cec5SDimitry Andric unsigned getDividerWidth(const CoverageViewOptions &Opts) { 630b57cec5SDimitry Andric return getCombinedColumnWidth(Opts) + 4; 640b57cec5SDimitry Andric } 650b57cec5SDimitry Andric 660b57cec5SDimitry Andric } // anonymous namespace 670b57cec5SDimitry Andric 680b57cec5SDimitry Andric void SourceCoverageViewText::renderViewHeader(raw_ostream &) {} 690b57cec5SDimitry Andric 700b57cec5SDimitry Andric void SourceCoverageViewText::renderViewFooter(raw_ostream &) {} 710b57cec5SDimitry Andric 720b57cec5SDimitry Andric void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) { 730b57cec5SDimitry Andric getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName() 740b57cec5SDimitry Andric << ":\n"; 750b57cec5SDimitry Andric } 760b57cec5SDimitry Andric 770b57cec5SDimitry Andric void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS, 780b57cec5SDimitry Andric unsigned ViewDepth) { 790b57cec5SDimitry Andric for (unsigned I = 0; I < ViewDepth; ++I) 800b57cec5SDimitry Andric OS << " |"; 810b57cec5SDimitry Andric } 820b57cec5SDimitry Andric 830b57cec5SDimitry Andric void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {} 840b57cec5SDimitry Andric 850b57cec5SDimitry Andric void SourceCoverageViewText::renderViewDivider(raw_ostream &OS, 860b57cec5SDimitry Andric unsigned ViewDepth) { 870b57cec5SDimitry Andric assert(ViewDepth != 0 && "Cannot render divider at top level"); 880b57cec5SDimitry Andric renderLinePrefix(OS, ViewDepth - 1); 890b57cec5SDimitry Andric OS.indent(2); 900b57cec5SDimitry Andric unsigned Length = getDividerWidth(getOptions()); 910b57cec5SDimitry Andric for (unsigned I = 0; I < Length; ++I) 920b57cec5SDimitry Andric OS << '-'; 930b57cec5SDimitry Andric OS << '\n'; 940b57cec5SDimitry Andric } 950b57cec5SDimitry Andric 960b57cec5SDimitry Andric void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L, 970b57cec5SDimitry Andric const LineCoverageStats &LCS, 980b57cec5SDimitry Andric unsigned ExpansionCol, 990b57cec5SDimitry Andric unsigned ViewDepth) { 1000b57cec5SDimitry Andric StringRef Line = L.Line; 1010b57cec5SDimitry Andric unsigned LineNumber = L.LineNo; 1020b57cec5SDimitry Andric auto *WrappedSegment = LCS.getWrappedSegment(); 1030b57cec5SDimitry Andric CoverageSegmentArray Segments = LCS.getLineSegments(); 1040b57cec5SDimitry Andric 1050b57cec5SDimitry Andric Optional<raw_ostream::Colors> Highlight; 1060b57cec5SDimitry Andric SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges; 1070b57cec5SDimitry Andric 1080b57cec5SDimitry Andric // The first segment overlaps from a previous line, so we treat it specially. 1090b57cec5SDimitry Andric if (WrappedSegment && !WrappedSegment->IsGapRegion && 1100b57cec5SDimitry Andric WrappedSegment->HasCount && WrappedSegment->Count == 0) 1110b57cec5SDimitry Andric Highlight = raw_ostream::RED; 1120b57cec5SDimitry Andric 1130b57cec5SDimitry Andric // Output each segment of the line, possibly highlighted. 1140b57cec5SDimitry Andric unsigned Col = 1; 1150b57cec5SDimitry Andric for (const auto *S : Segments) { 1160b57cec5SDimitry Andric unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1); 1170b57cec5SDimitry Andric colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, 1180b57cec5SDimitry Andric getOptions().Colors && Highlight, /*Bold=*/false, 1190b57cec5SDimitry Andric /*BG=*/true) 1200b57cec5SDimitry Andric << Line.substr(Col - 1, End - Col); 1210b57cec5SDimitry Andric if (getOptions().Debug && Highlight) 1220b57cec5SDimitry Andric HighlightedRanges.push_back(std::make_pair(Col, End)); 1230b57cec5SDimitry Andric Col = End; 1240b57cec5SDimitry Andric if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) && 1250b57cec5SDimitry Andric S->HasCount && S->Count == 0) 1260b57cec5SDimitry Andric Highlight = raw_ostream::RED; 1270b57cec5SDimitry Andric else if (Col == ExpansionCol) 1280b57cec5SDimitry Andric Highlight = raw_ostream::CYAN; 1290b57cec5SDimitry Andric else 1300b57cec5SDimitry Andric Highlight = None; 1310b57cec5SDimitry Andric } 1320b57cec5SDimitry Andric 1330b57cec5SDimitry Andric // Show the rest of the line. 1340b57cec5SDimitry Andric colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, 1350b57cec5SDimitry Andric getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true) 1360b57cec5SDimitry Andric << Line.substr(Col - 1, Line.size() - Col + 1); 1370b57cec5SDimitry Andric OS << '\n'; 1380b57cec5SDimitry Andric 1390b57cec5SDimitry Andric if (getOptions().Debug) { 1400b57cec5SDimitry Andric for (const auto &Range : HighlightedRanges) 1410b57cec5SDimitry Andric errs() << "Highlighted line " << LineNumber << ", " << Range.first 1420b57cec5SDimitry Andric << " -> " << Range.second << '\n'; 1430b57cec5SDimitry Andric if (Highlight) 1440b57cec5SDimitry Andric errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n"; 1450b57cec5SDimitry Andric } 1460b57cec5SDimitry Andric } 1470b57cec5SDimitry Andric 1480b57cec5SDimitry Andric void SourceCoverageViewText::renderLineCoverageColumn( 1490b57cec5SDimitry Andric raw_ostream &OS, const LineCoverageStats &Line) { 1500b57cec5SDimitry Andric if (!Line.isMapped()) { 1510b57cec5SDimitry Andric OS.indent(LineCoverageColumnWidth) << '|'; 1520b57cec5SDimitry Andric return; 1530b57cec5SDimitry Andric } 1540b57cec5SDimitry Andric std::string C = formatCount(Line.getExecutionCount()); 1550b57cec5SDimitry Andric OS.indent(LineCoverageColumnWidth - C.size()); 1560b57cec5SDimitry Andric colored_ostream(OS, raw_ostream::MAGENTA, 1570b57cec5SDimitry Andric Line.hasMultipleRegions() && getOptions().Colors) 1580b57cec5SDimitry Andric << C; 1590b57cec5SDimitry Andric OS << '|'; 1600b57cec5SDimitry Andric } 1610b57cec5SDimitry Andric 1620b57cec5SDimitry Andric void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS, 1630b57cec5SDimitry Andric unsigned LineNo) { 1640b57cec5SDimitry Andric SmallString<32> Buffer; 1650b57cec5SDimitry Andric raw_svector_ostream BufferOS(Buffer); 1660b57cec5SDimitry Andric BufferOS << LineNo; 1670b57cec5SDimitry Andric auto Str = BufferOS.str(); 1680b57cec5SDimitry Andric // Trim and align to the right. 1690b57cec5SDimitry Andric Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth)); 1700b57cec5SDimitry Andric OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|'; 1710b57cec5SDimitry Andric } 1720b57cec5SDimitry Andric 1730b57cec5SDimitry Andric void SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS, 1740b57cec5SDimitry Andric const LineCoverageStats &Line, 1750b57cec5SDimitry Andric unsigned ViewDepth) { 1760b57cec5SDimitry Andric renderLinePrefix(OS, ViewDepth); 1770b57cec5SDimitry Andric OS.indent(getCombinedColumnWidth(getOptions())); 1780b57cec5SDimitry Andric 1790b57cec5SDimitry Andric CoverageSegmentArray Segments = Line.getLineSegments(); 1800b57cec5SDimitry Andric 1810b57cec5SDimitry Andric // Just consider the segments which start *and* end on this line. 1820b57cec5SDimitry Andric if (Segments.size() > 1) 1830b57cec5SDimitry Andric Segments = Segments.drop_back(); 1840b57cec5SDimitry Andric 1850b57cec5SDimitry Andric unsigned PrevColumn = 1; 1860b57cec5SDimitry Andric for (const auto *S : Segments) { 1870b57cec5SDimitry Andric if (!S->IsRegionEntry) 1880b57cec5SDimitry Andric continue; 1890b57cec5SDimitry Andric if (S->Count == Line.getExecutionCount()) 1900b57cec5SDimitry Andric continue; 1910b57cec5SDimitry Andric // Skip to the new region. 1920b57cec5SDimitry Andric if (S->Col > PrevColumn) 1930b57cec5SDimitry Andric OS.indent(S->Col - PrevColumn); 1940b57cec5SDimitry Andric PrevColumn = S->Col + 1; 1950b57cec5SDimitry Andric std::string C = formatCount(S->Count); 1960b57cec5SDimitry Andric PrevColumn += C.size(); 1970b57cec5SDimitry Andric OS << '^' << C; 1980b57cec5SDimitry Andric 1990b57cec5SDimitry Andric if (getOptions().Debug) 2000b57cec5SDimitry Andric errs() << "Marker at " << S->Line << ":" << S->Col << " = " 2010b57cec5SDimitry Andric << formatCount(S->Count) << "\n"; 2020b57cec5SDimitry Andric } 2030b57cec5SDimitry Andric OS << '\n'; 2040b57cec5SDimitry Andric } 2050b57cec5SDimitry Andric 2060b57cec5SDimitry Andric void SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L, 2070b57cec5SDimitry Andric const LineCoverageStats &LCS, 2080b57cec5SDimitry Andric unsigned ExpansionCol, 2090b57cec5SDimitry Andric unsigned ViewDepth) { 2100b57cec5SDimitry Andric renderLinePrefix(OS, ViewDepth); 2110b57cec5SDimitry Andric OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1)); 2120b57cec5SDimitry Andric renderLine(OS, L, LCS, ExpansionCol, ViewDepth); 2130b57cec5SDimitry Andric } 2140b57cec5SDimitry Andric 2150b57cec5SDimitry Andric void SourceCoverageViewText::renderExpansionView(raw_ostream &OS, 2160b57cec5SDimitry Andric ExpansionView &ESV, 2170b57cec5SDimitry Andric unsigned ViewDepth) { 2180b57cec5SDimitry Andric // Render the child subview. 2190b57cec5SDimitry Andric if (getOptions().Debug) 2200b57cec5SDimitry Andric errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol() 2210b57cec5SDimitry Andric << " -> " << ESV.getEndCol() << '\n'; 2220b57cec5SDimitry Andric ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false, 2230b57cec5SDimitry Andric /*ShowTitle=*/false, ViewDepth + 1); 2240b57cec5SDimitry Andric } 2250b57cec5SDimitry Andric 226*e8d8bef9SDimitry Andric void SourceCoverageViewText::renderBranchView(raw_ostream &OS, BranchView &BRV, 227*e8d8bef9SDimitry Andric unsigned ViewDepth) { 228*e8d8bef9SDimitry Andric // Render the child subview. 229*e8d8bef9SDimitry Andric if (getOptions().Debug) 230*e8d8bef9SDimitry Andric errs() << "Branch at line " << BRV.getLine() << '\n'; 231*e8d8bef9SDimitry Andric 232*e8d8bef9SDimitry Andric for (const auto &R : BRV.Regions) { 233*e8d8bef9SDimitry Andric double TruePercent = 0.0; 234*e8d8bef9SDimitry Andric double FalsePercent = 0.0; 235*e8d8bef9SDimitry Andric unsigned Total = R.ExecutionCount + R.FalseExecutionCount; 236*e8d8bef9SDimitry Andric 237*e8d8bef9SDimitry Andric if (!getOptions().ShowBranchCounts && Total != 0) { 238*e8d8bef9SDimitry Andric TruePercent = ((double)(R.ExecutionCount) / (double)Total) * 100.0; 239*e8d8bef9SDimitry Andric FalsePercent = ((double)(R.FalseExecutionCount) / (double)Total) * 100.0; 240*e8d8bef9SDimitry Andric } 241*e8d8bef9SDimitry Andric 242*e8d8bef9SDimitry Andric renderLinePrefix(OS, ViewDepth); 243*e8d8bef9SDimitry Andric OS << " Branch (" << R.LineStart << ":" << R.ColumnStart << "): ["; 244*e8d8bef9SDimitry Andric 245*e8d8bef9SDimitry Andric if (R.Folded) { 246*e8d8bef9SDimitry Andric OS << "Folded - Ignored]\n"; 247*e8d8bef9SDimitry Andric continue; 248*e8d8bef9SDimitry Andric } 249*e8d8bef9SDimitry Andric 250*e8d8bef9SDimitry Andric colored_ostream(OS, raw_ostream::RED, 251*e8d8bef9SDimitry Andric getOptions().Colors && !R.ExecutionCount, 252*e8d8bef9SDimitry Andric /*Bold=*/false, /*BG=*/true) 253*e8d8bef9SDimitry Andric << "True"; 254*e8d8bef9SDimitry Andric 255*e8d8bef9SDimitry Andric if (getOptions().ShowBranchCounts) 256*e8d8bef9SDimitry Andric OS << ": " << formatCount(R.ExecutionCount) << ", "; 257*e8d8bef9SDimitry Andric else 258*e8d8bef9SDimitry Andric OS << ": " << format("%0.2f", TruePercent) << "%, "; 259*e8d8bef9SDimitry Andric 260*e8d8bef9SDimitry Andric colored_ostream(OS, raw_ostream::RED, 261*e8d8bef9SDimitry Andric getOptions().Colors && !R.FalseExecutionCount, 262*e8d8bef9SDimitry Andric /*Bold=*/false, /*BG=*/true) 263*e8d8bef9SDimitry Andric << "False"; 264*e8d8bef9SDimitry Andric 265*e8d8bef9SDimitry Andric if (getOptions().ShowBranchCounts) 266*e8d8bef9SDimitry Andric OS << ": " << formatCount(R.FalseExecutionCount); 267*e8d8bef9SDimitry Andric else 268*e8d8bef9SDimitry Andric OS << ": " << format("%0.2f", FalsePercent) << "%"; 269*e8d8bef9SDimitry Andric OS << "]\n"; 270*e8d8bef9SDimitry Andric } 271*e8d8bef9SDimitry Andric } 272*e8d8bef9SDimitry Andric 2730b57cec5SDimitry Andric void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS, 2740b57cec5SDimitry Andric InstantiationView &ISV, 2750b57cec5SDimitry Andric unsigned ViewDepth) { 2760b57cec5SDimitry Andric renderLinePrefix(OS, ViewDepth); 2770b57cec5SDimitry Andric OS << ' '; 2780b57cec5SDimitry Andric if (!ISV.View) 2790b57cec5SDimitry Andric getOptions().colored_ostream(OS, raw_ostream::RED) 2800b57cec5SDimitry Andric << "Unexecuted instantiation: " << ISV.FunctionName << "\n"; 2810b57cec5SDimitry Andric else 2820b57cec5SDimitry Andric ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, 2830b57cec5SDimitry Andric /*ShowTitle=*/false, ViewDepth); 2840b57cec5SDimitry Andric } 2850b57cec5SDimitry Andric 2860b57cec5SDimitry Andric void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) { 2870b57cec5SDimitry Andric if (getOptions().hasProjectTitle()) 2880b57cec5SDimitry Andric getOptions().colored_ostream(OS, raw_ostream::CYAN) 2890b57cec5SDimitry Andric << getOptions().ProjectTitle << "\n"; 2900b57cec5SDimitry Andric 2910b57cec5SDimitry Andric getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n"; 2920b57cec5SDimitry Andric 2930b57cec5SDimitry Andric if (getOptions().hasCreatedTime()) 2940b57cec5SDimitry Andric getOptions().colored_ostream(OS, raw_ostream::CYAN) 2950b57cec5SDimitry Andric << getOptions().CreatedTimeStr << "\n"; 2960b57cec5SDimitry Andric } 2970b57cec5SDimitry Andric 2980b57cec5SDimitry Andric void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned, 2990b57cec5SDimitry Andric unsigned) {} 300