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/Tooling.h" |
19 | #include "llvm/ADT/STLExtras.h" |
20 | #include "llvm/MC/TargetRegistry.h" |
21 | #include "llvm/Support/FormatVariadic.h" |
22 | #include "llvm/Support/Path.h" |
23 | #include "llvm/Support/TargetSelect.h" |
24 | #include "llvm/Testing/Support/Error.h" |
25 | #include "gtest/gtest.h" |
26 | #include <algorithm> |
27 | #include <string> |
28 | |
29 | using namespace clang; |
30 | using namespace tooling; |
31 | using namespace dependencies; |
32 | |
33 | namespace { |
34 | |
35 | /// Prints out all of the gathered dependencies into a string. |
36 | class TestFileCollector : public DependencyFileGenerator { |
37 | public: |
38 | TestFileCollector(DependencyOutputOptions &Opts, |
39 | std::vector<std::string> &Deps) |
40 | : DependencyFileGenerator(Opts), Deps(Deps) {} |
41 | |
42 | void finishedMainFile(DiagnosticsEngine &Diags) override { |
43 | auto NewDeps = getDependencies(); |
44 | Deps.insert(position: Deps.end(), first: NewDeps.begin(), last: NewDeps.end()); |
45 | } |
46 | |
47 | private: |
48 | std::vector<std::string> &Deps; |
49 | }; |
50 | |
51 | class TestDependencyScanningAction : public tooling::ToolAction { |
52 | public: |
53 | TestDependencyScanningAction(std::vector<std::string> &Deps) : Deps(Deps) {} |
54 | |
55 | bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, |
56 | FileManager *FileMgr, |
57 | std::shared_ptr<PCHContainerOperations> PCHContainerOps, |
58 | DiagnosticConsumer *DiagConsumer) override { |
59 | CompilerInstance Compiler(std::move(PCHContainerOps)); |
60 | Compiler.setInvocation(std::move(Invocation)); |
61 | Compiler.setFileManager(FileMgr); |
62 | |
63 | Compiler.createDiagnostics(Client: DiagConsumer, /*ShouldOwnClient=*/false); |
64 | if (!Compiler.hasDiagnostics()) |
65 | return false; |
66 | |
67 | Compiler.createSourceManager(FileMgr&: *FileMgr); |
68 | Compiler.addDependencyCollector(Listener: std::make_shared<TestFileCollector>( |
69 | args&: Compiler.getInvocation().getDependencyOutputOpts(), args&: Deps)); |
70 | |
71 | auto Action = std::make_unique<PreprocessOnlyAction>(); |
72 | return Compiler.ExecuteAction(Act&: *Action); |
73 | } |
74 | |
75 | private: |
76 | std::vector<std::string> &Deps; |
77 | }; |
78 | |
79 | } // namespace |
80 | |
81 | TEST(DependencyScanner, ScanDepsReuseFilemanager) { |
82 | std::vector<std::string> Compilation = {"-c" , "-E" , "-MT" , "test.cpp.o" }; |
83 | StringRef CWD = "/root" ; |
84 | FixedCompilationDatabase CDB(CWD, Compilation); |
85 | |
86 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
87 | VFS->setCurrentWorkingDirectory(CWD); |
88 | auto Sept = llvm::sys::path::get_separator(); |
89 | std::string = |
90 | std::string(llvm::formatv(Fmt: "{0}root{0}header.h" , Vals&: Sept)); |
91 | std::string SymlinkPath = |
92 | std::string(llvm::formatv(Fmt: "{0}root{0}symlink.h" , Vals&: Sept)); |
93 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.cpp" , Vals&: Sept)); |
94 | |
95 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
96 | VFS->addHardLink(NewLink: SymlinkPath, Target: HeaderPath); |
97 | VFS->addFile(Path: TestPath, ModificationTime: 0, |
98 | Buffer: llvm::MemoryBuffer::getMemBuffer( |
99 | InputData: "#include \"symlink.h\"\n#include \"header.h\"\n" )); |
100 | |
101 | ClangTool Tool(CDB, {"test.cpp" }, std::make_shared<PCHContainerOperations>(), |
102 | VFS); |
103 | Tool.clearArgumentsAdjusters(); |
104 | std::vector<std::string> Deps; |
105 | TestDependencyScanningAction Action(Deps); |
106 | Tool.run(Action: &Action); |
107 | using llvm::sys::path::convert_to_slash; |
108 | // The first invocation should return dependencies in order of access. |
109 | ASSERT_EQ(Deps.size(), 3u); |
110 | EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp" ); |
111 | EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h" ); |
112 | EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h" ); |
113 | |
114 | // The file manager should still have two FileEntries, as one file is a |
115 | // hardlink. |
116 | FileManager &Files = Tool.getFiles(); |
117 | EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u); |
118 | |
119 | Deps.clear(); |
120 | Tool.run(Action: &Action); |
121 | // The second invocation should have the same order of dependencies. |
122 | ASSERT_EQ(Deps.size(), 3u); |
123 | EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp" ); |
124 | EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h" ); |
125 | EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h" ); |
126 | |
127 | EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u); |
128 | } |
129 | |
130 | TEST(DependencyScanner, ScanDepsReuseFilemanagerSkippedFile) { |
131 | std::vector<std::string> Compilation = {"-c" , "-E" , "-MT" , "test.cpp.o" }; |
132 | StringRef CWD = "/root" ; |
133 | FixedCompilationDatabase CDB(CWD, Compilation); |
134 | |
135 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
136 | VFS->setCurrentWorkingDirectory(CWD); |
137 | auto Sept = llvm::sys::path::get_separator(); |
138 | std::string = |
139 | std::string(llvm::formatv(Fmt: "{0}root{0}header.h" , Vals&: Sept)); |
140 | std::string SymlinkPath = |
141 | std::string(llvm::formatv(Fmt: "{0}root{0}symlink.h" , Vals&: Sept)); |
142 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.cpp" , Vals&: Sept)); |
143 | std::string Test2Path = |
144 | std::string(llvm::formatv(Fmt: "{0}root{0}test2.cpp" , Vals&: Sept)); |
145 | |
146 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, |
147 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "#pragma once\n" )); |
148 | VFS->addHardLink(NewLink: SymlinkPath, Target: HeaderPath); |
149 | VFS->addFile(Path: TestPath, ModificationTime: 0, |
150 | Buffer: llvm::MemoryBuffer::getMemBuffer( |
151 | InputData: "#include \"header.h\"\n#include \"symlink.h\"\n" )); |
152 | VFS->addFile(Path: Test2Path, ModificationTime: 0, |
153 | Buffer: llvm::MemoryBuffer::getMemBuffer( |
154 | InputData: "#include \"symlink.h\"\n#include \"header.h\"\n" )); |
155 | |
156 | ClangTool Tool(CDB, {"test.cpp" , "test2.cpp" }, |
157 | std::make_shared<PCHContainerOperations>(), VFS); |
158 | Tool.clearArgumentsAdjusters(); |
159 | std::vector<std::string> Deps; |
160 | TestDependencyScanningAction Action(Deps); |
161 | Tool.run(Action: &Action); |
162 | using llvm::sys::path::convert_to_slash; |
163 | ASSERT_EQ(Deps.size(), 6u); |
164 | EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp" ); |
165 | EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h" ); |
166 | EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h" ); |
167 | EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test2.cpp" ); |
168 | EXPECT_EQ(convert_to_slash(Deps[4]), "/root/symlink.h" ); |
169 | EXPECT_EQ(convert_to_slash(Deps[5]), "/root/header.h" ); |
170 | } |
171 | |
172 | TEST(DependencyScanner, ScanDepsReuseFilemanagerHasInclude) { |
173 | std::vector<std::string> Compilation = {"-c" , "-E" , "-MT" , "test.cpp.o" }; |
174 | StringRef CWD = "/root" ; |
175 | FixedCompilationDatabase CDB(CWD, Compilation); |
176 | |
177 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
178 | VFS->setCurrentWorkingDirectory(CWD); |
179 | auto Sept = llvm::sys::path::get_separator(); |
180 | std::string = |
181 | std::string(llvm::formatv(Fmt: "{0}root{0}header.h" , Vals&: Sept)); |
182 | std::string SymlinkPath = |
183 | std::string(llvm::formatv(Fmt: "{0}root{0}symlink.h" , Vals&: Sept)); |
184 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.cpp" , Vals&: Sept)); |
185 | |
186 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
187 | VFS->addHardLink(NewLink: SymlinkPath, Target: HeaderPath); |
188 | VFS->addFile( |
189 | Path: TestPath, ModificationTime: 0, |
190 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "#if __has_include(\"header.h\") && " |
191 | "__has_include(\"symlink.h\")\n#endif" )); |
192 | |
193 | ClangTool Tool(CDB, {"test.cpp" , "test.cpp" }, |
194 | std::make_shared<PCHContainerOperations>(), VFS); |
195 | Tool.clearArgumentsAdjusters(); |
196 | std::vector<std::string> Deps; |
197 | TestDependencyScanningAction Action(Deps); |
198 | Tool.run(Action: &Action); |
199 | using llvm::sys::path::convert_to_slash; |
200 | ASSERT_EQ(Deps.size(), 6u); |
201 | EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp" ); |
202 | EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h" ); |
203 | EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h" ); |
204 | EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test.cpp" ); |
205 | EXPECT_EQ(convert_to_slash(Deps[4]), "/root/header.h" ); |
206 | EXPECT_EQ(convert_to_slash(Deps[5]), "/root/symlink.h" ); |
207 | } |
208 | |
209 | TEST(DependencyScanner, ScanDepsWithFS) { |
210 | std::vector<std::string> CommandLine = {"clang" , |
211 | "-target" , |
212 | "x86_64-apple-macosx10.7" , |
213 | "-c" , |
214 | "test.cpp" , |
215 | "-o" |
216 | "test.cpp.o" }; |
217 | StringRef CWD = "/root" ; |
218 | |
219 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
220 | VFS->setCurrentWorkingDirectory(CWD); |
221 | auto Sept = llvm::sys::path::get_separator(); |
222 | std::string = |
223 | std::string(llvm::formatv(Fmt: "{0}root{0}header.h" , Vals&: Sept)); |
224 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.cpp" , Vals&: Sept)); |
225 | |
226 | VFS->addFile(Path: HeaderPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
227 | VFS->addFile(Path: TestPath, ModificationTime: 0, |
228 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "#include \"header.h\"\n" )); |
229 | |
230 | DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, |
231 | ScanningOutputFormat::Make); |
232 | DependencyScanningTool ScanTool(Service, VFS); |
233 | |
234 | std::string DepFile; |
235 | ASSERT_THAT_ERROR( |
236 | ScanTool.getDependencyFile(CommandLine, CWD).moveInto(DepFile), |
237 | llvm::Succeeded()); |
238 | using llvm::sys::path::convert_to_slash; |
239 | EXPECT_EQ(convert_to_slash(DepFile), |
240 | "test.cpp.o: /root/test.cpp /root/header.h\n" ); |
241 | } |
242 | |
243 | TEST(DependencyScanner, ScanDepsWithModuleLookup) { |
244 | std::vector<std::string> CommandLine = { |
245 | "clang" , |
246 | "-target" , |
247 | "x86_64-apple-macosx10.7" , |
248 | "-c" , |
249 | "test.m" , |
250 | "-o" |
251 | "test.m.o" , |
252 | "-fmodules" , |
253 | "-I/root/SomeSources" , |
254 | }; |
255 | StringRef CWD = "/root" ; |
256 | |
257 | auto VFS = new llvm::vfs::InMemoryFileSystem(); |
258 | VFS->setCurrentWorkingDirectory(CWD); |
259 | auto Sept = llvm::sys::path::get_separator(); |
260 | std::string OtherPath = |
261 | std::string(llvm::formatv(Fmt: "{0}root{0}SomeSources{0}other.h" , Vals&: Sept)); |
262 | std::string TestPath = std::string(llvm::formatv(Fmt: "{0}root{0}test.m" , Vals&: Sept)); |
263 | |
264 | VFS->addFile(Path: OtherPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "\n" )); |
265 | VFS->addFile(Path: TestPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "@import Foo;\n" )); |
266 | |
267 | struct InterceptorFS : llvm::vfs::ProxyFileSystem { |
268 | std::vector<std::string> StatPaths; |
269 | std::vector<std::string> ReadFiles; |
270 | |
271 | InterceptorFS(IntrusiveRefCntPtr<FileSystem> UnderlyingFS) |
272 | : ProxyFileSystem(UnderlyingFS) {} |
273 | |
274 | llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override { |
275 | StatPaths.push_back(x: Path.str()); |
276 | return ProxyFileSystem::status(Path); |
277 | } |
278 | |
279 | llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> |
280 | openFileForRead(const Twine &Path) override { |
281 | ReadFiles.push_back(x: Path.str()); |
282 | return ProxyFileSystem::openFileForRead(Path); |
283 | } |
284 | }; |
285 | |
286 | auto InterceptFS = llvm::makeIntrusiveRefCnt<InterceptorFS>(A&: VFS); |
287 | |
288 | DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, |
289 | ScanningOutputFormat::Make); |
290 | DependencyScanningTool ScanTool(Service, InterceptFS); |
291 | |
292 | // This will fail with "fatal error: module 'Foo' not found" but it doesn't |
293 | // matter, the point of the test is to check that files are not read |
294 | // unnecessarily. |
295 | std::string DepFile; |
296 | ASSERT_THAT_ERROR( |
297 | ScanTool.getDependencyFile(CommandLine, CWD).moveInto(DepFile), |
298 | llvm::Failed()); |
299 | |
300 | EXPECT_TRUE(llvm::find(InterceptFS->StatPaths, OtherPath) == |
301 | InterceptFS->StatPaths.end()); |
302 | EXPECT_EQ(InterceptFS->ReadFiles, std::vector<std::string>{"test.m" }); |
303 | } |
304 | |