diff --git a/docs/CommandGuide/llvm-cov.rst b/docs/CommandGuide/llvm-cov.rst index 8e2806b0c61..35a7a036456 100644 --- a/docs/CommandGuide/llvm-cov.rst +++ b/docs/CommandGuide/llvm-cov.rst @@ -240,6 +240,14 @@ OPTIONS Use the specified output format. The supported formats are: "text". +.. option:: -output-dir=PATH + + Specify a directory to write coverage reports into. If the directory does not + exist, it is created. When used in function view mode (i.e when -name or + -name-regex are used to select specific functions), the report is written to + PATH/functions.EXTENSION. When used in file view mode, a report for each file + is written to PATH/REL_PATH_TO_FILE.EXTENSION. + .. option:: -line-coverage-gt= Show code coverage only for functions with line coverage greater than the diff --git a/test/tools/llvm-cov/showLineExecutionCounts.cpp b/test/tools/llvm-cov/showLineExecutionCounts.cpp index 6b969f709a9..587b973ec82 100644 --- a/test/tools/llvm-cov/showLineExecutionCounts.cpp +++ b/test/tools/llvm-cov/showLineExecutionCounts.cpp @@ -28,3 +28,9 @@ int main() { // CHECK: 161| [[@LINE]]|int main( // RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata -filename-equivalence %s | FileCheck -check-prefixes=CHECK,WHOLE-FILE %s // RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %t.profdata -filename-equivalence -name=main %s | FileCheck -check-prefixes=CHECK,FILTER %s + +// Test -output-dir. +// RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -output-dir %t.dir -instr-profile %t.profdata -filename-equivalence %s +// RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -output-dir %t.dir -instr-profile %t.profdata -filename-equivalence -name=main %s +// RUN: FileCheck -check-prefixes=CHECK,WHOLE-FILE -input-file %t.dir/coverage/tmp/showLineExecutionCounts.cpp.txt %s +// RUN: FileCheck -check-prefixes=CHECK,FILTER -input-file %t.dir/functions.txt %s diff --git a/tools/llvm-cov/CodeCoverage.cpp b/tools/llvm-cov/CodeCoverage.cpp index 04901a27179..0943cebb262 100644 --- a/tools/llvm-cov/CodeCoverage.cpp +++ b/tools/llvm-cov/CodeCoverage.cpp @@ -406,6 +406,12 @@ int CodeCoverageTool::show(int argc, const char **argv, clEnumValEnd), cl::init(CoverageViewOptions::OutputFormat::Text)); + cl::opt ShowOutputDirectory( + "output-dir", cl::init(""), + cl::desc("Directory in which coverage information is written out")); + cl::alias ShowOutputDirectoryA("o", cl::desc("Alias for --output-dir"), + cl::aliasopt(ShowOutputDirectory)); + auto Err = commandLineParser(argc, argv); if (Err) return Err; @@ -418,6 +424,14 @@ int CodeCoverageTool::show(int argc, const char **argv, ViewOpts.ShowExpandedRegions = ShowExpansions; ViewOpts.ShowFunctionInstantiations = ShowInstantiations; ViewOpts.ShowFormat = ShowFormat; + ViewOpts.ShowOutputDirectory = ShowOutputDirectory; + + if (ViewOpts.ShowOutputDirectory != "") { + if (auto E = sys::fs::create_directories(ViewOpts.ShowOutputDirectory)) { + error("Could not create output directory!", E.message()); + return 1; + } + } auto Coverage = load(); if (!Coverage) @@ -436,8 +450,17 @@ int CodeCoverageTool::show(int argc, const char **argv, << "\n"; continue; } - mainView->print(outs(), /*WholeFile=*/false, /*ShowSourceName=*/true); - outs() << "\n"; + + auto OSOrErr = + mainView->createOutputFile("functions", /*InToplevel=*/true); + if (Error E = OSOrErr.takeError()) { + handleAllErrors(OSOrErr.takeError(), + [&](const ErrorInfoBase &EI) { error(EI.message()); }); + return 1; + } + auto OS = std::move(OSOrErr.get()); + mainView->print(*OS.get(), /*WholeFile=*/false, /*ShowSourceName=*/true); + mainView->closeOutputFile(std::move(OS)); } return 0; } @@ -459,10 +482,16 @@ int CodeCoverageTool::show(int argc, const char **argv, continue; } - mainView->print(outs(), /*Wholefile=*/true, + auto OSOrErr = mainView->createOutputFile(SourceFile, /*InToplevel=*/false); + if (Error E = OSOrErr.takeError()) { + handleAllErrors(OSOrErr.takeError(), + [&](const ErrorInfoBase &EI) { error(EI.message()); }); + return 1; + } + auto OS = std::move(OSOrErr.get()); + mainView->print(*OS.get(), /*Wholefile=*/true, /*ShowSourceName=*/ShowFilenames); - if (SourceFiles.size() > 1) - outs() << "\n"; + mainView->closeOutputFile(std::move(OS)); } return 0; diff --git a/tools/llvm-cov/CoverageViewOptions.h b/tools/llvm-cov/CoverageViewOptions.h index 93bc09ca9d8..bde8b82520b 100644 --- a/tools/llvm-cov/CoverageViewOptions.h +++ b/tools/llvm-cov/CoverageViewOptions.h @@ -30,6 +30,7 @@ struct CoverageViewOptions { bool ShowFunctionInstantiations; bool ShowFullFilenames; OutputFormat ShowFormat; + std::string ShowOutputDirectory; /// \brief Change the output's stream color if the colors are enabled. ColoredRawOstream colored_ostream(raw_ostream &OS, diff --git a/tools/llvm-cov/SourceCoverageView.cpp b/tools/llvm-cov/SourceCoverageView.cpp index 3309189a935..f9ce946dc5f 100644 --- a/tools/llvm-cov/SourceCoverageView.cpp +++ b/tools/llvm-cov/SourceCoverageView.cpp @@ -15,7 +15,9 @@ #include "SourceCoverageViewText.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/LineIterator.h" +#include "llvm/Support/Path.h" using namespace llvm; @@ -34,6 +36,51 @@ std::string SourceCoverageView::formatCount(uint64_t N) { return Result; } +void SourceCoverageView::StreamDestructor::operator()(raw_ostream *OS) const { + if (OS == &outs()) + return; + delete OS; +} + +/// \brief Create a file at ``Dir/ToplevelDir/@Path.Extension``. If +/// \p ToplevelDir is empty, its path component is skipped. +static Expected +createFileInDirectory(StringRef Dir, StringRef ToplevelDir, StringRef Path, + StringRef Extension) { + assert(Extension.size() && "The file extension may not be empty"); + + SmallString<256> FullPath(Dir); + if (!ToplevelDir.empty()) + sys::path::append(FullPath, ToplevelDir); + + auto PathBaseDir = sys::path::relative_path(sys::path::parent_path(Path)); + sys::path::append(FullPath, PathBaseDir); + + if (auto E = sys::fs::create_directories(FullPath)) + return errorCodeToError(E); + + auto PathFilename = (sys::path::filename(Path) + "." + Extension).str(); + sys::path::append(FullPath, PathFilename); + + std::error_code E; + auto OS = SourceCoverageView::OwnedStream( + new raw_fd_ostream(FullPath, E, sys::fs::F_RW)); + if (E) + return errorCodeToError(E); + return std::move(OS); +} + +Expected +SourceCoverageView::createOutputStream(const CoverageViewOptions &Opts, + StringRef Path, StringRef Extension, + bool InToplevel) { + if (Opts.ShowOutputDirectory == "") + return OwnedStream(&outs()); + + return createFileInDirectory(Opts.ShowOutputDirectory, + InToplevel ? "" : "coverage", Path, Extension); +} + void SourceCoverageView::addExpansion( const coverage::CounterMappingRegion &Region, std::unique_ptr View) { diff --git a/tools/llvm-cov/SourceCoverageView.h b/tools/llvm-cov/SourceCoverageView.h index 03c07422964..def5c6ad71d 100644 --- a/tools/llvm-cov/SourceCoverageView.h +++ b/tools/llvm-cov/SourceCoverageView.h @@ -101,7 +101,7 @@ struct LineCoverageStats { /// /// A source coverage view and its nested sub-views form a file-oriented /// representation of code coverage data. This view can be printed out by a -/// renderer which implements the Rendering Interface. +/// renderer which implements both the File Creation and Rendering interfaces. class SourceCoverageView { /// A function or file name. StringRef SourceName; @@ -122,6 +122,25 @@ class SourceCoverageView { /// on display. std::vector InstantiationSubViews; +public: + struct StreamDestructor { + void operator()(raw_ostream *OS) const; + }; + + using OwnedStream = std::unique_ptr; + + /// @name File Creation Interface + /// @{ + + /// \brief Create a file to print a coverage view into. + virtual Expected createOutputFile(StringRef Path, + bool InToplevel) = 0; + + /// \brief Close a file which has been used to print a coverage view. + virtual void closeOutputFile(OwnedStream OS) = 0; + + /// @} + protected: struct LineRef { StringRef Line; @@ -183,6 +202,12 @@ protected: /// digits. static std::string formatCount(uint64_t N); + /// \brief If directory output is enabled, create a file with \p Path as the + /// suffix. Otherwise, return stdout. + static Expected + createOutputStream(const CoverageViewOptions &Opts, StringRef Path, + StringRef Extension, bool InToplevel); + SourceCoverageView(StringRef SourceName, const MemoryBuffer &File, const CoverageViewOptions &Options, coverage::CoverageData &&CoverageInfo) diff --git a/tools/llvm-cov/SourceCoverageViewText.cpp b/tools/llvm-cov/SourceCoverageViewText.cpp index 0757a4ebd95..65169519c30 100644 --- a/tools/llvm-cov/SourceCoverageViewText.cpp +++ b/tools/llvm-cov/SourceCoverageViewText.cpp @@ -37,6 +37,15 @@ unsigned getDividerWidth(const CoverageViewOptions &Opts) { } // anonymous namespace +Expected +SourceCoverageViewText::createOutputFile(StringRef Path, bool InToplevel) { + return createOutputStream(getOptions(), Path, "txt", InToplevel); +} + +void SourceCoverageViewText::closeOutputFile(OwnedStream OS) { + OS->operator<<('\n'); +} + void SourceCoverageViewText::renderSourceName(raw_ostream &OS) { getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName() << ":\n"; diff --git a/tools/llvm-cov/SourceCoverageViewText.h b/tools/llvm-cov/SourceCoverageViewText.h index 17f2e797abd..769bfa1a652 100644 --- a/tools/llvm-cov/SourceCoverageViewText.h +++ b/tools/llvm-cov/SourceCoverageViewText.h @@ -20,6 +20,13 @@ namespace llvm { /// \brief A code coverage view which supports text-based rendering. class SourceCoverageViewText : public SourceCoverageView { +public: + Expected createOutputFile(StringRef Path, + bool InToplevel) override; + + void closeOutputFile(OwnedStream OS) override; + +private: void renderSourceName(raw_ostream &OS) override; void renderLinePrefix(raw_ostream &OS, unsigned ViewDepth) override;