Index: include/clang/Tooling/CommonOptionsParser.h =================================================================== --- include/clang/Tooling/CommonOptionsParser.h +++ include/clang/Tooling/CommonOptionsParser.h @@ -87,11 +87,12 @@ /// /// It also allows calls to set the required number of positional parameters. /// - /// This constructor exits program in case of error. + /// If \p ExitOnError is set (default), This constructor exits program in case + /// of error; otherwise, this sets the error flag and stores error messages. CommonOptionsParser(int &argc, const char **argv, llvm::cl::OptionCategory &Category, llvm::cl::NumOccurrencesFlag OccurrencesFlag, - const char *Overview = nullptr); + const char *Overview = nullptr, bool ExitOnError = true); /// Returns a reference to the loaded compilations database. CompilationDatabase &getCompilations() { @@ -103,9 +104,15 @@ return SourcePathList; } + bool hasError() const { return HasError; } + + const std::string &getErrorMessage() const { return ErrorMessage; } + static const char *const HelpMessage; private: + bool HasError; + std::string ErrorMessage; std::unique_ptr Compilations; std::vector SourcePathList; }; Index: include/clang/Tooling/Execution.h =================================================================== --- /dev/null +++ include/clang/Tooling/Execution.h @@ -0,0 +1,190 @@ +//===--- Execution.h - Executing clang frontend actions -*- C++ ---------*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines framework for executing clang frontend actions. +// +// The framework can be extended to support different execution plans including +// standalone execution on the given TUs or parallel execution on all TUs in +// the codebase. +// +// In order to enable multiprocessing execution, tool actions are expected to +// output result into the ToolResults provided by the executor. The +// `ToolResults` is an interface that abstracts how results are stored e.g. +// in-memory for standalone execution or on-disk for large-scale execution. +// +// New executors can be registered as ToolExecutorPlugins via the +// `ToolExecutorPluginRegistry`. CLI tools can use +// `createExecutorFromCommandLineArgs` to create a specific registered executor +// according to the command-line arguments. +// +// This is still experimental but expected to replace the existing `ClangTool` +// interface. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_EXECUTION_H +#define LLVM_CLANG_TOOLING_EXECUTION_H + +#include "clang/Tooling/Tooling.h" + +namespace clang { +namespace tooling { + +/// \brief An abstraction for the result of a tool execution. For example, the +/// underlying result can be in-memory or on-disk. +/// +/// Results should be string key-value pairs. For example, a refactoring tool +/// can use source location as key and a replacement in YAML format as value. +class ToolResults { +public: + virtual ~ToolResults() {} + virtual void addResult(llvm::StringRef Key, llvm::StringRef Value) = 0; + virtual std::vector> AllKVResults() = 0; + virtual void forEachResult( + llvm::function_ref + Callback) = 0; +}; + +/// \brief Specifies the actions to be executed in a tool execution and the +/// compilation arguments to be used. +struct ExecutionConfig { + ExecutionConfig() = default; + + // Config for executing a single `Action` with the default configuration e.g. + // `ClangStripOutputAdjuster` and `ClangSyntaxOnlyAdjuster`. + explicit ExecutionConfig(std::unique_ptr Action); + + // Config for executing multiple actions that use customized argument + // adjusters in addition to the default one. + std::vector>> + Actions; +}; + +/// \brief Interface for executing clang frontend actions. +/// +/// This can be extended to support running tool actions in different +/// execution mode, e.g. on a specific set of TUs or many TUs in parallel. +class ToolExecutor { +public: + virtual ~ToolExecutor() {} + + /// \brief Returns the name of a specific executor. + virtual llvm::StringRef getExecutorName() const = 0; + + /// \brief Executes actions specified in the execution configuration. + virtual llvm::Error Execute(const ExecutionConfig &Config) = 0; + + /// \brief Returns a reference of the tool results. + virtual ToolResults *getToolResults() = 0; + + /// \brief Map a virtual file to be used while running the tool. + /// + /// \param FilePath The path at which the content will be mapped. + /// \param Content A buffer of the file's content. + virtual void mapVirtualFile(StringRef FilePath, StringRef Content) = 0; +}; + +class InMemoryToolResults : public ToolResults { +public: + void addResult(llvm::StringRef Key, llvm::StringRef Value) override; + std::vector> AllKVResults() override; + void forEachResult(llvm::function_ref) override; + +private: + std::vector> KVResults; +}; + +/// \brief A stand alone executor that runs FrontendActions on a given set of +/// TUs in sequence. +class StandaloneToolExecutor : public ToolExecutor { +public: + static const char *ExecutorName; + + /// \brief Init with \p CompilationDatabase and the paths of all files to be + /// proccessed. + StandaloneToolExecutor( + const CompilationDatabase &Compilations, + llvm::ArrayRef SourcePaths, + std::shared_ptr PCHContainerOps = + std::make_shared()); + + /// \brief Init with \p CommonOptionsParser. This is expected to be used by + /// `createExecutorFromCommandLineArgs` based on commandline options. + /// + /// The executor takes ownership of \p Options. + StandaloneToolExecutor( + std::unique_ptr Options, + std::shared_ptr PCHContainerOps = + std::make_shared()); + + llvm::StringRef getExecutorName() const override { return ExecutorName; } + + llvm::Error Execute(const ExecutionConfig &Config) override; + + /// \brief Set a \c DiagnosticConsumer to use during parsing. + void setDiagnosticConsumer(DiagnosticConsumer *DiagConsumer) { + Tool.setDiagnosticConsumer(DiagConsumer); + } + + /// \brief Returns a reference to the tool results container. The results will + /// be populated after the execution. + ToolResults *getToolResults() override { return &Results; }; + + llvm::ArrayRef getSourcePaths() const { + return Tool.getSourcePaths(); + } + + void mapVirtualFile(StringRef FilePath, StringRef Content) override { + Tool.mapVirtualFile(FilePath, Content); + } + + /// \brief Returns the file manager used in the tool. + /// + /// The file manager is shared between all translation units. + FileManager &getFiles() { return Tool.getFiles(); } + +private: + // Used to store the parser when the executor is initialized with parser. + std::unique_ptr OptionsParser; + // FIXME: The standalone executor is currently just a wrapper of `ClangTool`. + // Merge `ClangTool` implementation into the this. + ClangTool Tool; + InMemoryToolResults Results; +}; + +/// \brief Interface for factories that create speicifc executors based on the +/// commandline arguments. This is also used as a plugin to be registered into +/// ToolExecutorPluginRegistry. +class ToolExecutorPlugin { +public: + virtual ~ToolExecutorPlugin() {} + + /// \brief Create a ToolExecutor based on the commandline arguments. If the + /// set of commandline arguments is not supported by the ToolExecutor, this + /// returns an error. + /// Expected commandline options should be registered in the implementation. + virtual llvm::Expected> + create(int &argc, const char **argv, llvm::cl::OptionCategory &Category) = 0; +}; + +/// \brief This creates a ToolExecutor that is in the global registry and +/// supports the given commandline arguments. +/// +/// By default, this creates a `StandaloneToolExecutor` if not other tool +/// flags are used. +llvm::Expected> +createExecutorFromCommandLineArgs(int &argc, const char **argv, + llvm::cl::OptionCategory &Category, + const char *Overview = nullptr); + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_EXECUTION_H Index: include/clang/Tooling/ToolExecutorPluginRegistry.h =================================================================== --- /dev/null +++ include/clang/Tooling/ToolExecutorPluginRegistry.h @@ -0,0 +1,24 @@ +//===--- ToolExecutorPluginRegistry.h - -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_TOOLEXECUTORPLUGINREGISTRY_H +#define LLVM_CLANG_TOOLING_TOOLEXECUTORPLUGINREGISTRY_H + +#include "clang/Tooling/Execution.h" +#include "llvm/Support/Registry.h" + +namespace clang { +namespace tooling { + +typedef llvm::Registry ToolExecutorPluginRegistry; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_TOOLEXECUTORPLUGINREGISTRY_H Index: include/clang/Tooling/Tooling.h =================================================================== --- include/clang/Tooling/Tooling.h +++ include/clang/Tooling/Tooling.h @@ -31,18 +31,21 @@ #define LLVM_CLANG_TOOLING_TOOLING_H #include "clang/AST/ASTConsumer.h" -#include "clang/Frontend/PCHContainerOperations.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/LLVM.h" #include "clang/Driver/Util.h" #include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/PCHContainerOperations.h" #include "clang/Lex/ModuleLoader.h" #include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/Twine.h" #include "llvm/Option/Option.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Registry.h" #include #include #include @@ -337,7 +340,9 @@ /// The file manager is shared between all translation units. FileManager &getFiles() { return *Files; } - private: + llvm::ArrayRef getSourcePaths() const { return SourcePaths; } + +private: const CompilationDatabase &Compilations; std::vector SourcePaths; std::shared_ptr PCHContainerOps; Index: lib/Tooling/CMakeLists.txt =================================================================== --- lib/Tooling/CMakeLists.txt +++ lib/Tooling/CMakeLists.txt @@ -11,6 +11,7 @@ ArgumentsAdjusters.cpp CommonOptionsParser.cpp CompilationDatabase.cpp + Execution.cpp FileMatchTrie.cpp FixIt.cpp JSONCompilationDatabase.cpp Index: lib/Tooling/CommonOptionsParser.cpp =================================================================== --- lib/Tooling/CommonOptionsParser.cpp +++ lib/Tooling/CommonOptionsParser.cpp @@ -83,7 +83,8 @@ CommonOptionsParser::CommonOptionsParser( int &argc, const char **argv, cl::OptionCategory &Category, - llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) { + llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview, + bool ExitOnError) { static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden, cl::sub(*cl::AllSubCommands)); @@ -105,14 +106,21 @@ cl::desc("Additional argument to prepend to the compiler command line"), cl::cat(Category), cl::sub(*cl::AllSubCommands)); + cl::ResetAllOptionOccurrences(); + cl::HideUnrelatedOptions(Category); - std::string ErrorMessage; - Compilations = - FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage); + std::string Err; + Compilations = FixedCompilationDatabase::loadFromCommandLine(argc, argv, Err); + llvm::raw_string_ostream OS(ErrorMessage); if (!Compilations && !ErrorMessage.empty()) - llvm::errs() << ErrorMessage; - cl::ParseCommandLineOptions(argc, argv, Overview); + OS << Err << "\n"; + HasError = !cl::ParseCommandLineOptions(argc, argv, Overview, &OS); + assert(!(ExitOnError && HasError) && + "Command parsing failed in common option parser."); + // Stop initializing if command-line option parsing failed. + if (HasError) + return; cl::PrintOptionValues(); SourcePathList = SourcePaths; Index: lib/Tooling/Execution.cpp =================================================================== --- /dev/null +++ lib/Tooling/Execution.cpp @@ -0,0 +1,132 @@ +//===- lib/Tooling/Execution.cpp - Implements tool execution framework. ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/ToolExecutorPluginRegistry.h" +#include "clang/Tooling/Tooling.h" + +LLVM_INSTANTIATE_REGISTRY(clang::tooling::ToolExecutorPluginRegistry) + +namespace clang { +namespace tooling { + +static llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error(Message, + llvm::inconvertibleErrorCode()); +} + +void InMemoryToolResults::addResult(llvm::StringRef Key, + llvm::StringRef Value) { + KVResults.push_back({Key.str(), Value.str()}); +} + +std::vector> +InMemoryToolResults::AllKVResults() { + return KVResults; +} + +void InMemoryToolResults::forEachResult( + llvm::function_ref + Callback) { + for (const auto &KV : KVResults) { + Callback(KV.first, KV.second); + } +} + +ExecutionConfig::ExecutionConfig(std::unique_ptr Action) { + Actions.push_back(std::make_pair(nullptr, std::move(Action))); +} + +llvm::Expected> +createExecutorFromCommandLineArgs(int &argc, const char **argv, + llvm::cl::OptionCategory &Category, + const char *Overview) { + std::string ErrorMessage; + llvm::raw_string_ostream OS(ErrorMessage); + for (auto I = ToolExecutorPluginRegistry::begin(), + E = ToolExecutorPluginRegistry::end(); + I != E; ++I) { + std::unique_ptr Plugin(I->instantiate()); + std::vector args(argv, argv + argc); + int arg_num = argc; + llvm::Expected> Executor = + Plugin->create(arg_num, argv, Category); + if (!Executor) { + OS << "Failed to create '" << I->getName() + << "': " << llvm::toString(Executor.takeError()) << "\n"; + llvm::cl::ResetAllOptionOccurrences(); + continue; + } + argc = arg_num; + return std::move(*Executor); + } + OS.flush(); + return make_string_error(ErrorMessage); +} + +const char *StandaloneToolExecutor::ExecutorName = "StandaloneToolExector"; + +StandaloneToolExecutor::StandaloneToolExecutor( + const CompilationDatabase &Compilations, + llvm::ArrayRef SourcePaths, + std::shared_ptr PCHContainerOps) + : Tool(Compilations, SourcePaths) {} + +StandaloneToolExecutor::StandaloneToolExecutor( + std::unique_ptr Options, + std::shared_ptr PCHContainerOps) + : OptionsParser(std::move(Options)), + Tool(OptionsParser->getCompilations(), OptionsParser->getSourcePathList(), + PCHContainerOps) {} + +llvm::Error StandaloneToolExecutor::Execute(const ExecutionConfig &Config) { + if (Config.Actions.empty()) + return make_string_error("No action to execute."); + + if (Config.Actions.size() != 1) + return make_string_error( + "Only support executing exactly 1 ToolAction at this point."); + + auto &Action = Config.Actions.front(); + if (Action.first) + Tool.appendArgumentsAdjuster(Action.first); + if (int Ret = Tool.run(Action.second.get())) + return make_string_error("Failed to run ToolAction."); + + return llvm::Error::success(); +} + +class StandaloneToolExecutorPlugin : public ToolExecutorPlugin { +public: + llvm::Expected> + create(int &argc, const char **argv, + llvm::cl::OptionCategory &Category) override { + auto OptionsParser = llvm::make_unique( + argc, argv, Category, llvm::cl::OneOrMore, /*Overview=*/nullptr, + /*ExitOnError=*/false); + if (OptionsParser->hasError()) + return make_string_error("[StandaloneToolExecutorPlugin] " + + OptionsParser->getErrorMessage()); + if (OptionsParser->getSourcePathList().empty()) + return make_string_error( + "[StandaloneToolExecutorPlugin] No positional argument found."); + return llvm::make_unique(std::move(OptionsParser)); + } +}; + +// This anchor is used to force the linker to link in the generated object file +// and thus register the plugin. +volatile int ToolExecutorPluginAnchorSource = 0; + +static ToolExecutorPluginRegistry::Add + X("standalone-executor", + "Runs ToolActions on a set of files provided via positional arguments."); + +} // end namespace tooling +} // end namespace clang Index: lib/Tooling/Tooling.cpp =================================================================== --- lib/Tooling/Tooling.cpp +++ lib/Tooling/Tooling.cpp @@ -29,6 +29,7 @@ #include "llvm/Config/llvm-config.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" +#include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Host.h" Index: unittests/Tooling/ToolingTest.cpp =================================================================== --- unittests/Tooling/ToolingTest.cpp +++ unittests/Tooling/ToolingTest.cpp @@ -7,14 +7,18 @@ // //===----------------------------------------------------------------------===// +#include "gmock/gmock.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclGroup.h" +#include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/ToolExecutorPluginRegistry.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Config/llvm-config.h" @@ -25,6 +29,8 @@ #include #include +using testing::ElementsAre; + namespace clang { namespace tooling { @@ -60,6 +66,60 @@ private: bool * const FoundTopLevelDecl; }; + +// This traverses the AST and outputs function name as key and "1" as value for +// each function declaration. +class ASTConsumerWithResult + : public ASTConsumer, + public RecursiveASTVisitor { +public: + using ASTVisitor = RecursiveASTVisitor; + + explicit ASTConsumerWithResult(ToolResults *Results) : Results(Results) { + assert(Results != nullptr); + } + + void HandleTranslationUnit(clang::ASTContext &Context) override { + TraverseDecl(Context.getTranslationUnitDecl()); + } + + bool TraverseFunctionDecl(clang::FunctionDecl *Decl) { + Results->addResult(Decl->getNameAsString(), "1"); + return ASTVisitor::TraverseFunctionDecl(Decl); + } + +private: + ToolResults *const Results; +}; + +class ReportResultAction : public ASTFrontendAction { +public: + explicit ReportResultAction(ToolResults *Results) : Results(Results) { + assert(Results != nullptr); + } + +protected: + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &compiler, + llvm::StringRef /* dummy */) override { + std::unique_ptr ast_consumer{ + new ASTConsumerWithResult(Results)}; + return ast_consumer; + } + +private: + ToolResults *const Results; +}; + +class ReportResultActionFactory : public FrontendActionFactory { +public: + ReportResultActionFactory(ToolResults *Results) : Results(Results) {} + FrontendAction *create() override { return new ReportResultAction(Results); } + +private: + ToolResults *const Results; +}; + } // end namespace TEST(runToolOnCode, FindsNoTopLevelDeclOnEmptyCode) { @@ -533,5 +593,168 @@ } #endif +inline llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error(Message, + llvm::inconvertibleErrorCode()); +} + +class TestToolExecutor : public ToolExecutor { +public: + static const char *ExecutorName; + + TestToolExecutor(std::unique_ptr Options) + : OptionsParser(std::move(Options)) {} + + llvm::StringRef getExecutorName() const override { return ExecutorName; } + + llvm::Error Execute(const ExecutionConfig &) override { + return llvm::Error::success(); + } + + ToolResults *getToolResults() override { return nullptr; }; + + llvm::ArrayRef getSourcePaths() const { + return OptionsParser->getSourcePathList(); + } + + void mapVirtualFile(StringRef FilePath, StringRef Content) override { + VFS[FilePath] = Content; + } + +private: + std::unique_ptr OptionsParser; + std::string SourcePaths; + std::map VFS; +}; + +const char *TestToolExecutor::ExecutorName = "TestToolExecutor"; + +class TestToolExecutorPlugin : public ToolExecutorPlugin { +public: + llvm::Expected> + create(int &argc, const char **argv, + llvm::cl::OptionCategory &Category) override { + static llvm::cl::opt TestExecutor( + "test_executor", llvm::cl::desc("Use TestToolExecutor")); + auto OptionsParser = llvm::make_unique( + argc, argv, Category, llvm::cl::OneOrMore, /*Overview=*/nullptr, + /*ExitOnError=*/false); + if (OptionsParser->hasError()) + return make_string_error("[TestToolExecutorPlugin] " + + OptionsParser->getErrorMessage()); + if (!TestExecutor) + return make_string_error( + "[TestToolExecutorPlugin] --test_executor is not set."); + return llvm::make_unique(std::move(OptionsParser)); + } +}; + +// This anchor is used to force the linker to link in the generated object file +// and thus register the plugin. +extern volatile int ToolExecutorPluginAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED TestToolExecutorPluginAnchorDest = + ToolExecutorPluginAnchorSource; + +static ToolExecutorPluginRegistry::Add + X("test-executor", "Plugin for TestToolExecutor."); + +TEST(CreateToolExecutorTest, FailedCreateExecutorUndefinedFlag) { + llvm::cl::ResetCommandLineParser(); + llvm::cl::OptionCategory TestCategory("tooling-test options"); + std::vector argv = {"prog", "--fake_flag_no_no_no", "f"}; + int argc = argv.size(); + auto Executor = + createExecutorFromCommandLineArgs(argc, &argv[0], TestCategory); + ASSERT_FALSE((bool)Executor); + llvm::consumeError(Executor.takeError()); +} + +TEST(CreateToolExecutorTest, RegisterFlagsBeforeReset) { + llvm::cl::ResetCommandLineParser(); + + llvm::cl::opt BeforeReset( + "before_reset", llvm::cl::desc("Defined before reset."), + llvm::cl::init("")); + llvm::cl::OptionCategory TestCategory("tooling-test options"); + + llvm::cl::ResetAllOptionOccurrences(); + + std::vector argv = {"prog", "--before_reset=set", "f"}; + int argc = argv.size(); + auto Executor = + createExecutorFromCommandLineArgs(argc, &argv[0], TestCategory); + ASSERT_TRUE((bool)Executor); + EXPECT_EQ(BeforeReset, "set"); +} + +TEST(CreateToolExecutorTest, CreateStandaloneToolExecutor) { + llvm::cl::ResetCommandLineParser(); + llvm::cl::OptionCategory TestCategory("tooling-test options"); + std::vector argv = {"prog", "standalone.cpp"}; + int argc = argv.size(); + auto Executor = + createExecutorFromCommandLineArgs(argc, &argv[0], TestCategory); + ASSERT_TRUE((bool)Executor); + EXPECT_EQ(Executor->get()->getExecutorName(), + StandaloneToolExecutor::ExecutorName); +} + +TEST(CreateToolExecutorTest, CreateTestToolExecutor) { + llvm::cl::ResetCommandLineParser(); + llvm::cl::OptionCategory TestCategory("tooling-test options"); + std::vector argv = {"prog", "test.cpp", "--test_executor"}; + int argc = argv.size(); + auto Executor = + createExecutorFromCommandLineArgs(argc, &argv[0], TestCategory); + ASSERT_TRUE((bool)Executor); + EXPECT_EQ(Executor->get()->getExecutorName(), TestToolExecutor::ExecutorName); +} + +TEST(StandaloneToolTest, SynctaxOnlyActionOnSimpleCode) { + FixedCompilationDatabase Compilations("/", std::vector()); + StandaloneToolExecutor Executor(Compilations, + std::vector(1, "/a.cc")); + Executor.mapVirtualFile("/a.cc", "int x = 0;"); + + auto Err = Executor.Execute( + ExecutionConfig(newFrontendActionFactory())); + ASSERT_TRUE(!Err); +} + +TEST(StandaloneToolTest, SimpleAction) { + FixedCompilationDatabase Compilations("/", std::vector()); + StandaloneToolExecutor Executor(Compilations, + std::vector(1, "/a.cc")); + Executor.mapVirtualFile("/a.cc", "int x = 0;"); + + ToolResults *Results = Executor.getToolResults(); + + auto Err = Executor.Execute(ExecutionConfig( + std::unique_ptr(new ReportResultActionFactory(Results)))); + ASSERT_TRUE(!Err); + auto KVs = Results->AllKVResults(); + ASSERT_EQ(KVs.size(), 0u); +} + +TEST(StandaloneToolTest, SimpleActionWithResult) { + FixedCompilationDatabase Compilations("/", std::vector()); + StandaloneToolExecutor Executor(Compilations, + std::vector(1, "/a.cc")); + Executor.mapVirtualFile("/a.cc", "int x = 0; void f() {}"); + + ToolResults *Results = Executor.getToolResults(); + + auto Err = Executor.Execute(ExecutionConfig( + std::unique_ptr(new ReportResultActionFactory(Results)))); + ASSERT_TRUE(!Err); + auto KVs = Results->AllKVResults(); + ASSERT_EQ(KVs.size(), 1u); + EXPECT_EQ("f", KVs[0].first); + EXPECT_EQ("1", KVs[0].second); + + Results->forEachResult( + [](llvm::StringRef, llvm::StringRef Value) { EXPECT_EQ("1", Value); }); +} + } // end namespace tooling } // end namespace clang