1 | //===- DependencyScannerTest.cpp ------------------------------------------===// |
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/AST/ASTConsumer.h" |
10 | #include "clang/AST/DeclCXX.h" |
11 | #include "clang/AST/DeclGroup.h" |
12 | #include "clang/Frontend/ASTUnit.h" |
13 | #include "clang/Frontend/CompilerInstance.h" |
14 | #include "clang/Frontend/FrontendAction.h" |
15 | #include "clang/Frontend/FrontendActions.h" |
16 | #include "clang/Tooling/CompilationDatabase.h" |
17 | #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" |
18 | #include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" |
19 | #include "clang/Tooling/Tooling.h" |
20 | #include "llvm/ADT/STLExtras.h" |
21 | #include "llvm/MC/TargetRegistry.h" |
22 | #include "llvm/Support/FormatVariadic.h" |
23 | #include "llvm/Support/Path.h" |
24 | #include "llvm/Support/TargetSelect.h" |
25 | #include "llvm/Testing/Support/Error.h" |
26 | #include "gtest/gtest.h" |
27 | #include <algorithm> |
28 | #include <string> |
29 | |
30 | using namespace clang; |
31 | using namespace tooling; |
32 | using namespace dependencies; |
33 | |
34 | namespace { |
35 | |
36 | /// Prints out all of the gathered dependencies into a string. |
37 | class TestFileCollector : public DependencyFileGenerator { |
38 | public: |
39 | TestFileCollector(DependencyOutputOptions &Opts, |
40 | std::vector<std::string> &Deps) |
41 | : DependencyFileGenerator(Opts), Deps(Deps) {} |
42 | |
43 | void finishedMainFile(DiagnosticsEngine &Diags) override { |
44 | auto NewDeps = getDependencies(); |
45 | llvm::append_range(C&: Deps, R&: NewDeps); |
46 | } |
47 | |
48 | private: |
49 | std::vector<std::string> &Deps; |
50 | }; |
51 | |
52 | class TestDependencyScanningAction : public tooling::ToolAction { |
53 | public: |
54 | TestDependencyScanningAction(std::vector<std::string> &Deps) : Deps(Deps) {} |
55 | |
56 | bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, |
57 | FileManager *FileMgr, |
58 | std::shared_ptr<PCHContainerOperations> PCHContainerOps, |
59 | DiagnosticConsumer *DiagConsumer) override { |
60 | CompilerInstance Compiler(std::move(Invocation), |
61 | std::move(PCHContainerOps)); |
62 | Compiler.setFileManager(FileMgr); |
63 | |
64 | Compiler.createDiagnostics(VFS&: FileMgr->getVirtualFileSystem(), Client: DiagConsumer, |
65 | /*ShouldOwnClient=*/false); |
66 | if (!Compiler.hasDiagnostics()) |
67 | return false; |
68 | |
69 | Compiler.createSourceManager(FileMgr&: *FileMgr); |
70 | Compiler.addDependencyCollector(Listener: std::make_shared<TestFileCollector>( |
71 | args&: Compiler.getInvocation().getDependencyOutputOpts(), args&: Deps)); |
72 | |
73 | auto Action = std::make_unique<PreprocessOnlyAction>(); |
74 | return Compiler.ExecuteAction(Act&: *Action); |
75 | } |
76 | |
77 | private: |
78 | std::vector<std::string> &Deps; |
79 | }; |
80 | |
81 | } // namespace |
82 | |
83 | TEST(DependencyScanner, ScanDepsReuseFilemanager) { |
84 | std::vector<std::string> Compilation = {"-c" , "-E" , "-MT" , "test.cpp.o" }; |
85 | StringRef CWD = "/root" ; |
86 | FixedCompilationDatabase CDB(CWD, Compilation); |
87 | |
88 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
89 | VFS->setCurrentWorkingDirectory(CWD); |
90 | auto Sept = llvm::sys::path::get_separator(); |
91 | std::string = |
92 | std::string(llvm::formatv(Fmt: "{0}root{0}header.h" , Vals&: Sept)); |
93 | std::string SymlinkPath = |
94 | std::string(llvm::formatv(Fmt: "{0}root{0}symlink.h" , Vals&: Sept)); |
95 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.cpp" , Vals&: Sept)); |
96 | |
97 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
98 | VFS->addHardLink(NewLink: SymlinkPath, Target: HeaderPath); |
99 | VFS->addFile(Path: TestPath, ModificationTime: 0, |
100 | Buffer: llvm::MemoryBuffer::getMemBuffer( |
101 | InputData: "#include \"symlink.h\"\n#include \"header.h\"\n" )); |
102 | |
103 | ClangTool Tool(CDB, {"test.cpp" }, std::make_shared<PCHContainerOperations>(), |
104 | VFS); |
105 | Tool.clearArgumentsAdjusters(); |
106 | std::vector<std::string> Deps; |
107 | TestDependencyScanningAction Action(Deps); |
108 | Tool.run(Action: &Action); |
109 | using llvm::sys::path::convert_to_slash; |
110 | // The first invocation should return dependencies in order of access. |
111 | ASSERT_EQ(Deps.size(), 3u); |
112 | EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp" ); |
113 | EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h" ); |
114 | EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h" ); |
115 | |
116 | // The file manager should still have two FileEntries, as one file is a |
117 | // hardlink. |
118 | FileManager &Files = Tool.getFiles(); |
119 | EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u); |
120 | |
121 | Deps.clear(); |
122 | Tool.run(Action: &Action); |
123 | // The second invocation should have the same order of dependencies. |
124 | ASSERT_EQ(Deps.size(), 3u); |
125 | EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp" ); |
126 | EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h" ); |
127 | EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h" ); |
128 | |
129 | EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u); |
130 | } |
131 | |
132 | TEST(DependencyScanner, ScanDepsReuseFilemanagerSkippedFile) { |
133 | std::vector<std::string> Compilation = {"-c" , "-E" , "-MT" , "test.cpp.o" }; |
134 | StringRef CWD = "/root" ; |
135 | FixedCompilationDatabase CDB(CWD, Compilation); |
136 | |
137 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
138 | VFS->setCurrentWorkingDirectory(CWD); |
139 | auto Sept = llvm::sys::path::get_separator(); |
140 | std::string = |
141 | std::string(llvm::formatv(Fmt: "{0}root{0}header.h" , Vals&: Sept)); |
142 | std::string SymlinkPath = |
143 | std::string(llvm::formatv(Fmt: "{0}root{0}symlink.h" , Vals&: Sept)); |
144 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.cpp" , Vals&: Sept)); |
145 | std::string Test2Path = |
146 | std::string(llvm::formatv(Fmt: "{0}root{0}test2.cpp" , Vals&: Sept)); |
147 | |
148 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, |
149 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "#pragma once\n" )); |
150 | VFS->addHardLink(NewLink: SymlinkPath, Target: HeaderPath); |
151 | VFS->addFile(Path: TestPath, ModificationTime: 0, |
152 | Buffer: llvm::MemoryBuffer::getMemBuffer( |
153 | InputData: "#include \"header.h\"\n#include \"symlink.h\"\n" )); |
154 | VFS->addFile(Path: Test2Path, ModificationTime: 0, |
155 | Buffer: llvm::MemoryBuffer::getMemBuffer( |
156 | InputData: "#include \"symlink.h\"\n#include \"header.h\"\n" )); |
157 | |
158 | ClangTool Tool(CDB, {"test.cpp" , "test2.cpp" }, |
159 | std::make_shared<PCHContainerOperations>(), VFS); |
160 | Tool.clearArgumentsAdjusters(); |
161 | std::vector<std::string> Deps; |
162 | TestDependencyScanningAction Action(Deps); |
163 | Tool.run(Action: &Action); |
164 | using llvm::sys::path::convert_to_slash; |
165 | ASSERT_EQ(Deps.size(), 6u); |
166 | EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp" ); |
167 | EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h" ); |
168 | EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h" ); |
169 | EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test2.cpp" ); |
170 | EXPECT_EQ(convert_to_slash(Deps[4]), "/root/symlink.h" ); |
171 | EXPECT_EQ(convert_to_slash(Deps[5]), "/root/header.h" ); |
172 | } |
173 | |
174 | TEST(DependencyScanner, ScanDepsReuseFilemanagerHasInclude) { |
175 | std::vector<std::string> Compilation = {"-c" , "-E" , "-MT" , "test.cpp.o" }; |
176 | StringRef CWD = "/root" ; |
177 | FixedCompilationDatabase CDB(CWD, Compilation); |
178 | |
179 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
180 | VFS->setCurrentWorkingDirectory(CWD); |
181 | auto Sept = llvm::sys::path::get_separator(); |
182 | std::string = |
183 | std::string(llvm::formatv(Fmt: "{0}root{0}header.h" , Vals&: Sept)); |
184 | std::string SymlinkPath = |
185 | std::string(llvm::formatv(Fmt: "{0}root{0}symlink.h" , Vals&: Sept)); |
186 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.cpp" , Vals&: Sept)); |
187 | |
188 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
189 | VFS->addHardLink(NewLink: SymlinkPath, Target: HeaderPath); |
190 | VFS->addFile( |
191 | Path: TestPath, ModificationTime: 0, |
192 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "#if __has_include(\"header.h\") && " |
193 | "__has_include(\"symlink.h\")\n#endif" )); |
194 | |
195 | ClangTool Tool(CDB, {"test.cpp" , "test.cpp" }, |
196 | std::make_shared<PCHContainerOperations>(), VFS); |
197 | Tool.clearArgumentsAdjusters(); |
198 | std::vector<std::string> Deps; |
199 | TestDependencyScanningAction Action(Deps); |
200 | Tool.run(Action: &Action); |
201 | using llvm::sys::path::convert_to_slash; |
202 | ASSERT_EQ(Deps.size(), 6u); |
203 | EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp" ); |
204 | EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h" ); |
205 | EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h" ); |
206 | EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test.cpp" ); |
207 | EXPECT_EQ(convert_to_slash(Deps[4]), "/root/header.h" ); |
208 | EXPECT_EQ(convert_to_slash(Deps[5]), "/root/symlink.h" ); |
209 | } |
210 | |
211 | TEST(DependencyScanner, ScanDepsWithFS) { |
212 | std::vector<std::string> CommandLine = {"clang" , |
213 | "-target" , |
214 | "x86_64-apple-macosx10.7" , |
215 | "-c" , |
216 | "test.cpp" , |
217 | "-o" |
218 | "test.cpp.o" }; |
219 | StringRef CWD = "/root" ; |
220 | |
221 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
222 | VFS->setCurrentWorkingDirectory(CWD); |
223 | auto Sept = llvm::sys::path::get_separator(); |
224 | std::string = |
225 | std::string(llvm::formatv(Fmt: "{0}root{0}header.h" , Vals&: Sept)); |
226 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.cpp" , Vals&: Sept)); |
227 | |
228 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
229 | VFS->addFile(Path: TestPath, ModificationTime: 0, |
230 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "#include \"header.h\"\n" )); |
231 | |
232 | DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, |
233 | ScanningOutputFormat::Make); |
234 | DependencyScanningTool ScanTool(Service, VFS); |
235 | |
236 | std::string DepFile; |
237 | ASSERT_THAT_ERROR( |
238 | ScanTool.getDependencyFile(CommandLine, CWD).moveInto(DepFile), |
239 | llvm::Succeeded()); |
240 | using llvm::sys::path::convert_to_slash; |
241 | EXPECT_EQ(convert_to_slash(DepFile), |
242 | "test.cpp.o: /root/test.cpp /root/header.h\n" ); |
243 | } |
244 | |
245 | TEST(DependencyScanner, ScanDepsWithModuleLookup) { |
246 | std::vector<std::string> CommandLine = { |
247 | "clang" , |
248 | "-target" , |
249 | "x86_64-apple-macosx10.7" , |
250 | "-c" , |
251 | "test.m" , |
252 | "-o" |
253 | "test.m.o" , |
254 | "-fmodules" , |
255 | "-I/root/SomeSources" , |
256 | }; |
257 | StringRef CWD = "/root" ; |
258 | |
259 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
260 | VFS->setCurrentWorkingDirectory(CWD); |
261 | auto Sept = llvm::sys::path::get_separator(); |
262 | std::string OtherPath = |
263 | std::string(llvm::formatv(Fmt: "{0}root{0}SomeSources{0}other.h" , Vals&: Sept)); |
264 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.m" , Vals&: Sept)); |
265 | |
266 | VFS->addFile(Path: OtherPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
267 | VFS->addFile(Path: TestPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "@import Foo;\n" )); |
268 | |
269 | struct InterceptorFS : llvm::vfs::ProxyFileSystem { |
270 | std::vector<std::string> StatPaths; |
271 | std::vector<std::string> ReadFiles; |
272 | |
273 | InterceptorFS(IntrusiveRefCntPtr<FileSystem> UnderlyingFS) |
274 | : ProxyFileSystem(UnderlyingFS) {} |
275 | |
276 | llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override { |
277 | StatPaths.push_back(x: Path.str()); |
278 | return ProxyFileSystem::status(Path); |
279 | } |
280 | |
281 | llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> |
282 | openFileForRead(const Twine &Path) override { |
283 | ReadFiles.push_back(x: Path.str()); |
284 | return ProxyFileSystem::openFileForRead(Path); |
285 | } |
286 | }; |
287 | |
288 | auto InterceptFS = llvm::makeIntrusiveRefCnt<InterceptorFS>(A&: VFS); |
289 | |
290 | DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, |
291 | ScanningOutputFormat::Make); |
292 | DependencyScanningTool ScanTool(Service, InterceptFS); |
293 | |
294 | // This will fail with "fatal error: module 'Foo' not found" but it doesn't |
295 | // matter, the point of the test is to check that files are not read |
296 | // unnecessarily. |
297 | std::string DepFile; |
298 | ASSERT_THAT_ERROR( |
299 | ScanTool.getDependencyFile(CommandLine, CWD).moveInto(DepFile), |
300 | llvm::Failed()); |
301 | |
302 | EXPECT_TRUE(llvm::find(InterceptFS->StatPaths, OtherPath) == |
303 | InterceptFS->StatPaths.end()); |
304 | EXPECT_EQ(InterceptFS->ReadFiles, std::vector<std::string>{"test.m" }); |
305 | } |
306 | |
307 | TEST(DependencyScanner, ScanDepsWithDiagConsumer) { |
308 | StringRef CWD = "/root" ; |
309 | |
310 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
311 | VFS->setCurrentWorkingDirectory(CWD); |
312 | auto Sept = llvm::sys::path::get_separator(); |
313 | std::string = |
314 | std::string(llvm::formatv(Fmt: "{0}root{0}header.h" , Vals&: Sept)); |
315 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.cpp" , Vals&: Sept)); |
316 | std::string AsmPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.s" , Vals&: Sept)); |
317 | |
318 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
319 | VFS->addFile(Path: TestPath, ModificationTime: 0, |
320 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "#include \"header.h\"\n" )); |
321 | VFS->addFile(Path: AsmPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "" )); |
322 | |
323 | DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, |
324 | ScanningOutputFormat::Make); |
325 | DependencyScanningWorker Worker(Service, VFS); |
326 | |
327 | llvm::DenseSet<ModuleID> AlreadySeen; |
328 | FullDependencyConsumer DC(AlreadySeen); |
329 | CallbackActionController AC(nullptr); |
330 | |
331 | struct EnsureFinishedConsumer : public DiagnosticConsumer { |
332 | bool Finished = false; |
333 | void finish() override { Finished = true; } |
334 | }; |
335 | |
336 | { |
337 | // Check that a successful scan calls DiagConsumer.finish(). |
338 | std::vector<std::string> Args = {"clang" , |
339 | "-target" , |
340 | "x86_64-apple-macosx10.7" , |
341 | "-c" , |
342 | "test.cpp" , |
343 | "-o" |
344 | "test.cpp.o" }; |
345 | |
346 | EnsureFinishedConsumer DiagConsumer; |
347 | bool Success = Worker.computeDependencies(WorkingDirectory: CWD, CommandLine: Args, DepConsumer&: DC, Controller&: AC, DiagConsumer); |
348 | |
349 | EXPECT_TRUE(Success); |
350 | EXPECT_EQ(DiagConsumer.getNumErrors(), 0u); |
351 | EXPECT_TRUE(DiagConsumer.Finished); |
352 | } |
353 | |
354 | { |
355 | // Check that an invalid command-line, which never enters the scanning |
356 | // action calls DiagConsumer.finish(). |
357 | std::vector<std::string> Args = {"clang" , "-invalid-arg" }; |
358 | EnsureFinishedConsumer DiagConsumer; |
359 | bool Success = Worker.computeDependencies(WorkingDirectory: CWD, CommandLine: Args, DepConsumer&: DC, Controller&: AC, DiagConsumer); |
360 | |
361 | EXPECT_FALSE(Success); |
362 | EXPECT_GE(DiagConsumer.getNumErrors(), 1u); |
363 | EXPECT_TRUE(DiagConsumer.Finished); |
364 | } |
365 | |
366 | { |
367 | // Check that a valid command line that produces no scanning jobs calls |
368 | // DiagConsumer.finish(). |
369 | std::vector<std::string> Args = {"clang" , |
370 | "-target" , |
371 | "x86_64-apple-macosx10.7" , |
372 | "-c" , |
373 | "-x" , |
374 | "assembler" , |
375 | "test.s" , |
376 | "-o" |
377 | "test.cpp.o" }; |
378 | |
379 | EnsureFinishedConsumer DiagConsumer; |
380 | bool Success = Worker.computeDependencies(WorkingDirectory: CWD, CommandLine: Args, DepConsumer&: DC, Controller&: AC, DiagConsumer); |
381 | |
382 | EXPECT_FALSE(Success); |
383 | EXPECT_EQ(DiagConsumer.getNumErrors(), 1u); |
384 | EXPECT_TRUE(DiagConsumer.Finished); |
385 | } |
386 | } |
387 | |