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
29using namespace clang;
30using namespace tooling;
31using namespace dependencies;
32
33namespace {
34
35/// Prints out all of the gathered dependencies into a string.
36class TestFileCollector : public DependencyFileGenerator {
37public:
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
47private:
48 std::vector<std::string> &Deps;
49};
50
51class TestDependencyScanningAction : public tooling::ToolAction {
52public:
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
75private:
76 std::vector<std::string> &Deps;
77};
78
79} // namespace
80
81TEST(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 HeaderPath =
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
130TEST(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 HeaderPath =
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
172TEST(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 HeaderPath =
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
209TEST(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 HeaderPath =
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
243TEST(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

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