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