416 lines · cpp
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-exception6//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/FileSystem.h"18#include "llvm/Support/Format.h"19#include "llvm/Support/Path.h"20#include <optional>21 22using namespace llvm;23 24Expected<CoveragePrinter::OwnedStream>25CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) {26 return createOutputStream(Path, "txt", InToplevel);27}28 29void CoveragePrinterText::closeViewFile(OwnedStream OS) {30 OS->operator<<('\n');31}32 33Error CoveragePrinterText::createIndexFile(34 ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,35 const CoverageFiltersMatchAll &Filters) {36 auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true);37 if (Error E = OSOrErr.takeError())38 return E;39 auto OS = std::move(OSOrErr.get());40 raw_ostream &OSRef = *OS.get();41 42 CoverageReport Report(Opts, Coverage);43 Report.renderFileReports(OSRef, SourceFiles, Filters);44 45 Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n"46 << Opts.getLLVMVersionString();47 48 return Error::success();49}50 51struct CoveragePrinterTextDirectory::Reporter : public DirectoryCoverageReport {52 CoveragePrinterTextDirectory &Printer;53 54 Reporter(CoveragePrinterTextDirectory &Printer,55 const coverage::CoverageMapping &Coverage,56 const CoverageFiltersMatchAll &Filters)57 : DirectoryCoverageReport(Printer.Opts, Coverage, Filters),58 Printer(Printer) {}59 60 Error generateSubDirectoryReport(SubFileReports &&SubFiles,61 SubDirReports &&SubDirs,62 FileCoverageSummary &&SubTotals) override {63 auto &LCPath = SubTotals.Name;64 assert(Options.hasOutputDirectory() &&65 "No output directory for index file");66 67 SmallString<128> OSPath = LCPath;68 sys::path::append(OSPath, "index");69 auto OSOrErr = Printer.createOutputStream(OSPath, "txt",70 /*InToplevel=*/false);71 if (auto E = OSOrErr.takeError())72 return E;73 auto OS = std::move(OSOrErr.get());74 raw_ostream &OSRef = *OS.get();75 76 std::vector<FileCoverageSummary> Reports;77 for (auto &&SubDir : SubDirs)78 Reports.push_back(std::move(SubDir.second.first));79 for (auto &&SubFile : SubFiles)80 Reports.push_back(std::move(SubFile.second));81 82 CoverageReport Report(Options, Coverage);83 Report.renderFileReports(OSRef, Reports, SubTotals, Filters.empty());84 85 Options.colored_ostream(OSRef, raw_ostream::CYAN)86 << "\n"87 << Options.getLLVMVersionString();88 89 return Error::success();90 }91};92 93Error CoveragePrinterTextDirectory::createIndexFile(94 ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,95 const CoverageFiltersMatchAll &Filters) {96 if (SourceFiles.size() <= 1)97 return CoveragePrinterText::createIndexFile(SourceFiles, Coverage, Filters);98 99 Reporter Report(*this, Coverage, Filters);100 auto TotalsOrErr = Report.prepareDirectoryReports(SourceFiles);101 if (auto E = TotalsOrErr.takeError())102 return E;103 auto &LCPath = TotalsOrErr->Name;104 105 auto TopIndexFilePath =106 getOutputPath("index", "txt", /*InToplevel=*/true, /*Relative=*/false);107 auto LCPIndexFilePath =108 getOutputPath((LCPath + "index").str(), "txt", /*InToplevel=*/false,109 /*Relative=*/false);110 return errorCodeToError(111 sys::fs::copy_file(LCPIndexFilePath, TopIndexFilePath));112}113 114namespace {115 116static const unsigned LineCoverageColumnWidth = 7;117static const unsigned LineNumberColumnWidth = 5;118 119/// Get the width of the leading columns.120unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) {121 return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +122 (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);123}124 125/// The width of the line that is used to divide between the view and126/// the subviews.127unsigned getDividerWidth(const CoverageViewOptions &Opts) {128 return getCombinedColumnWidth(Opts) + 4;129}130 131} // anonymous namespace132 133void SourceCoverageViewText::renderViewHeader(raw_ostream &) {}134 135void SourceCoverageViewText::renderViewFooter(raw_ostream &) {}136 137void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) {138 getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName()139 << ":\n";140}141 142void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS,143 unsigned ViewDepth) {144 for (unsigned I = 0; I < ViewDepth; ++I)145 OS << " |";146}147 148void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {}149 150void SourceCoverageViewText::renderViewDivider(raw_ostream &OS,151 unsigned ViewDepth) {152 assert(ViewDepth != 0 && "Cannot render divider at top level");153 renderLinePrefix(OS, ViewDepth - 1);154 OS.indent(2);155 unsigned Length = getDividerWidth(getOptions());156 for (unsigned I = 0; I < Length; ++I)157 OS << '-';158 OS << '\n';159}160 161void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L,162 const LineCoverageStats &LCS,163 unsigned ExpansionCol,164 unsigned ViewDepth) {165 StringRef Line = L.Line;166 unsigned LineNumber = L.LineNo;167 auto *WrappedSegment = LCS.getWrappedSegment();168 CoverageSegmentArray Segments = LCS.getLineSegments();169 170 std::optional<raw_ostream::Colors> Highlight;171 SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;172 173 // The first segment overlaps from a previous line, so we treat it specially.174 if (WrappedSegment && !WrappedSegment->IsGapRegion &&175 WrappedSegment->HasCount && WrappedSegment->Count == 0)176 Highlight = raw_ostream::RED;177 178 // Output each segment of the line, possibly highlighted.179 unsigned Col = 1;180 for (const auto *S : Segments) {181 unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1);182 colored_ostream(OS, Highlight.value_or(raw_ostream::SAVEDCOLOR),183 getOptions().Colors && Highlight, /*Bold=*/false,184 /*BG=*/true)185 << Line.substr(Col - 1, End - Col);186 if (getOptions().Debug && Highlight)187 HighlightedRanges.push_back(std::make_pair(Col, End));188 Col = End;189 if ((!S->IsGapRegion || Highlight == raw_ostream::RED) && S->HasCount &&190 S->Count == 0)191 Highlight = raw_ostream::RED;192 else if (Col == ExpansionCol)193 Highlight = raw_ostream::CYAN;194 else195 Highlight = std::nullopt;196 }197 198 // Show the rest of the line.199 colored_ostream(OS, Highlight.value_or(raw_ostream::SAVEDCOLOR),200 getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true)201 << Line.substr(Col - 1, Line.size() - Col + 1);202 OS << '\n';203 204 if (getOptions().Debug) {205 for (const auto &Range : HighlightedRanges)206 errs() << "Highlighted line " << LineNumber << ", " << Range.first207 << " -> " << Range.second << '\n';208 if (Highlight)209 errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";210 }211}212 213void SourceCoverageViewText::renderLineCoverageColumn(214 raw_ostream &OS, const LineCoverageStats &Line) {215 if (!Line.isMapped()) {216 OS.indent(LineCoverageColumnWidth) << '|';217 return;218 }219 std::string C = formatBinaryCount(Line.getExecutionCount());220 OS.indent(LineCoverageColumnWidth - C.size());221 colored_ostream(OS, raw_ostream::MAGENTA,222 Line.hasMultipleRegions() && getOptions().Colors)223 << C;224 OS << '|';225}226 227void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS,228 unsigned LineNo) {229 SmallString<32> Buffer;230 raw_svector_ostream BufferOS(Buffer);231 BufferOS << LineNo;232 auto Str = BufferOS.str();233 // Trim and align to the right.234 Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));235 OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';236}237 238void SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS,239 const LineCoverageStats &Line,240 unsigned ViewDepth) {241 renderLinePrefix(OS, ViewDepth);242 OS.indent(getCombinedColumnWidth(getOptions()));243 244 CoverageSegmentArray Segments = Line.getLineSegments();245 246 // Just consider the segments which start *and* end on this line.247 if (Segments.size() > 1)248 Segments = Segments.drop_back();249 250 unsigned PrevColumn = 1;251 for (const auto *S : Segments) {252 if (!S->IsRegionEntry)253 continue;254 if (S->Count == Line.getExecutionCount())255 continue;256 // Skip to the new region.257 if (S->Col > PrevColumn)258 OS.indent(S->Col - PrevColumn);259 PrevColumn = S->Col + 1;260 std::string C = formatCount(S->Count);261 PrevColumn += C.size();262 OS << '^' << C;263 264 if (getOptions().Debug)265 errs() << "Marker at " << S->Line << ":" << S->Col << " = "266 << formatBinaryCount(S->Count) << "\n";267 }268 OS << '\n';269}270 271void SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L,272 const LineCoverageStats &LCS,273 unsigned ExpansionCol,274 unsigned ViewDepth) {275 renderLinePrefix(OS, ViewDepth);276 OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1));277 renderLine(OS, L, LCS, ExpansionCol, ViewDepth);278}279 280void SourceCoverageViewText::renderExpansionView(raw_ostream &OS,281 ExpansionView &ESV,282 unsigned ViewDepth) {283 // Render the child subview.284 if (getOptions().Debug)285 errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol()286 << " -> " << ESV.getEndCol() << '\n';287 ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,288 /*ShowTitle=*/false, ViewDepth + 1);289}290 291void SourceCoverageViewText::renderBranchView(raw_ostream &OS, BranchView &BRV,292 unsigned ViewDepth) {293 // Render the child subview.294 if (getOptions().Debug)295 errs() << "Branch at line " << BRV.getLine() << '\n';296 297 auto BranchCount = [&](StringRef Label, uint64_t Count, bool Folded,298 double Total) {299 if (Folded)300 return std::string{"Folded"};301 302 std::string Str;303 raw_string_ostream OS(Str);304 305 colored_ostream(OS, raw_ostream::RED, getOptions().Colors && !Count,306 /*Bold=*/false, /*BG=*/true)307 << Label;308 309 if (getOptions().ShowBranchCounts)310 OS << ": " << formatBinaryCount(Count);311 else312 OS << ": " << format("%0.2f", (Total != 0 ? 100.0 * Count / Total : 0.0))313 << "%";314 315 return Str;316 };317 318 for (const auto &R : BRV.Regions) {319 // This can be `double` since it is only used as a denominator.320 // FIXME: It is still inaccurate if Count is greater than (1LL << 53).321 double Total =322 static_cast<double>(R.ExecutionCount) + R.FalseExecutionCount;323 324 renderLinePrefix(OS, ViewDepth);325 OS << " Branch (" << R.LineStart << ":" << R.ColumnStart << "): [";326 327 if (R.TrueFolded && R.FalseFolded) {328 OS << "Folded - Ignored]\n";329 continue;330 }331 332 OS << BranchCount("True", R.ExecutionCount, R.TrueFolded, Total) << ", "333 << BranchCount("False", R.FalseExecutionCount, R.FalseFolded, Total)334 << "]\n";335 }336}337 338void SourceCoverageViewText::renderMCDCView(raw_ostream &OS, MCDCView &MRV,339 unsigned ViewDepth) {340 for (auto &Record : MRV.Records) {341 renderLinePrefix(OS, ViewDepth);342 OS << "---> MC/DC Decision Region (";343 // Display Line + Column information.344 const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion();345 OS << DecisionRegion.LineStart << ":";346 OS << DecisionRegion.ColumnStart << ") to (";347 OS << DecisionRegion.LineEnd << ":";348 OS << DecisionRegion.ColumnEnd << ")\n";349 renderLinePrefix(OS, ViewDepth);350 OS << "\n";351 352 // Display MC/DC Information.353 renderLinePrefix(OS, ViewDepth);354 OS << " Number of Conditions: " << Record.getNumConditions() << "\n";355 for (unsigned i = 0; i < Record.getNumConditions(); i++) {356 renderLinePrefix(OS, ViewDepth);357 OS << " " << Record.getConditionHeaderString(i);358 }359 renderLinePrefix(OS, ViewDepth);360 OS << "\n";361 renderLinePrefix(OS, ViewDepth);362 OS << " Executed MC/DC Test Vectors:\n";363 renderLinePrefix(OS, ViewDepth);364 OS << "\n";365 renderLinePrefix(OS, ViewDepth);366 OS << " ";367 OS << Record.getTestVectorHeaderString();368 for (unsigned i = 0; i < Record.getNumTestVectors(); i++) {369 renderLinePrefix(OS, ViewDepth);370 OS << Record.getTestVectorString(i);371 }372 renderLinePrefix(OS, ViewDepth);373 OS << "\n";374 for (unsigned i = 0; i < Record.getNumConditions(); i++) {375 renderLinePrefix(OS, ViewDepth);376 OS << Record.getConditionCoverageString(i);377 }378 renderLinePrefix(OS, ViewDepth);379 OS << " MC/DC Coverage for Decision: ";380 colored_ostream(OS, raw_ostream::RED,381 getOptions().Colors && Record.getPercentCovered() < 100.0,382 /*Bold=*/false, /*BG=*/true)383 << format("%0.2f", Record.getPercentCovered()) << "%";384 OS << "\n";385 renderLinePrefix(OS, ViewDepth);386 OS << "\n";387 }388}389 390void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS,391 InstantiationView &ISV,392 unsigned ViewDepth) {393 renderLinePrefix(OS, ViewDepth);394 OS << ' ';395 if (!ISV.View)396 getOptions().colored_ostream(OS, raw_ostream::RED)397 << "Unexecuted instantiation: " << ISV.FunctionName << "\n";398 else399 ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,400 /*ShowTitle=*/false, ViewDepth);401}402 403void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) {404 if (getOptions().hasProjectTitle())405 getOptions().colored_ostream(OS, raw_ostream::CYAN)406 << getOptions().ProjectTitle << "\n";407 408 getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n";409 410 if (getOptions().hasCreatedTime())411 getOptions().colored_ostream(OS, raw_ostream::CYAN)412 << getOptions().CreatedTimeStr << "\n";413}414 415void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned) {}416