xref: /llvm-project/clang/unittests/Tooling/ExecutionTest.cpp (revision 4e600751d2f7e8e7b85a71b7128b68444bdde91b)
1 //===- unittest/Tooling/ExecutionTest.cpp - Tool execution tests. --------===//
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-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "clang/Tooling/Execution.h"
10 #include "clang/AST/ASTConsumer.h"
11 #include "clang/AST/DeclCXX.h"
12 #include "clang/AST/DynamicRecursiveASTVisitor.h"
13 #include "clang/Frontend/ASTUnit.h"
14 #include "clang/Frontend/FrontendAction.h"
15 #include "clang/Frontend/FrontendActions.h"
16 #include "clang/Tooling/AllTUsExecution.h"
17 #include "clang/Tooling/CompilationDatabase.h"
18 #include "clang/Tooling/StandaloneExecution.h"
19 #include "clang/Tooling/ToolExecutorPluginRegistry.h"
20 #include "clang/Tooling/Tooling.h"
21 #include "gmock/gmock.h"
22 #include "gtest/gtest.h"
23 #include <algorithm>
24 #include <string>
25 
26 namespace clang {
27 namespace tooling {
28 
29 namespace {
30 
31 // This traverses the AST and outputs function name as key and "1" as value for
32 // each function declaration.
33 class ASTConsumerWithResult : public ASTConsumer,
34                               public DynamicRecursiveASTVisitor {
35 public:
36   explicit ASTConsumerWithResult(ExecutionContext *Context) : Context(Context) {
37     assert(Context != nullptr);
38   }
39 
40   void HandleTranslationUnit(clang::ASTContext &Context) override {
41     TraverseDecl(Context.getTranslationUnitDecl());
42   }
43 
44   bool TraverseFunctionDecl(clang::FunctionDecl *Decl) override {
45     Context->reportResult(Decl->getNameAsString(),
46                           Context->getRevision() + ":" + Context->getCorpus() +
47                               ":" + Context->getCurrentCompilationUnit() +
48                               "/1");
49     return DynamicRecursiveASTVisitor::TraverseFunctionDecl(Decl);
50   }
51 
52 private:
53   ExecutionContext *const Context;
54 };
55 
56 class ReportResultAction : public ASTFrontendAction {
57 public:
58   explicit ReportResultAction(ExecutionContext *Context) : Context(Context) {
59     assert(Context != nullptr);
60   }
61 
62 protected:
63   std::unique_ptr<clang::ASTConsumer>
64   CreateASTConsumer(clang::CompilerInstance &compiler,
65                     StringRef /* dummy */) override {
66     std::unique_ptr<clang::ASTConsumer> ast_consumer{
67         new ASTConsumerWithResult(Context)};
68     return ast_consumer;
69   }
70 
71 private:
72   ExecutionContext *const Context;
73 };
74 
75 class ReportResultActionFactory : public FrontendActionFactory {
76 public:
77   ReportResultActionFactory(ExecutionContext *Context) : Context(Context) {}
78   std::unique_ptr<FrontendAction> create() override {
79     return std::make_unique<ReportResultAction>(Context);
80   }
81 
82 private:
83   ExecutionContext *const Context;
84 };
85 
86 } // namespace
87 
88 class TestToolExecutor : public ToolExecutor {
89 public:
90   static const char *ExecutorName;
91 
92   TestToolExecutor(CommonOptionsParser Options)
93       : OptionsParser(std::move(Options)) {}
94 
95   StringRef getExecutorName() const override { return ExecutorName; }
96 
97   llvm::Error
98   execute(llvm::ArrayRef<std::pair<std::unique_ptr<FrontendActionFactory>,
99                                    ArgumentsAdjuster>>) override {
100     return llvm::Error::success();
101   }
102 
103   ExecutionContext *getExecutionContext() override { return nullptr; };
104 
105   ToolResults *getToolResults() override { return nullptr; }
106 
107   llvm::ArrayRef<std::string> getSourcePaths() const {
108     return OptionsParser.getSourcePathList();
109   }
110 
111   void mapVirtualFile(StringRef FilePath, StringRef Content) override {
112     VFS[std::string(FilePath)] = std::string(Content);
113   }
114 
115 private:
116   CommonOptionsParser OptionsParser;
117   std::string SourcePaths;
118   std::map<std::string, std::string> VFS;
119 };
120 
121 const char *TestToolExecutor::ExecutorName = "test-executor";
122 
123 class TestToolExecutorPlugin : public ToolExecutorPlugin {
124 public:
125   llvm::Expected<std::unique_ptr<ToolExecutor>>
126   create(CommonOptionsParser &OptionsParser) override {
127     return std::make_unique<TestToolExecutor>(std::move(OptionsParser));
128   }
129 };
130 
131 static ToolExecutorPluginRegistry::Add<TestToolExecutorPlugin>
132     X("test-executor", "Plugin for TestToolExecutor.");
133 
134 llvm::cl::OptionCategory TestCategory("execution-test options");
135 
136 TEST(CreateToolExecutorTest, FailedCreateExecutorUndefinedFlag) {
137   std::vector<const char *> argv = {"prog", "--fake_flag_no_no_no", "f"};
138   int argc = argv.size();
139   auto Executor = internal::createExecutorFromCommandLineArgsImpl(
140       argc, &argv[0], TestCategory);
141   ASSERT_FALSE((bool)Executor);
142   llvm::consumeError(Executor.takeError());
143 }
144 
145 TEST(CreateToolExecutorTest, RegisterFlagsBeforeReset) {
146   llvm::cl::opt<std::string> BeforeReset(
147       "before_reset", llvm::cl::desc("Defined before reset."),
148       llvm::cl::init(""));
149 
150   llvm::cl::ResetAllOptionOccurrences();
151 
152   std::vector<const char *> argv = {"prog", "--before_reset=set", "f"};
153   int argc = argv.size();
154   auto Executor = internal::createExecutorFromCommandLineArgsImpl(
155       argc, &argv[0], TestCategory);
156   ASSERT_TRUE((bool)Executor);
157   EXPECT_EQ(BeforeReset, "set");
158   BeforeReset.removeArgument();
159 }
160 
161 TEST(CreateToolExecutorTest, CreateStandaloneToolExecutor) {
162   std::vector<const char *> argv = {"prog", "standalone.cpp"};
163   int argc = argv.size();
164   auto Executor = internal::createExecutorFromCommandLineArgsImpl(
165       argc, &argv[0], TestCategory);
166   ASSERT_TRUE((bool)Executor);
167   EXPECT_EQ(Executor->get()->getExecutorName(),
168             StandaloneToolExecutor::ExecutorName);
169 }
170 
171 TEST(CreateToolExecutorTest, CreateTestToolExecutor) {
172   std::vector<const char *> argv = {"prog", "test.cpp",
173                                     "--executor=test-executor"};
174   int argc = argv.size();
175   auto Executor = internal::createExecutorFromCommandLineArgsImpl(
176       argc, &argv[0], TestCategory);
177   ASSERT_TRUE((bool)Executor);
178   EXPECT_EQ(Executor->get()->getExecutorName(), TestToolExecutor::ExecutorName);
179 }
180 
181 TEST(StandaloneToolTest, SynctaxOnlyActionOnSimpleCode) {
182   FixedCompilationDatabase Compilations(".", std::vector<std::string>());
183   StandaloneToolExecutor Executor(Compilations,
184                                   std::vector<std::string>(1, "a.cc"));
185   Executor.mapVirtualFile("a.cc", "int x = 0;");
186 
187   auto Err = Executor.execute(newFrontendActionFactory<SyntaxOnlyAction>(),
188                               getClangSyntaxOnlyAdjuster());
189   ASSERT_TRUE(!Err);
190 }
191 
192 TEST(StandaloneToolTest, SimpleAction) {
193   FixedCompilationDatabase Compilations(".", std::vector<std::string>());
194   StandaloneToolExecutor Executor(Compilations,
195                                   std::vector<std::string>(1, "a.cc"));
196   Executor.mapVirtualFile("a.cc", "int x = 0;");
197 
198   auto Err = Executor.execute(std::unique_ptr<FrontendActionFactory>(
199       new ReportResultActionFactory(Executor.getExecutionContext())));
200   ASSERT_TRUE(!Err);
201   auto KVs = Executor.getToolResults()->AllKVResults();
202   ASSERT_EQ(KVs.size(), 0u);
203 }
204 
205 TEST(StandaloneToolTest, SimpleActionWithResult) {
206   FixedCompilationDatabase Compilations(".", std::vector<std::string>());
207   StandaloneToolExecutor Executor(Compilations,
208                                   std::vector<std::string>(1, "a.cc"));
209   Executor.mapVirtualFile("a.cc", "int x = 0; void f() {}");
210 
211   auto Err = Executor.execute(std::unique_ptr<FrontendActionFactory>(
212       new ReportResultActionFactory(Executor.getExecutionContext())));
213   ASSERT_TRUE(!Err);
214   auto KVs = Executor.getToolResults()->AllKVResults();
215   ASSERT_EQ(KVs.size(), 1u);
216   EXPECT_EQ("f", KVs[0].first);
217   // Currently the standlone executor returns empty corpus, revision, and
218   // compilation unit.
219   EXPECT_EQ("::/1", KVs[0].second);
220 
221   Executor.getToolResults()->forEachResult(
222       [](StringRef, StringRef Value) { EXPECT_EQ("::/1", Value); });
223 }
224 
225 class FixedCompilationDatabaseWithFiles : public CompilationDatabase {
226 public:
227   FixedCompilationDatabaseWithFiles(Twine Directory,
228                                     ArrayRef<std::string> Files,
229                                     ArrayRef<std::string> CommandLine)
230       : FixedCompilations(Directory, CommandLine), Files(Files) {}
231 
232   std::vector<CompileCommand>
233   getCompileCommands(StringRef FilePath) const override {
234     return FixedCompilations.getCompileCommands(FilePath);
235   }
236 
237   std::vector<std::string> getAllFiles() const override { return Files; }
238 
239 private:
240   FixedCompilationDatabase FixedCompilations;
241   std::vector<std::string> Files;
242 };
243 
244 MATCHER_P(Named, Name, "") { return arg.first == Name; }
245 
246 TEST(AllTUsToolTest, AFewFiles) {
247   FixedCompilationDatabaseWithFiles Compilations(
248       ".", {"a.cc", "b.cc", "c.cc", "ignore.cc"}, std::vector<std::string>());
249   AllTUsToolExecutor Executor(Compilations, /*ThreadCount=*/0);
250   Filter.setValue("[a-c].cc");
251   Executor.mapVirtualFile("a.cc", "void x() {}");
252   Executor.mapVirtualFile("b.cc", "void y() {}");
253   Executor.mapVirtualFile("c.cc", "void z() {}");
254   Executor.mapVirtualFile("ignore.cc", "void d() {}");
255 
256   auto Err = Executor.execute(std::unique_ptr<FrontendActionFactory>(
257       new ReportResultActionFactory(Executor.getExecutionContext())));
258   ASSERT_TRUE(!Err);
259   EXPECT_THAT(
260       Executor.getToolResults()->AllKVResults(),
261       ::testing::UnorderedElementsAre(Named("x"), Named("y"), Named("z")));
262   Filter.setValue(".*"); // reset to default value.
263 }
264 
265 TEST(AllTUsToolTest, ManyFiles) {
266   unsigned NumFiles = 100;
267   std::vector<std::string> Files;
268   std::map<std::string, std::string> FileToContent;
269   std::vector<std::string> ExpectedSymbols;
270   for (unsigned i = 1; i <= NumFiles; ++i) {
271     std::string File = "f" + std::to_string(i) + ".cc";
272     std::string Symbol = "looong_function_name_" + std::to_string(i);
273     Files.push_back(File);
274     FileToContent[File] = "void " + Symbol + "() {}";
275     ExpectedSymbols.push_back(Symbol);
276   }
277   FixedCompilationDatabaseWithFiles Compilations(".", Files,
278                                                  std::vector<std::string>());
279   AllTUsToolExecutor Executor(Compilations, /*ThreadCount=*/0);
280   for (const auto &FileAndContent : FileToContent) {
281     Executor.mapVirtualFile(FileAndContent.first, FileAndContent.second);
282   }
283 
284   auto Err = Executor.execute(std::unique_ptr<FrontendActionFactory>(
285       new ReportResultActionFactory(Executor.getExecutionContext())));
286   ASSERT_TRUE(!Err);
287   std::vector<std::string> Results;
288   Executor.getToolResults()->forEachResult(
289       [&](StringRef Name, StringRef) { Results.push_back(std::string(Name)); });
290   EXPECT_THAT(ExpectedSymbols, ::testing::UnorderedElementsAreArray(Results));
291 }
292 
293 } // end namespace tooling
294 } // end namespace clang
295