Path: blob/main/contrib/llvm-project/llvm/tools/llvm-cov/SourceCoverageViewText.cpp
35231 views
//===- SourceCoverageViewText.cpp - A text-based code coverage view -------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7///8/// \file This file implements the text-based coverage renderer.9///10//===----------------------------------------------------------------------===//1112#include "SourceCoverageViewText.h"13#include "CoverageReport.h"14#include "llvm/ADT/SmallString.h"15#include "llvm/ADT/StringExtras.h"16#include "llvm/Support/FileSystem.h"17#include "llvm/Support/Format.h"18#include "llvm/Support/Path.h"19#include <optional>2021using namespace llvm;2223Expected<CoveragePrinter::OwnedStream>24CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) {25return createOutputStream(Path, "txt", InToplevel);26}2728void CoveragePrinterText::closeViewFile(OwnedStream OS) {29OS->operator<<('\n');30}3132Error CoveragePrinterText::createIndexFile(33ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,34const CoverageFiltersMatchAll &Filters) {35auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true);36if (Error E = OSOrErr.takeError())37return E;38auto OS = std::move(OSOrErr.get());39raw_ostream &OSRef = *OS.get();4041CoverageReport Report(Opts, Coverage);42Report.renderFileReports(OSRef, SourceFiles, Filters);4344Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n"45<< Opts.getLLVMVersionString();4647return Error::success();48}4950struct CoveragePrinterTextDirectory::Reporter : public DirectoryCoverageReport {51CoveragePrinterTextDirectory &Printer;5253Reporter(CoveragePrinterTextDirectory &Printer,54const coverage::CoverageMapping &Coverage,55const CoverageFiltersMatchAll &Filters)56: DirectoryCoverageReport(Printer.Opts, Coverage, Filters),57Printer(Printer) {}5859Error generateSubDirectoryReport(SubFileReports &&SubFiles,60SubDirReports &&SubDirs,61FileCoverageSummary &&SubTotals) override {62auto &LCPath = SubTotals.Name;63assert(Options.hasOutputDirectory() &&64"No output directory for index file");6566SmallString<128> OSPath = LCPath;67sys::path::append(OSPath, "index");68auto OSOrErr = Printer.createOutputStream(OSPath, "txt",69/*InToplevel=*/false);70if (auto E = OSOrErr.takeError())71return E;72auto OS = std::move(OSOrErr.get());73raw_ostream &OSRef = *OS.get();7475std::vector<FileCoverageSummary> Reports;76for (auto &&SubDir : SubDirs)77Reports.push_back(std::move(SubDir.second.first));78for (auto &&SubFile : SubFiles)79Reports.push_back(std::move(SubFile.second));8081CoverageReport Report(Options, Coverage);82Report.renderFileReports(OSRef, Reports, SubTotals, Filters.empty());8384Options.colored_ostream(OSRef, raw_ostream::CYAN)85<< "\n"86<< Options.getLLVMVersionString();8788return Error::success();89}90};9192Error CoveragePrinterTextDirectory::createIndexFile(93ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,94const CoverageFiltersMatchAll &Filters) {95if (SourceFiles.size() <= 1)96return CoveragePrinterText::createIndexFile(SourceFiles, Coverage, Filters);9798Reporter Report(*this, Coverage, Filters);99auto TotalsOrErr = Report.prepareDirectoryReports(SourceFiles);100if (auto E = TotalsOrErr.takeError())101return E;102auto &LCPath = TotalsOrErr->Name;103104auto TopIndexFilePath =105getOutputPath("index", "txt", /*InToplevel=*/true, /*Relative=*/false);106auto LCPIndexFilePath =107getOutputPath((LCPath + "index").str(), "txt", /*InToplevel=*/false,108/*Relative=*/false);109return errorCodeToError(110sys::fs::copy_file(LCPIndexFilePath, TopIndexFilePath));111}112113namespace {114115static const unsigned LineCoverageColumnWidth = 7;116static const unsigned LineNumberColumnWidth = 5;117118/// Get the width of the leading columns.119unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) {120return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +121(Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);122}123124/// The width of the line that is used to divide between the view and125/// the subviews.126unsigned getDividerWidth(const CoverageViewOptions &Opts) {127return getCombinedColumnWidth(Opts) + 4;128}129130} // anonymous namespace131132void SourceCoverageViewText::renderViewHeader(raw_ostream &) {}133134void SourceCoverageViewText::renderViewFooter(raw_ostream &) {}135136void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) {137getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName()138<< ":\n";139}140141void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS,142unsigned ViewDepth) {143for (unsigned I = 0; I < ViewDepth; ++I)144OS << " |";145}146147void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {}148149void SourceCoverageViewText::renderViewDivider(raw_ostream &OS,150unsigned ViewDepth) {151assert(ViewDepth != 0 && "Cannot render divider at top level");152renderLinePrefix(OS, ViewDepth - 1);153OS.indent(2);154unsigned Length = getDividerWidth(getOptions());155for (unsigned I = 0; I < Length; ++I)156OS << '-';157OS << '\n';158}159160void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L,161const LineCoverageStats &LCS,162unsigned ExpansionCol,163unsigned ViewDepth) {164StringRef Line = L.Line;165unsigned LineNumber = L.LineNo;166auto *WrappedSegment = LCS.getWrappedSegment();167CoverageSegmentArray Segments = LCS.getLineSegments();168169std::optional<raw_ostream::Colors> Highlight;170SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;171172// The first segment overlaps from a previous line, so we treat it specially.173if (WrappedSegment && !WrappedSegment->IsGapRegion &&174WrappedSegment->HasCount && WrappedSegment->Count == 0)175Highlight = raw_ostream::RED;176177// Output each segment of the line, possibly highlighted.178unsigned Col = 1;179for (const auto *S : Segments) {180unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1);181colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,182getOptions().Colors && Highlight, /*Bold=*/false,183/*BG=*/true)184<< Line.substr(Col - 1, End - Col);185if (getOptions().Debug && Highlight)186HighlightedRanges.push_back(std::make_pair(Col, End));187Col = End;188if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) &&189S->HasCount && S->Count == 0)190Highlight = raw_ostream::RED;191else if (Col == ExpansionCol)192Highlight = raw_ostream::CYAN;193else194Highlight = std::nullopt;195}196197// Show the rest of the line.198colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,199getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true)200<< Line.substr(Col - 1, Line.size() - Col + 1);201OS << '\n';202203if (getOptions().Debug) {204for (const auto &Range : HighlightedRanges)205errs() << "Highlighted line " << LineNumber << ", " << Range.first206<< " -> " << Range.second << '\n';207if (Highlight)208errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";209}210}211212void SourceCoverageViewText::renderLineCoverageColumn(213raw_ostream &OS, const LineCoverageStats &Line) {214if (!Line.isMapped()) {215OS.indent(LineCoverageColumnWidth) << '|';216return;217}218std::string C = formatCount(Line.getExecutionCount());219OS.indent(LineCoverageColumnWidth - C.size());220colored_ostream(OS, raw_ostream::MAGENTA,221Line.hasMultipleRegions() && getOptions().Colors)222<< C;223OS << '|';224}225226void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS,227unsigned LineNo) {228SmallString<32> Buffer;229raw_svector_ostream BufferOS(Buffer);230BufferOS << LineNo;231auto Str = BufferOS.str();232// Trim and align to the right.233Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));234OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';235}236237void SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS,238const LineCoverageStats &Line,239unsigned ViewDepth) {240renderLinePrefix(OS, ViewDepth);241OS.indent(getCombinedColumnWidth(getOptions()));242243CoverageSegmentArray Segments = Line.getLineSegments();244245// Just consider the segments which start *and* end on this line.246if (Segments.size() > 1)247Segments = Segments.drop_back();248249unsigned PrevColumn = 1;250for (const auto *S : Segments) {251if (!S->IsRegionEntry)252continue;253if (S->Count == Line.getExecutionCount())254continue;255// Skip to the new region.256if (S->Col > PrevColumn)257OS.indent(S->Col - PrevColumn);258PrevColumn = S->Col + 1;259std::string C = formatCount(S->Count);260PrevColumn += C.size();261OS << '^' << C;262263if (getOptions().Debug)264errs() << "Marker at " << S->Line << ":" << S->Col << " = "265<< formatCount(S->Count) << "\n";266}267OS << '\n';268}269270void SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L,271const LineCoverageStats &LCS,272unsigned ExpansionCol,273unsigned ViewDepth) {274renderLinePrefix(OS, ViewDepth);275OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1));276renderLine(OS, L, LCS, ExpansionCol, ViewDepth);277}278279void SourceCoverageViewText::renderExpansionView(raw_ostream &OS,280ExpansionView &ESV,281unsigned ViewDepth) {282// Render the child subview.283if (getOptions().Debug)284errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol()285<< " -> " << ESV.getEndCol() << '\n';286ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,287/*ShowTitle=*/false, ViewDepth + 1);288}289290void SourceCoverageViewText::renderBranchView(raw_ostream &OS, BranchView &BRV,291unsigned ViewDepth) {292// Render the child subview.293if (getOptions().Debug)294errs() << "Branch at line " << BRV.getLine() << '\n';295296for (const auto &R : BRV.Regions) {297double TruePercent = 0.0;298double FalsePercent = 0.0;299// FIXME: It may overflow when the data is too large, but I have not300// encountered it in actual use, and not sure whether to use __uint128_t.301uint64_t Total = R.ExecutionCount + R.FalseExecutionCount;302303if (!getOptions().ShowBranchCounts && Total != 0) {304TruePercent = ((double)(R.ExecutionCount) / (double)Total) * 100.0;305FalsePercent = ((double)(R.FalseExecutionCount) / (double)Total) * 100.0;306}307308renderLinePrefix(OS, ViewDepth);309OS << " Branch (" << R.LineStart << ":" << R.ColumnStart << "): [";310311if (R.Folded) {312OS << "Folded - Ignored]\n";313continue;314}315316colored_ostream(OS, raw_ostream::RED,317getOptions().Colors && !R.ExecutionCount,318/*Bold=*/false, /*BG=*/true)319<< "True";320321if (getOptions().ShowBranchCounts)322OS << ": " << formatCount(R.ExecutionCount) << ", ";323else324OS << ": " << format("%0.2f", TruePercent) << "%, ";325326colored_ostream(OS, raw_ostream::RED,327getOptions().Colors && !R.FalseExecutionCount,328/*Bold=*/false, /*BG=*/true)329<< "False";330331if (getOptions().ShowBranchCounts)332OS << ": " << formatCount(R.FalseExecutionCount);333else334OS << ": " << format("%0.2f", FalsePercent) << "%";335OS << "]\n";336}337}338339void SourceCoverageViewText::renderMCDCView(raw_ostream &OS, MCDCView &MRV,340unsigned ViewDepth) {341for (auto &Record : MRV.Records) {342renderLinePrefix(OS, ViewDepth);343OS << "---> MC/DC Decision Region (";344// Display Line + Column information.345const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion();346OS << DecisionRegion.LineStart << ":";347OS << DecisionRegion.ColumnStart << ") to (";348OS << DecisionRegion.LineEnd << ":";349OS << DecisionRegion.ColumnEnd << ")\n";350renderLinePrefix(OS, ViewDepth);351OS << "\n";352353// Display MC/DC Information.354renderLinePrefix(OS, ViewDepth);355OS << " Number of Conditions: " << Record.getNumConditions() << "\n";356for (unsigned i = 0; i < Record.getNumConditions(); i++) {357renderLinePrefix(OS, ViewDepth);358OS << " " << Record.getConditionHeaderString(i);359}360renderLinePrefix(OS, ViewDepth);361OS << "\n";362renderLinePrefix(OS, ViewDepth);363OS << " Executed MC/DC Test Vectors:\n";364renderLinePrefix(OS, ViewDepth);365OS << "\n";366renderLinePrefix(OS, ViewDepth);367OS << " ";368OS << Record.getTestVectorHeaderString();369for (unsigned i = 0; i < Record.getNumTestVectors(); i++) {370renderLinePrefix(OS, ViewDepth);371OS << Record.getTestVectorString(i);372}373renderLinePrefix(OS, ViewDepth);374OS << "\n";375for (unsigned i = 0; i < Record.getNumConditions(); i++) {376renderLinePrefix(OS, ViewDepth);377OS << Record.getConditionCoverageString(i);378}379renderLinePrefix(OS, ViewDepth);380OS << " MC/DC Coverage for Decision: ";381colored_ostream(OS, raw_ostream::RED,382getOptions().Colors && Record.getPercentCovered() < 100.0,383/*Bold=*/false, /*BG=*/true)384<< format("%0.2f", Record.getPercentCovered()) << "%";385OS << "\n";386renderLinePrefix(OS, ViewDepth);387OS << "\n";388}389}390391void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS,392InstantiationView &ISV,393unsigned ViewDepth) {394renderLinePrefix(OS, ViewDepth);395OS << ' ';396if (!ISV.View)397getOptions().colored_ostream(OS, raw_ostream::RED)398<< "Unexecuted instantiation: " << ISV.FunctionName << "\n";399else400ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,401/*ShowTitle=*/false, ViewDepth);402}403404void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) {405if (getOptions().hasProjectTitle())406getOptions().colored_ostream(OS, raw_ostream::CYAN)407<< getOptions().ProjectTitle << "\n";408409getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n";410411if (getOptions().hasCreatedTime())412getOptions().colored_ostream(OS, raw_ostream::CYAN)413<< getOptions().CreatedTimeStr << "\n";414}415416void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned) {}417418419