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
30using namespace clang;
31using namespace tooling;
32using namespace dependencies;
33
34namespace {
35
36/// Prints out all of the gathered dependencies into a string.
37class TestFileCollector : public DependencyFileGenerator {
38public:
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
48private:
49 std::vector<std::string> &Deps;
50};
51
52class TestDependencyScanningAction : public tooling::ToolAction {
53public:
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
77private:
78 std::vector<std::string> &Deps;
79};
80
81} // namespace
82
83TEST(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 HeaderPath =
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
132TEST(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 HeaderPath =
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
174TEST(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 HeaderPath =
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
211TEST(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 HeaderPath =
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
245TEST(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
307TEST(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 HeaderPath =
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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang/unittests/Tooling/DependencyScanning/DependencyScannerTest.cpp