1303231Sdim//===- SourceCoverageViewText.cpp - A text-based code coverage view -------===// 2303231Sdim// 3353358Sdim// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4353358Sdim// See https://llvm.org/LICENSE.txt for license information. 5353358Sdim// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6303231Sdim// 7303231Sdim//===----------------------------------------------------------------------===// 8303231Sdim/// 9303231Sdim/// \file This file implements the text-based coverage renderer. 10303231Sdim/// 11303231Sdim//===----------------------------------------------------------------------===// 12303231Sdim 13314564Sdim#include "CoverageReport.h" 14303231Sdim#include "SourceCoverageViewText.h" 15303231Sdim#include "llvm/ADT/Optional.h" 16303231Sdim#include "llvm/ADT/SmallString.h" 17303231Sdim#include "llvm/ADT/StringExtras.h" 18303231Sdim 19303231Sdimusing namespace llvm; 20303231Sdim 21303231SdimExpected<CoveragePrinter::OwnedStream> 22303231SdimCoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) { 23303231Sdim return createOutputStream(Path, "txt", InToplevel); 24303231Sdim} 25303231Sdim 26303231Sdimvoid CoveragePrinterText::closeViewFile(OwnedStream OS) { 27303231Sdim OS->operator<<('\n'); 28303231Sdim} 29303231Sdim 30314564SdimError CoveragePrinterText::createIndexFile( 31327952Sdim ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage, 32327952Sdim const CoverageFiltersMatchAll &Filters) { 33303231Sdim auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true); 34303231Sdim if (Error E = OSOrErr.takeError()) 35303231Sdim return E; 36303231Sdim auto OS = std::move(OSOrErr.get()); 37303231Sdim raw_ostream &OSRef = *OS.get(); 38303231Sdim 39314564Sdim CoverageReport Report(Opts, Coverage); 40327952Sdim Report.renderFileReports(OSRef, SourceFiles, Filters); 41303231Sdim 42314564Sdim Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n" 43314564Sdim << Opts.getLLVMVersionString(); 44314564Sdim 45303231Sdim return Error::success(); 46303231Sdim} 47303231Sdim 48303231Sdimnamespace { 49303231Sdim 50303231Sdimstatic const unsigned LineCoverageColumnWidth = 7; 51303231Sdimstatic const unsigned LineNumberColumnWidth = 5; 52303231Sdim 53341825Sdim/// Get the width of the leading columns. 54303231Sdimunsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) { 55303231Sdim return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) + 56303231Sdim (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0); 57303231Sdim} 58303231Sdim 59341825Sdim/// The width of the line that is used to divide between the view and 60303231Sdim/// the subviews. 61303231Sdimunsigned getDividerWidth(const CoverageViewOptions &Opts) { 62303231Sdim return getCombinedColumnWidth(Opts) + 4; 63303231Sdim} 64303231Sdim 65303231Sdim} // anonymous namespace 66303231Sdim 67303231Sdimvoid SourceCoverageViewText::renderViewHeader(raw_ostream &) {} 68303231Sdim 69303231Sdimvoid SourceCoverageViewText::renderViewFooter(raw_ostream &) {} 70303231Sdim 71314564Sdimvoid SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) { 72303231Sdim getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName() 73303231Sdim << ":\n"; 74303231Sdim} 75303231Sdim 76303231Sdimvoid SourceCoverageViewText::renderLinePrefix(raw_ostream &OS, 77303231Sdim unsigned ViewDepth) { 78303231Sdim for (unsigned I = 0; I < ViewDepth; ++I) 79303231Sdim OS << " |"; 80303231Sdim} 81303231Sdim 82303231Sdimvoid SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {} 83303231Sdim 84303231Sdimvoid SourceCoverageViewText::renderViewDivider(raw_ostream &OS, 85303231Sdim unsigned ViewDepth) { 86303231Sdim assert(ViewDepth != 0 && "Cannot render divider at top level"); 87303231Sdim renderLinePrefix(OS, ViewDepth - 1); 88303231Sdim OS.indent(2); 89303231Sdim unsigned Length = getDividerWidth(getOptions()); 90303231Sdim for (unsigned I = 0; I < Length; ++I) 91303231Sdim OS << '-'; 92303231Sdim OS << '\n'; 93303231Sdim} 94303231Sdim 95327952Sdimvoid SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L, 96327952Sdim const LineCoverageStats &LCS, 97327952Sdim unsigned ExpansionCol, 98327952Sdim unsigned ViewDepth) { 99303231Sdim StringRef Line = L.Line; 100303231Sdim unsigned LineNumber = L.LineNo; 101327952Sdim auto *WrappedSegment = LCS.getWrappedSegment(); 102327952Sdim CoverageSegmentArray Segments = LCS.getLineSegments(); 103303231Sdim 104303231Sdim Optional<raw_ostream::Colors> Highlight; 105303231Sdim SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges; 106303231Sdim 107303231Sdim // The first segment overlaps from a previous line, so we treat it specially. 108327952Sdim if (WrappedSegment && !WrappedSegment->IsGapRegion && 109327952Sdim WrappedSegment->HasCount && WrappedSegment->Count == 0) 110303231Sdim Highlight = raw_ostream::RED; 111303231Sdim 112303231Sdim // Output each segment of the line, possibly highlighted. 113303231Sdim unsigned Col = 1; 114303231Sdim for (const auto *S : Segments) { 115303231Sdim unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1); 116303231Sdim colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, 117303231Sdim getOptions().Colors && Highlight, /*Bold=*/false, 118303231Sdim /*BG=*/true) 119303231Sdim << Line.substr(Col - 1, End - Col); 120303231Sdim if (getOptions().Debug && Highlight) 121303231Sdim HighlightedRanges.push_back(std::make_pair(Col, End)); 122303231Sdim Col = End; 123327952Sdim if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) && 124327952Sdim S->HasCount && S->Count == 0) 125327952Sdim Highlight = raw_ostream::RED; 126327952Sdim else if (Col == ExpansionCol) 127303231Sdim Highlight = raw_ostream::CYAN; 128303231Sdim else 129303231Sdim Highlight = None; 130303231Sdim } 131303231Sdim 132303231Sdim // Show the rest of the line. 133303231Sdim colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, 134303231Sdim getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true) 135303231Sdim << Line.substr(Col - 1, Line.size() - Col + 1); 136303231Sdim OS << '\n'; 137303231Sdim 138303231Sdim if (getOptions().Debug) { 139303231Sdim for (const auto &Range : HighlightedRanges) 140303231Sdim errs() << "Highlighted line " << LineNumber << ", " << Range.first 141303231Sdim << " -> " << Range.second << '\n'; 142303231Sdim if (Highlight) 143303231Sdim errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n"; 144303231Sdim } 145303231Sdim} 146303231Sdim 147303231Sdimvoid SourceCoverageViewText::renderLineCoverageColumn( 148303231Sdim raw_ostream &OS, const LineCoverageStats &Line) { 149303231Sdim if (!Line.isMapped()) { 150303231Sdim OS.indent(LineCoverageColumnWidth) << '|'; 151303231Sdim return; 152303231Sdim } 153327952Sdim std::string C = formatCount(Line.getExecutionCount()); 154303231Sdim OS.indent(LineCoverageColumnWidth - C.size()); 155303231Sdim colored_ostream(OS, raw_ostream::MAGENTA, 156303231Sdim Line.hasMultipleRegions() && getOptions().Colors) 157303231Sdim << C; 158303231Sdim OS << '|'; 159303231Sdim} 160303231Sdim 161303231Sdimvoid SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS, 162303231Sdim unsigned LineNo) { 163303231Sdim SmallString<32> Buffer; 164303231Sdim raw_svector_ostream BufferOS(Buffer); 165303231Sdim BufferOS << LineNo; 166303231Sdim auto Str = BufferOS.str(); 167303231Sdim // Trim and align to the right. 168303231Sdim Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth)); 169303231Sdim OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|'; 170303231Sdim} 171303231Sdim 172327952Sdimvoid SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS, 173327952Sdim const LineCoverageStats &Line, 174327952Sdim unsigned ViewDepth) { 175303231Sdim renderLinePrefix(OS, ViewDepth); 176303231Sdim OS.indent(getCombinedColumnWidth(getOptions())); 177303231Sdim 178327952Sdim CoverageSegmentArray Segments = Line.getLineSegments(); 179327952Sdim 180327952Sdim // Just consider the segments which start *and* end on this line. 181327952Sdim if (Segments.size() > 1) 182327952Sdim Segments = Segments.drop_back(); 183327952Sdim 184303231Sdim unsigned PrevColumn = 1; 185303231Sdim for (const auto *S : Segments) { 186303231Sdim if (!S->IsRegionEntry) 187303231Sdim continue; 188327952Sdim if (S->Count == Line.getExecutionCount()) 189327952Sdim continue; 190303231Sdim // Skip to the new region. 191303231Sdim if (S->Col > PrevColumn) 192303231Sdim OS.indent(S->Col - PrevColumn); 193303231Sdim PrevColumn = S->Col + 1; 194303231Sdim std::string C = formatCount(S->Count); 195303231Sdim PrevColumn += C.size(); 196303231Sdim OS << '^' << C; 197327952Sdim 198327952Sdim if (getOptions().Debug) 199327952Sdim errs() << "Marker at " << S->Line << ":" << S->Col << " = " 200327952Sdim << formatCount(S->Count) << "\n"; 201303231Sdim } 202303231Sdim OS << '\n'; 203303231Sdim} 204303231Sdim 205327952Sdimvoid SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L, 206327952Sdim const LineCoverageStats &LCS, 207327952Sdim unsigned ExpansionCol, 208327952Sdim unsigned ViewDepth) { 209303231Sdim renderLinePrefix(OS, ViewDepth); 210303231Sdim OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1)); 211327952Sdim renderLine(OS, L, LCS, ExpansionCol, ViewDepth); 212303231Sdim} 213303231Sdim 214303231Sdimvoid SourceCoverageViewText::renderExpansionView(raw_ostream &OS, 215303231Sdim ExpansionView &ESV, 216303231Sdim unsigned ViewDepth) { 217303231Sdim // Render the child subview. 218303231Sdim if (getOptions().Debug) 219303231Sdim errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol() 220303231Sdim << " -> " << ESV.getEndCol() << '\n'; 221303231Sdim ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false, 222327952Sdim /*ShowTitle=*/false, ViewDepth + 1); 223303231Sdim} 224303231Sdim 225303231Sdimvoid SourceCoverageViewText::renderInstantiationView(raw_ostream &OS, 226303231Sdim InstantiationView &ISV, 227303231Sdim unsigned ViewDepth) { 228303231Sdim renderLinePrefix(OS, ViewDepth); 229303231Sdim OS << ' '; 230314564Sdim if (!ISV.View) 231314564Sdim getOptions().colored_ostream(OS, raw_ostream::RED) 232314564Sdim << "Unexecuted instantiation: " << ISV.FunctionName << "\n"; 233314564Sdim else 234314564Sdim ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, 235327952Sdim /*ShowTitle=*/false, ViewDepth); 236303231Sdim} 237314564Sdim 238314564Sdimvoid SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) { 239314564Sdim if (getOptions().hasProjectTitle()) 240314564Sdim getOptions().colored_ostream(OS, raw_ostream::CYAN) 241314564Sdim << getOptions().ProjectTitle << "\n"; 242314564Sdim 243314564Sdim getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n"; 244314564Sdim 245314564Sdim if (getOptions().hasCreatedTime()) 246314564Sdim getOptions().colored_ostream(OS, raw_ostream::CYAN) 247314564Sdim << getOptions().CreatedTimeStr << "\n"; 248314564Sdim} 249314564Sdim 250314564Sdimvoid SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned, 251314564Sdim unsigned) {} 252