Index: llvm-cov/CodeCoverage.cpp =================================================================== --- llvm-cov/CodeCoverage.cpp +++ llvm-cov/CodeCoverage.cpp @@ -87,10 +87,14 @@ /// \brief Create the main source view of a particular source file. std::unique_ptr - createSourceFileView(StringRef SourceFile, const CoverageMapping &Coverage); + createSourceFileView(StringRef SourceFile, const CoverageMapping &Coverage, + ArrayRef ObjectFiles); /// \brief Load the coverage mapping data. Return nullptr if an error occured. - std::unique_ptr load(); + std::unique_ptr load(StringRef ObjectFilename); + + /// \brief set the map of RemappedFilenames. + void setRemappedFilenames(const std::vector CoveredFiles); /// \brief Remove input source files which aren't mapped by \p Coverage. void removeUnmappedInputs(const CoverageMapping &Coverage); @@ -101,6 +105,23 @@ /// \brief Demangle \p Sym if possible. Otherwise, just return \p Sym. StringRef getSymbolForHumans(StringRef Sym) const; + /// Return true if the function \p Function is defined in the given source + /// files \p SourceFiles. Otherwise, return false. + bool functionInAnySourceFiles(const FunctionRecord &Function, + ArrayRef SourceFiles); + + /// Return true if the function \p Function has been covered and a coverage + /// report was created. Otherwise, return false. + bool functionHasBeenCovered( + const FunctionRecord &Function, + llvm::StringMap> &CoveredFunctions); + + /// Add the function \p Function to the covered functions map \p + /// CoveredFunctions. + void setFunctionAsCovered( + const FunctionRecord &Function, + llvm::StringMap> &CoveredFunctions); + typedef llvm::function_ref CommandLineParserType; int show(int argc, const char **argv, @@ -112,7 +133,7 @@ int export_(int argc, const char **argv, CommandLineParserType commandLineParser); - std::string ObjectFilename; + std::vector ObjectFilenames; CoverageViewOptions ViewOpts; CoverageFiltersMatchAll Filters; @@ -155,6 +176,17 @@ return Str; } +// Return the full file path if \p Filename has been made absolute, native and +// cleaned-up (removed-dots), otherwise a platform-specific error_code. +static llvm::ErrorOr normalizeFilename(StringRef Filename) { + SmallString<128> ObjectFilePath(Filename); + sys::path::remove_dots(ObjectFilePath, /*remove_dot_dots=*/true); + sys::path::native(ObjectFilePath); + if (std::error_code EC = sys::fs::make_absolute(ObjectFilePath)) + return EC; + return ObjectFilePath.str().str(); +} + void CodeCoverageTool::error(const Twine &Message, StringRef Whence) { std::unique_lock Guard{ErrsLock}; ViewOpts.colored_ostream(errs(), raw_ostream::RED) @@ -273,7 +305,8 @@ std::unique_ptr CodeCoverageTool::createSourceFileView(StringRef SourceFile, - const CoverageMapping &Coverage) { + const CoverageMapping &Coverage, + ArrayRef ObjectFilenames) { auto SourceBuffer = getSourceFile(SourceFile); if (!SourceBuffer) return nullptr; @@ -282,8 +315,9 @@ return nullptr; auto Expansions = FileCoverage.getExpansions(); - auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(), - ViewOpts, std::move(FileCoverage)); + auto View = + SourceCoverageView::create(SourceFile, SourceBuffer.get(), ViewOpts, + std::move(FileCoverage), ObjectFilenames); attachExpansionSubViews(*View, Expansions, Coverage); for (const auto *Function : Coverage.getInstantiations(SourceFile)) { @@ -320,7 +354,8 @@ return LHSTime > RHSTime; } -std::unique_ptr CodeCoverageTool::load() { +std::unique_ptr +CodeCoverageTool::load(StringRef ObjectFilename) { if (modifiedTimeGT(ObjectFilename, PGOFilename)) warning("profile data may be out of date - object is newer", ObjectFilename); @@ -335,7 +370,7 @@ if (Mismatched) warning(utostr(Mismatched) + " functions have mismatched data"); - if (!SourceFiles.empty()) + if (!SourceFiles.empty() && ObjectFilenames.size() == 1) removeUnmappedInputs(*Coverage); demangleSymbols(*Coverage); @@ -343,6 +378,20 @@ return Coverage; } +void CodeCoverageTool::setRemappedFilenames( + const std::vector CoveredFiles) { + for (auto &SF : SourceFiles) { + StringRef SFBase = sys::path::filename(SF); + for (const auto &CF : CoveredFiles) { + if (SFBase == sys::path::filename(CF) && !RemappedFilenames.count(CF)) { + RemappedFilenames[CF] = SF; + SF = CF; + break; + } + } + } +} + void CodeCoverageTool::removeUnmappedInputs(const CoverageMapping &Coverage) { std::vector CoveredFiles = Coverage.getUniqueSourceFiles(); @@ -356,16 +405,7 @@ SF); }); } else { - for (auto &SF : SourceFiles) { - StringRef SFBase = sys::path::filename(SF); - for (const auto &CF : CoveredFiles) { - if (SFBase == sys::path::filename(CF)) { - RemappedFilenames[CF] = SF; - SF = CF; - break; - } - } - } + setRemappedFilenames(CoveredFiles); UncoveredFilesIt = std::remove_if( SourceFiles.begin(), SourceFiles.end(), [&](const std::string &SF) { return !RemappedFilenames.count(SF); }); @@ -457,9 +497,42 @@ return DemangledName->getValue(); } +bool CodeCoverageTool::functionInAnySourceFiles( + const FunctionRecord &Function, ArrayRef SourceFiles) { + if (SourceFiles.empty()) + return false; + for (const auto &FunctionFileName : Function.Filenames) + if (std::find(SourceFiles.begin(), SourceFiles.end(), FunctionFileName) != + SourceFiles.end()) + return true; + return false; +} + +bool CodeCoverageTool::functionHasBeenCovered( + const FunctionRecord &Function, + llvm::StringMap> &CoveredFunctions) { + if (CoveredFunctions.count(Function.Name)) + return functionInAnySourceFiles(Function, CoveredFunctions[Function.Name]); + return false; +} + +void CodeCoverageTool::setFunctionAsCovered( + const FunctionRecord &Function, + llvm::StringMap> &CoveredFunctions) { + if (CoveredFunctions.count(Function.Name)) { + for (const auto Filename : Function.Filenames) + if (std::find(CoveredFunctions[Function.Name].begin(), + CoveredFunctions[Function.Name].end(), + Filename) == CoveredFunctions[Function.Name].end()) + CoveredFunctions[Function.Name].push_back(Filename); + } else + CoveredFunctions[Function.Name] = Function.Filenames; +} + int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) { + std::string ObjectFilenameOptionLoc; cl::opt ObjectFilename( - cl::Positional, cl::Required, cl::location(this->ObjectFilename), + cl::Positional, cl::Required, cl::location(ObjectFilenameOptionLoc), cl::desc("Covered executable or object file.")); cl::list InputSourceFiles( @@ -544,13 +617,12 @@ CompareFilenamesOnly = FilenameEquivalence; ViewOpts.Format = Format; - SmallString<128> ObjectFilePath(this->ObjectFilename); - if (std::error_code EC = sys::fs::make_absolute(ObjectFilePath)) { - error(EC.message(), this->ObjectFilename); + auto FilenameOrErr = normalizeFilename(StringRef(ObjectFilenameOptionLoc)); + if (auto EC = FilenameOrErr.getError()) { + error(EC.message(), FilenameOrErr.get()); return 1; } - sys::path::native(ObjectFilePath); - ViewOpts.ObjectFilename = ObjectFilePath.c_str(); + ObjectFilenames.push_back(FilenameOrErr.get()); switch (ViewOpts.Format) { case CoverageViewOptions::OutputFormat::Text: ViewOpts.Colors = UseColor == cl::BOU_UNSET @@ -680,6 +752,12 @@ "project-title", cl::Optional, cl::desc("Set project title for the coverage report")); + cl::list AdditionalObjectFilenames( + "bin", cl::CommaSeparated, cl::ZeroOrMore, + cl::desc("Additional covered executable or object files."), + cl::value_desc("test.elf,test2.elf...")); + + auto Err = commandLineParser(argc, argv); if (Err) return Err; @@ -715,9 +793,52 @@ ? "Created: " + ModifiedTimeStr.substr(0, found) : "Created: " + ModifiedTimeStr; - auto Coverage = load(); - if (!Coverage) - return 1; + for (const auto &Filename : AdditionalObjectFilenames) { + auto FilenameOrErr = normalizeFilename(StringRef(Filename)); + if (auto EC = FilenameOrErr.getError()) { + error(EC.message(), Filename); + return 1; + } + if (std::find(ObjectFilenames.begin(), ObjectFilenames.end(), + FilenameOrErr.get()) == ObjectFilenames.end()) + ObjectFilenames.push_back(FilenameOrErr.get()); + } + + StringSet<> UncoveredFunctions; + StringMap> FunctionNameToCoveredFunctions; + StringMap> SourceToObjectMappings; + ObjectFilesMap ObjectMappings; + for (const auto &ObjectFile : ObjectFilenames) { + auto Coverage = load(ObjectFile); + if (!Coverage) + return 1; + std::vector CurObjSourceFiles; + std::vector CoveredFiles = Coverage->getUniqueSourceFiles(); + if (CompareFilenamesOnly && ObjectFilenames.size() > 1) + setRemappedFilenames(CoveredFiles); + + for (StringRef Filename : Coverage->getUniqueSourceFiles()) { + if (SourceFiles.empty() || + (std::find(SourceFiles.begin(), SourceFiles.end(), Filename) != + SourceFiles.end())) { + CurObjSourceFiles.push_back(Filename); + SourceToObjectMappings[Filename].push_back(ObjectFile); + } + } + ObjectMappings[ObjectFile] = + std::make_pair(CurObjSourceFiles, std::move(Coverage)); + } + + // Get the source files from ObjectMappings. + if (SourceFiles.empty()) { + for (const auto &ObjectFile : ObjectFilenames) { + auto Files = ObjectMappings[ObjectFile].first; + for (StringRef SourceFile : Files) + if (std::find(SourceFiles.begin(), SourceFiles.end(), SourceFile) == + SourceFiles.end()) + SourceFiles.push_back(SourceFile); + } + } auto Printer = CoveragePrinter::create(ViewOpts); @@ -730,20 +851,40 @@ auto OS = std::move(OSOrErr.get()); // Show functions. - for (const auto &Function : Coverage->getCoveredFunctions()) { - if (!Filters.matches(Function)) - continue; - - auto mainView = createFunctionView(Function, *Coverage); - if (!mainView) { - warning("Could not read coverage for '" + Function.Name + "'."); - continue; + for (const auto &ObjectFile : ObjectFilenames) { + auto Coverage = *ObjectMappings[ObjectFile].second.get(); + for (const auto &Function : Coverage.getCoveredFunctions()) { + if (!Filters.matches(Function)) + continue; + + if (!functionInAnySourceFiles(Function, SourceFiles)) { + UncoveredFunctions.insert(Function.Name); + continue; + } + if (functionHasBeenCovered(Function, FunctionNameToCoveredFunctions)) + continue; + setFunctionAsCovered(Function, FunctionNameToCoveredFunctions); + if (UncoveredFunctions.count(Function.Name)) + UncoveredFunctions.erase(Function.Name); + + auto mainView = createFunctionView(Function, Coverage); + if (!mainView) { + warning("Could not read coverage for '" + Function.Name + "'."); + continue; + } + mainView->print(*OS.get(), /*WholeFile=*/false, + /*ShowSourceName=*/true); } - - mainView->print(*OS.get(), /*WholeFile=*/false, /*ShowSourceName=*/true); } Printer->closeViewFile(std::move(OS)); + if (!UncoveredFunctions.empty()) + for (const auto &FunctionName : UncoveredFunctions) { + ViewOpts.colored_ostream(errs(), raw_ostream::RED) + << "warning: Could not read coverage for '" + << FunctionName.getKey().str() << "'." + << "\n"; + } return 0; } @@ -752,49 +893,53 @@ (SourceFiles.size() != 1) || ViewOpts.hasOutputDirectory() || (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML); - if (SourceFiles.empty()) - // Get the source files from the function coverage mapping. - for (StringRef Filename : Coverage->getUniqueSourceFiles()) - SourceFiles.push_back(Filename); - // Create an index out of the source files. if (ViewOpts.hasOutputDirectory()) { - if (Error E = Printer->createIndexFile(SourceFiles, *Coverage)) { + if (Error E = Printer->createIndexFile(ObjectMappings)) { error("Could not create index file!", toString(std::move(E))); return 1; } - } + } - // In -output-dir mode, it's safe to use multiple threads to print files. - unsigned ThreadCount = 1; - if (ViewOpts.hasOutputDirectory()) - ThreadCount = std::thread::hardware_concurrency(); - ThreadPool Pool(ThreadCount); - - for (const std::string &SourceFile : SourceFiles) { - Pool.async([this, &SourceFile, &Coverage, &Printer, ShowFilenames] { - auto View = createSourceFileView(SourceFile, *Coverage); - if (!View) { - warning("The file '" + SourceFile + "' isn't covered."); - return; - } + // In -output-dir mode, it's safe to use multiple threads to print files. + unsigned ThreadCount = 1; + if (ViewOpts.hasOutputDirectory()) + ThreadCount = std::thread::hardware_concurrency(); + ThreadPool Pool(ThreadCount); - auto OSOrErr = Printer->createViewFile(SourceFile, /*InToplevel=*/false); - if (Error E = OSOrErr.takeError()) { - error("Could not create view file!", toString(std::move(E))); - return; + for (const std::string &SourceFile : SourceFiles) { + if (!SourceToObjectMappings[SourceFile].size()) { + error("Missing source file", SourceFile); + continue; } - auto OS = std::move(OSOrErr.get()); + auto Coverage = + *ObjectMappings[SourceToObjectMappings[SourceFile][0]].second; + auto ObjectFilenames = SourceToObjectMappings[SourceFile]; + Pool.async([this, &SourceFile, Coverage, &Printer, ShowFilenames, + ObjectFilenames] { + auto View = createSourceFileView(SourceFile, Coverage, ObjectFilenames); + if (!View) { + warning("The file '" + SourceFile + "' isn't covered."); + return; + } - View->print(*OS.get(), /*Wholefile=*/true, - /*ShowSourceName=*/ShowFilenames); - Printer->closeViewFile(std::move(OS)); - }); - } + auto OSOrErr = + Printer->createViewFile(SourceFile, /*InToplevel=*/false); + if (Error E = OSOrErr.takeError()) { + error("Could not create view file!", toString(std::move(E))); + return; + } + auto OS = std::move(OSOrErr.get()); - Pool.wait(); + View->print(*OS.get(), /*Wholefile=*/true, + /*ShowSourceName=*/ShowFilenames); + Printer->closeViewFile(std::move(OS)); + }); + } - return 0; + Pool.wait(); + + return 0; } int CodeCoverageTool::report(int argc, const char **argv, @@ -806,15 +951,17 @@ if (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML) error("HTML output for summary reports is not yet supported."); - auto Coverage = load(); - if (!Coverage) - return 1; + for (const auto &ObjectFile : ObjectFilenames) { + auto Coverage = load(ObjectFile); + if (!Coverage) + return 1; - CoverageReport Report(ViewOpts, *Coverage.get()); - if (SourceFiles.empty()) - Report.renderFileReports(llvm::outs()); - else - Report.renderFunctionReports(SourceFiles, llvm::outs()); + CoverageReport Report(ViewOpts, *Coverage.get()); + if (SourceFiles.empty()) + Report.renderFileReports(llvm::outs()); + else + Report.renderFunctionReports(SourceFiles, llvm::outs()); + } return 0; } @@ -825,13 +972,15 @@ if (Err) return Err; - auto Coverage = load(); - if (!Coverage) { - error("Could not load coverage information"); - return 1; - } + for (const auto &ObjectFile : ObjectFilenames) { + auto Coverage = load(ObjectFile); + if (!Coverage) { + error("Could not load coverage information"); + return 1; + } - exportCoverageDataToJson(ObjectFilename, *Coverage.get(), outs()); + exportCoverageDataToJson(ObjectFile, *Coverage.get(), outs()); + } return 0; } Index: llvm-cov/CoverageViewOptions.h =================================================================== --- llvm-cov/CoverageViewOptions.h +++ llvm-cov/CoverageViewOptions.h @@ -36,7 +36,6 @@ std::vector DemanglerOpts; uint32_t TabSize; std::string ProjectTitle; - std::string ObjectFilename; std::string CreatedTimeStr; /// \brief Change the output's stream color if the colors are enabled. Index: llvm-cov/SourceCoverageView.h =================================================================== --- llvm-cov/SourceCoverageView.h +++ llvm-cov/SourceCoverageView.h @@ -21,6 +21,10 @@ namespace llvm { +typedef StringMap, + std::unique_ptr>> + ObjectFilesMap; + class SourceCoverageView; /// \brief A view that represents a macro or include expansion. @@ -142,8 +146,7 @@ virtual void closeViewFile(OwnedStream OS) = 0; /// \brief Create an index which lists reports for the given source files. - virtual Error createIndexFile(ArrayRef SourceFiles, - const coverage::CoverageMapping &Coverage) = 0; + virtual Error createIndexFile(ObjectFilesMap &ObjectMappings) = 0; /// @} }; @@ -157,6 +160,9 @@ /// A function or file name. StringRef SourceName; + /// All the object files that contain the source file \p SourceName. + std::vector ObjectFilenames; + /// A memory buffer backing the source on display. const MemoryBuffer &File; @@ -260,17 +266,20 @@ /// \brief Check if there are any sub-views attached to this view. bool hasSubViews() const; - SourceCoverageView(StringRef SourceName, const MemoryBuffer &File, + SourceCoverageView(StringRef SourceName, + ArrayRef ObjectFilenames, + const MemoryBuffer &File, const CoverageViewOptions &Options, coverage::CoverageData &&CoverageInfo) - : SourceName(SourceName), File(File), Options(Options), - CoverageInfo(std::move(CoverageInfo)) {} + : SourceName(SourceName), ObjectFilenames(ObjectFilenames), File(File), + Options(Options), CoverageInfo(std::move(CoverageInfo)) {} public: static std::unique_ptr create(StringRef SourceName, const MemoryBuffer &File, const CoverageViewOptions &Options, - coverage::CoverageData &&CoverageInfo); + coverage::CoverageData &&CoverageInfo, + ArrayRef ObjectFiles = std::vector()); virtual ~SourceCoverageView() {} Index: llvm-cov/SourceCoverageView.cpp =================================================================== --- llvm-cov/SourceCoverageView.cpp +++ llvm-cov/SourceCoverageView.cpp @@ -130,14 +130,15 @@ std::unique_ptr SourceCoverageView::create(StringRef SourceName, const MemoryBuffer &File, const CoverageViewOptions &Options, - coverage::CoverageData &&CoverageInfo) { + coverage::CoverageData &&CoverageInfo, + ArrayRef ObjectFilenames) { switch (Options.Format) { case CoverageViewOptions::OutputFormat::Text: return llvm::make_unique( - SourceName, File, Options, std::move(CoverageInfo)); + SourceName, ObjectFilenames, File, Options, std::move(CoverageInfo)); case CoverageViewOptions::OutputFormat::HTML: return llvm::make_unique( - SourceName, File, Options, std::move(CoverageInfo)); + SourceName, ObjectFilenames, File, Options, std::move(CoverageInfo)); } llvm_unreachable("Unknown coverage output format!"); } @@ -150,8 +151,11 @@ } std::string SourceCoverageView::getVerboseSourceName() const { + std::vector ObjectFiles; + for (StringRef ObjectFilename : ObjectFilenames) + ObjectFiles.push_back(sys::path::filename(ObjectFilename).str()); return getSourceName() + " (Binary: " + - sys::path::filename(getOptions().ObjectFilename).str() + ")"; + join(ObjectFiles.begin(), ObjectFiles.end(), ", ") + ")"; } void SourceCoverageView::addExpansion( Index: llvm-cov/SourceCoverageViewHTML.h =================================================================== --- llvm-cov/SourceCoverageViewHTML.h +++ llvm-cov/SourceCoverageViewHTML.h @@ -28,8 +28,7 @@ void closeViewFile(OwnedStream OS) override; - Error createIndexFile(ArrayRef SourceFiles, - const coverage::CoverageMapping &Coverage) override; + Error createIndexFile(ObjectFilesMap &ObjectMappings) override; CoveragePrinterHTML(const CoverageViewOptions &Opts) : CoveragePrinter(Opts) {} @@ -84,11 +83,13 @@ unsigned IndentLevel) override; public: - SourceCoverageViewHTML(StringRef SourceName, const MemoryBuffer &File, + SourceCoverageViewHTML(StringRef SourceName, + ArrayRef ObjectFilenames, + const MemoryBuffer &File, const CoverageViewOptions &Options, coverage::CoverageData &&CoverageInfo) - : SourceCoverageView(SourceName, File, Options, std::move(CoverageInfo)) { - } + : SourceCoverageView(SourceName, ObjectFilenames, File, Options, + std::move(CoverageInfo)) {} }; } // namespace llvm Index: llvm-cov/SourceCoverageViewHTML.cpp =================================================================== --- llvm-cov/SourceCoverageViewHTML.cpp +++ llvm-cov/SourceCoverageViewHTML.cpp @@ -346,9 +346,7 @@ OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row"); } -Error CoveragePrinterHTML::createIndexFile( - ArrayRef SourceFiles, - const coverage::CoverageMapping &Coverage) { +Error CoveragePrinterHTML::createIndexFile(ObjectFilesMap &ObjectMappings) { // Emit the default stylesheet. auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true); if (Error E = CSSOrErr.takeError()) @@ -385,10 +383,19 @@ OSRef << BeginCenteredDiv << BeginTable; emitColumnLabelsForIndex(OSRef); FileCoverageSummary Totals("TOTALS"); - auto FileReports = - CoverageReport::prepareFileReports(Coverage, Totals, SourceFiles); - for (unsigned I = 0, E = FileReports.size(); I < E; ++I) - emitFileSummary(OSRef, SourceFiles[I], FileReports[I]); + std::vector AllSourceFiles; + for (const auto &ObjectFile : ObjectMappings) { + const auto &SourceFiles = ObjectFile.second.first; + auto FileReports = CoverageReport::prepareFileReports( + *ObjectFile.second.second.get(), Totals, SourceFiles); + for (unsigned I = 0, E = FileReports.size(); I < E; ++I) { + if (std::find(AllSourceFiles.begin(), AllSourceFiles.end(), + SourceFiles[I]) == AllSourceFiles.end()) { + emitFileSummary(OSRef, SourceFiles[I], FileReports[I]); + AllSourceFiles.push_back(SourceFiles[I]); + } + } + } emitFileSummary(OSRef, "Totals", Totals, /*IsTotals=*/true); OSRef << EndTable << EndCenteredDiv << tag("h5", escape(Opts.getLLVMVersionString(), Opts)); Index: llvm-cov/SourceCoverageViewText.h =================================================================== --- llvm-cov/SourceCoverageViewText.h +++ llvm-cov/SourceCoverageViewText.h @@ -26,8 +26,7 @@ void closeViewFile(OwnedStream OS) override; - Error createIndexFile(ArrayRef SourceFiles, - const coverage::CoverageMapping &Coverage) override; + Error createIndexFile(ObjectFilesMap &ObjectMappings) override; CoveragePrinterText(const CoverageViewOptions &Opts) : CoveragePrinter(Opts) {} @@ -77,11 +76,13 @@ unsigned IndentLevel) override; public: - SourceCoverageViewText(StringRef SourceName, const MemoryBuffer &File, + SourceCoverageViewText(StringRef SourceName, + ArrayRef ObjectFilenames, + const MemoryBuffer &File, const CoverageViewOptions &Options, coverage::CoverageData &&CoverageInfo) - : SourceCoverageView(SourceName, File, Options, std::move(CoverageInfo)) { - } + : SourceCoverageView(SourceName, ObjectFilenames, File, Options, + std::move(CoverageInfo)) {} }; } // namespace llvm Index: llvm-cov/SourceCoverageViewText.cpp =================================================================== --- llvm-cov/SourceCoverageViewText.cpp +++ llvm-cov/SourceCoverageViewText.cpp @@ -28,17 +28,17 @@ OS->operator<<('\n'); } -Error CoveragePrinterText::createIndexFile( - ArrayRef SourceFiles, - const coverage::CoverageMapping &Coverage) { +Error CoveragePrinterText::createIndexFile(ObjectFilesMap &ObjectMappings) { auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true); if (Error E = OSOrErr.takeError()) return E; auto OS = std::move(OSOrErr.get()); raw_ostream &OSRef = *OS.get(); - CoverageReport Report(Opts, Coverage); - Report.renderFileReports(OSRef, SourceFiles); + for (const auto &ObjectFile : ObjectMappings) { + CoverageReport Report(Opts, *ObjectFile.second.second.get()); + Report.renderFileReports(OSRef); + } Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n" << Opts.getLLVMVersionString(); Index: tools/llvm-cov/Inputs/allELFs.proftext =================================================================== --- /dev/null +++ tools/llvm-cov/Inputs/allELFs.proftext @@ -0,0 +1,37 @@ +main +# Func Hash: +260 +# Num Counters: +3 +# Counter Values: +1 +2 +10 + +main +# Func Hash: +4 +# Num Counters: +2 +# Counter Values: +1 +20 + +_Z3fooi +# Func Hash: +10 +# Num Counters: +2 +# Counter Values: +2 +1 + +_Z3bari +# Func Hash: +10 +# Num Counters: +2 +# Counter Values: +1 +1 + Index: tools/llvm-cov/Inputs/multiELFsMainOne.proftext =================================================================== --- /dev/null +++ tools/llvm-cov/Inputs/multiELFsMainOne.proftext @@ -0,0 +1,27 @@ +main +# Func Hash: +4 +# Num Counters: +2 +# Counter Values: +1 +20 + +_Z3fooi +# Func Hash: +10 +# Num Counters: +2 +# Counter Values: +1 +1 + +_Z3bari +# Func Hash: +10 +# Num Counters: +2 +# Counter Values: +1 +1 + Index: tools/llvm-cov/Inputs/multiELFsMainTwo.proftext =================================================================== --- /dev/null +++ tools/llvm-cov/Inputs/multiELFsMainTwo.proftext @@ -0,0 +1,19 @@ +main +# Func Hash: +260 +# Num Counters: +3 +# Counter Values: +1 +2 +10 + +_Z3fooi +# Func Hash: +10 +# Num Counters: +2 +# Counter Values: +1 +0 + Index: tools/llvm-cov/multiELFsMainOne.cpp =================================================================== --- /dev/null +++ tools/llvm-cov/multiELFsMainOne.cpp @@ -0,0 +1,60 @@ +// Tests for the multiple ELFs show command: 1) show code coverage for the given source files, 2) show code coverage for the given function. + +int foo(int); +int bar(int x) { + if (x >= 100) + x = x / 2; + else + x = x * 2; + return x; +} +int main(int argc, char ** argv) { + int x=0; + for (int i = 0; i < 20; ++i) { + x *= 2; + x += 1; + } + x += bar(x); + x += foo(x); + return x; +} + +// RUN: llvm-profdata merge %S/Inputs/allELFs.proftext -o %t.profdata +// RUN: llvm-cov show %S/Inputs/multiELFsMainOne.covmapping -bin=%S/Inputs/multiELFsMainTwo.covmapping -instr-profile %t.profdata -filename-equivalence %s 2>&1 | FileCheck -check-prefix=ElfOne %s +// ElfOne: [[@LINE-21]]| |int foo(int); +// ElfOne-NEXT: [[@LINE-21]]| 1|int bar(int x) { +// ElfOne-NEXT: [[@LINE-21]]| 1| if (x >= 100) +// ElfOne-NEXT: [[@LINE-21]]| 1| x = x / 2; +// ElfOne-NEXT: [[@LINE-21]]| 1| else +// ElfOne-NEXT: [[@LINE-21]]| 0| x = x * 2; +// ElfOne-NEXT: [[@LINE-21]]| 1| return x; +// ElfOne-NEXT: [[@LINE-21]]| 1|} +// ElfOne-NEXT: [[@LINE-21]]| 1|int main(int argc, char ** argv) { +// ElfOne-NEXT: [[@LINE-21]]| 1| int x=0; +// ElfOne-NEXT: [[@LINE-21]]| 21| for (int i = 0; i < 20; ++i) { +// ElfOne-NEXT: [[@LINE-21]]| 20| x *= 2; +// ElfOne-NEXT: [[@LINE-21]]| 20| x += 1; +// ElfOne-NEXT: [[@LINE-21]]| 20| } +// ElfOne-NEXT: [[@LINE-21]]| 1| x += bar(x); +// ElfOne-NEXT: [[@LINE-21]]| 1| x += foo(x); +// ElfOne-NEXT: [[@LINE-21]]| 1| return x; +// ElfOne-NEXT: [[@LINE-21]]| 1|} + +// RUN: llvm-cov show %S/Inputs/multiELFsMainOne.covmapping -bin=%S/Inputs/multiELFsMainTwo.covmapping -instr-profile %t.profdata -filename-equivalence -name=main %s 2>&1 | FileCheck -check-prefix=MainFunc %s +// MainFunc: [[@LINE-33]]| 1|int main(int argc, char ** argv) { +// MainFunc-NEXT: [[@LINE-33]]| 1| int x=0; +// MainFunc-NEXT: [[@LINE-33]]| 21| for (int i = 0; i < 20; ++i) { +// MainFunc-NEXT: [[@LINE-33]]| 20| x *= 2; +// MainFunc-NEXT: [[@LINE-33]]| 20| x += 1; +// MainFunc-NEXT: [[@LINE-33]]| 20| } +// MainFunc-NEXT: [[@LINE-33]]| 1| x += bar(x); +// MainFunc-NEXT: [[@LINE-33]]| 1| x += foo(x); +// MainFunc-NEXT: [[@LINE-33]]| 1| return x; +// MainFunc-NEXT: [[@LINE-33]]| 1|} + +// RUN: llvm-cov show %S/Inputs/multiELFsMainOne.covmapping -bin=%S/Inputs/multiELFsMainTwo.covmapping -format=html -o %t.html.dir -instr-profile %t.profdata -filename-equivalence %s 2>&1 +// RUN: FileCheck -check-prefixes=INDEX %s -input-file %t.html.dir/index.html +// RUN: FileCheck -check-prefixes=SourceSummary %s -input-file %t.html.dir/coverage/tmp/multiELFsMainOne.cpp.html +// INDEX: tmp{{.*}}multiELFsMainOne.cpp +// INDEX:
TOTALS
+// SourceSummary:
{{.*}}tmp{{.*}}multiELFsMainOne.cpp (Binary: multiELFsMainOne.covmapping)
Index: tools/llvm-cov/multiELFsMainTwo.cpp =================================================================== --- /dev/null +++ tools/llvm-cov/multiELFsMainTwo.cpp @@ -0,0 +1,41 @@ +// Tests for the multiple ELFs show command (-bin): show code coverage for a source file and a function. + +int foo(int); + +int main(int argc, char **argv) { + int x = 0; + for (int i = 0; i < 2; ++i) { + x *= 2; + x += 1; + } + x += foo(x); + for (int i = 0; i < 10; ++i) { + x *= 3; + x += 1; + } + return x; +} + +// RUN: llvm-profdata merge %S/Inputs/allELFs.proftext -o %t.profdata +// RUN: llvm-cov show %S/Inputs/multiELFsMainOne.covmapping -bin=%S/Inputs/multiELFsMainTwo.covmapping -instr-profile %t.profdata -filename-equivalence %s 2>&1 | FileCheck %s +// RUN: llvm-cov show %S/Inputs/multiELFsMainOne.covmapping -bin=%S/Inputs/multiELFsMainTwo.covmapping -instr-profile %t.profdata -filename-equivalence -name=main %s 2>&1 | FileCheck %s +// CHECK: [[@LINE-17]]| 1|int main(int argc, char **argv) { +// CHECK-NEXT: [[@LINE-17]]| 1| int x = 0; +// CHECK-NEXT: [[@LINE-17]]| 3| for (int i = 0; i < 2; ++i) { +// CHECK-NEXT: [[@LINE-17]]| 2| x *= 2; +// CHECK-NEXT: [[@LINE-17]]| 2| x += 1; +// CHECK-NEXT: [[@LINE-17]]| 2| } +// CHECK-NEXT: [[@LINE-17]]| 1| x += foo(x); +// CHECK-NEXT: [[@LINE-17]]| 11| for (int i = 0; i < 10; ++i) { +// CHECK-NEXT: [[@LINE-17]]| 10| x *= 3; +// CHECK-NEXT: [[@LINE-17]]| 10| x += 1; +// CHECK-NEXT: [[@LINE-17]]| 10| } +// CHECK-NEXT: [[@LINE-17]]| 1| return x; +// CHECK-NEXT: [[@LINE-17]]| 1|} + +// RUN: llvm-cov show %S/Inputs/multiELFsMainOne.covmapping -bin=%S/Inputs/multiELFsMainTwo.covmapping -format=html -o %t.html.dir -instr-profile %t.profdata -filename-equivalence %s 2>&1 +// RUN: FileCheck -check-prefixes=INDEX %s -input-file %t.html.dir/index.html +// RUN: FileCheck -check-prefixes=SourceSummary %s -input-file %t.html.dir/coverage/tmp/multiELFsMainTwo.cpp.html +// INDEX: tmp{{.*}}multiELFsMainTwo.cpp +// INDEX:
TOTALS
+// SourceSummary:
{{.*}}tmp{{.*}}multiELFsMainTwo.cpp (Binary: multiELFsMainTwo.covmapping)
Index: tools/llvm-cov/multiELFsShare.cpp =================================================================== --- /dev/null +++ tools/llvm-cov/multiELFsShare.cpp @@ -0,0 +1,52 @@ +// Tests for the shared file in the multiple ELFs. + +int foo(int x) { + if (x >= 100) { + x = x / 2; + x++; + } + else + x = x * 2; + return x; +} + +// RUN: llvm-profdata merge %S/Inputs/multiELFsMainOne.proftext -o %t_one.profdata +// RUN: llvm-profdata merge %S/Inputs/multiELFsMainTwo.proftext -o %t_two.profdata +// RUN: llvm-profdata merge %S/Inputs/allELFs.proftext -o %t.profdata +// RUN: llvm-cov show %S/Inputs/multiELFsMainOne.covmapping -bin=%S/Inputs/multiELFsMainTwo.covmapping -instr-profile %t_one.profdata -filename-equivalence %s 2>&1 | FileCheck -check-prefix=ElfOne %s +// ElfOne: [[@LINE-14]]| 1|int foo(int x) { +// ElfOne-NEXT: [[@LINE-14]]| 1| if (x >= 100) { +// ElfOne-NEXT: [[@LINE-14]]| 1| x = x / 2; +// ElfOne-NEXT: [[@LINE-14]]| 1| x++; +// ElfOne-NEXT: [[@LINE-14]]| 1| } +// ElfOne-NEXT: [[@LINE-14]]| 1| else +// ElfOne-NEXT: [[@LINE-14]]| 0| x = x * 2; +// ElfOne-NEXT: [[@LINE-14]]| 1| return x; +// ElfOne-NEXT: [[@LINE-14]]| 1|} +// RUN: llvm-cov show %S/Inputs/multiELFsMainOne.covmapping -bin=%S/Inputs/multiELFsMainTwo.covmapping -instr-profile %t_two.profdata -filename-equivalence %s 2>&1 | FileCheck -check-prefix=ElfTwo %s +// ElfTwo: [[@LINE-24]]| 1|int foo(int x) { +// ElfTwo-NEXT: [[@LINE-24]]| 1| if (x >= 100) { +// ElfTwo-NEXT: [[@LINE-24]]| 0| x = x / 2; +// ElfTwo-NEXT: [[@LINE-24]]| 0| x++; +// ElfTwo-NEXT: [[@LINE-24]]| 0| } +// ElfTwo-NEXT: [[@LINE-24]]| 1| else +// ElfTwo-NEXT: [[@LINE-24]]| 1| x = x * 2; +// ElfTwo-NEXT: [[@LINE-24]]| 1| return x; +// ElfTwo-NEXT: [[@LINE-24]]| 1|} +// RUN: llvm-cov show %S/Inputs/multiELFsMainOne.covmapping -bin=%S/Inputs/multiELFsMainTwo.covmapping -instr-profile %t.profdata -filename-equivalence %s 2>&1 | FileCheck -check-prefix=Elfs %s +// Elfs: [[@LINE-34]]| 2|int foo(int x) { +// Elfs-NEXT: [[@LINE-34]]| 2| if (x >= 100) { +// Elfs-NEXT: [[@LINE-34]]| 1| x = x / 2; +// Elfs-NEXT: [[@LINE-34]]| 1| x++; +// Elfs-NEXT: [[@LINE-34]]| 1| } +// Elfs-NEXT: [[@LINE-34]]| 2| else +// Elfs-NEXT: [[@LINE-34]]| 1| x = x * 2; +// Elfs-NEXT: [[@LINE-34]]| 2| return x; +// Elfs-NEXT: [[@LINE-34]]| 2|} + +// RUN: llvm-cov show %S/Inputs/multiELFsMainOne.covmapping -bin=%S/Inputs/multiELFsMainTwo.covmapping -format=html -o %t.html.dir -instr-profile %t.profdata -filename-equivalence %s 2>&1 +// RUN: FileCheck -check-prefixes=INDEX %s -input-file %t.html.dir/index.html +// RUN: FileCheck -check-prefixes=SourceSummary %s -input-file %t.html.dir/coverage/tmp/multiELFsShare.cpp.html +// INDEX: tmp{{.*}}multiELFsShare.cpp +// INDEX:
TOTALS
+// SourceSummary:
{{.*}}tmp{{.*}}multiELFsShare.cpp (Binary: multiELFsMainOne.covmapping, multiELFsMainTwo.covmapping)
\ No newline at end of file