1 //===- DependencyScannerTest.cpp ------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
29 using namespace clang
;
30 using namespace tooling
;
31 using namespace dependencies
;
35 /// Prints out all of the gathered dependencies into a string.
36 class TestFileCollector
: public DependencyFileGenerator
{
38 TestFileCollector(DependencyOutputOptions
&Opts
,
39 std::vector
<std::string
> &Deps
)
40 : DependencyFileGenerator(Opts
), Deps(Deps
) {}
42 void finishedMainFile(DiagnosticsEngine
&Diags
) override
{
43 auto NewDeps
= getDependencies();
44 Deps
.insert(Deps
.end(), NewDeps
.begin(), NewDeps
.end());
48 std::vector
<std::string
> &Deps
;
51 class TestDependencyScanningAction
: public tooling::ToolAction
{
53 TestDependencyScanningAction(std::vector
<std::string
> &Deps
) : Deps(Deps
) {}
55 bool runInvocation(std::shared_ptr
<CompilerInvocation
> Invocation
,
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
);
63 Compiler
.createDiagnostics(FileMgr
->getVirtualFileSystem(), DiagConsumer
,
64 /*ShouldOwnClient=*/false);
65 if (!Compiler
.hasDiagnostics())
68 Compiler
.createSourceManager(*FileMgr
);
69 Compiler
.addDependencyCollector(std::make_shared
<TestFileCollector
>(
70 Compiler
.getInvocation().getDependencyOutputOpts(), Deps
));
72 auto Action
= std::make_unique
<PreprocessOnlyAction
>();
73 return Compiler
.ExecuteAction(*Action
);
77 std::vector
<std::string
> &Deps
;
82 TEST(DependencyScanner
, ScanDepsReuseFilemanager
) {
83 std::vector
<std::string
> Compilation
= {"-c", "-E", "-MT", "test.cpp.o"};
84 StringRef CWD
= "/root";
85 FixedCompilationDatabase
CDB(CWD
, Compilation
);
87 auto VFS
= new llvm::vfs::InMemoryFileSystem();
88 VFS
->setCurrentWorkingDirectory(CWD
);
89 auto Sept
= llvm::sys::path::get_separator();
90 std::string HeaderPath
=
91 std::string(llvm::formatv("{0}root{0}header.h", Sept
));
92 std::string SymlinkPath
=
93 std::string(llvm::formatv("{0}root{0}symlink.h", Sept
));
94 std::string TestPath
= std::string(llvm::formatv("{0}root{0}test.cpp", Sept
));
96 VFS
->addFile(HeaderPath
, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
97 VFS
->addHardLink(SymlinkPath
, HeaderPath
);
98 VFS
->addFile(TestPath
, 0,
99 llvm::MemoryBuffer::getMemBuffer(
100 "#include \"symlink.h\"\n#include \"header.h\"\n"));
102 ClangTool
Tool(CDB
, {"test.cpp"}, std::make_shared
<PCHContainerOperations
>(),
104 Tool
.clearArgumentsAdjusters();
105 std::vector
<std::string
> Deps
;
106 TestDependencyScanningAction
Action(Deps
);
108 using llvm::sys::path::convert_to_slash
;
109 // The first invocation should return dependencies in order of access.
110 ASSERT_EQ(Deps
.size(), 3u);
111 EXPECT_EQ(convert_to_slash(Deps
[0]), "/root/test.cpp");
112 EXPECT_EQ(convert_to_slash(Deps
[1]), "/root/symlink.h");
113 EXPECT_EQ(convert_to_slash(Deps
[2]), "/root/header.h");
115 // The file manager should still have two FileEntries, as one file is a
117 FileManager
&Files
= Tool
.getFiles();
118 EXPECT_EQ(Files
.getNumUniqueRealFiles(), 2u);
122 // The second invocation should have the same order of dependencies.
123 ASSERT_EQ(Deps
.size(), 3u);
124 EXPECT_EQ(convert_to_slash(Deps
[0]), "/root/test.cpp");
125 EXPECT_EQ(convert_to_slash(Deps
[1]), "/root/symlink.h");
126 EXPECT_EQ(convert_to_slash(Deps
[2]), "/root/header.h");
128 EXPECT_EQ(Files
.getNumUniqueRealFiles(), 2u);
131 TEST(DependencyScanner
, ScanDepsReuseFilemanagerSkippedFile
) {
132 std::vector
<std::string
> Compilation
= {"-c", "-E", "-MT", "test.cpp.o"};
133 StringRef CWD
= "/root";
134 FixedCompilationDatabase
CDB(CWD
, Compilation
);
136 auto VFS
= new llvm::vfs::InMemoryFileSystem();
137 VFS
->setCurrentWorkingDirectory(CWD
);
138 auto Sept
= llvm::sys::path::get_separator();
139 std::string HeaderPath
=
140 std::string(llvm::formatv("{0}root{0}header.h", Sept
));
141 std::string SymlinkPath
=
142 std::string(llvm::formatv("{0}root{0}symlink.h", Sept
));
143 std::string TestPath
= std::string(llvm::formatv("{0}root{0}test.cpp", Sept
));
144 std::string Test2Path
=
145 std::string(llvm::formatv("{0}root{0}test2.cpp", Sept
));
147 VFS
->addFile(HeaderPath
, 0,
148 llvm::MemoryBuffer::getMemBuffer("#pragma once\n"));
149 VFS
->addHardLink(SymlinkPath
, HeaderPath
);
150 VFS
->addFile(TestPath
, 0,
151 llvm::MemoryBuffer::getMemBuffer(
152 "#include \"header.h\"\n#include \"symlink.h\"\n"));
153 VFS
->addFile(Test2Path
, 0,
154 llvm::MemoryBuffer::getMemBuffer(
155 "#include \"symlink.h\"\n#include \"header.h\"\n"));
157 ClangTool
Tool(CDB
, {"test.cpp", "test2.cpp"},
158 std::make_shared
<PCHContainerOperations
>(), VFS
);
159 Tool
.clearArgumentsAdjusters();
160 std::vector
<std::string
> Deps
;
161 TestDependencyScanningAction
Action(Deps
);
163 using llvm::sys::path::convert_to_slash
;
164 ASSERT_EQ(Deps
.size(), 6u);
165 EXPECT_EQ(convert_to_slash(Deps
[0]), "/root/test.cpp");
166 EXPECT_EQ(convert_to_slash(Deps
[1]), "/root/header.h");
167 EXPECT_EQ(convert_to_slash(Deps
[2]), "/root/symlink.h");
168 EXPECT_EQ(convert_to_slash(Deps
[3]), "/root/test2.cpp");
169 EXPECT_EQ(convert_to_slash(Deps
[4]), "/root/symlink.h");
170 EXPECT_EQ(convert_to_slash(Deps
[5]), "/root/header.h");
173 TEST(DependencyScanner
, ScanDepsReuseFilemanagerHasInclude
) {
174 std::vector
<std::string
> Compilation
= {"-c", "-E", "-MT", "test.cpp.o"};
175 StringRef CWD
= "/root";
176 FixedCompilationDatabase
CDB(CWD
, Compilation
);
178 auto VFS
= new llvm::vfs::InMemoryFileSystem();
179 VFS
->setCurrentWorkingDirectory(CWD
);
180 auto Sept
= llvm::sys::path::get_separator();
181 std::string HeaderPath
=
182 std::string(llvm::formatv("{0}root{0}header.h", Sept
));
183 std::string SymlinkPath
=
184 std::string(llvm::formatv("{0}root{0}symlink.h", Sept
));
185 std::string TestPath
= std::string(llvm::formatv("{0}root{0}test.cpp", Sept
));
187 VFS
->addFile(HeaderPath
, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
188 VFS
->addHardLink(SymlinkPath
, HeaderPath
);
191 llvm::MemoryBuffer::getMemBuffer("#if __has_include(\"header.h\") && "
192 "__has_include(\"symlink.h\")\n#endif"));
194 ClangTool
Tool(CDB
, {"test.cpp", "test.cpp"},
195 std::make_shared
<PCHContainerOperations
>(), VFS
);
196 Tool
.clearArgumentsAdjusters();
197 std::vector
<std::string
> Deps
;
198 TestDependencyScanningAction
Action(Deps
);
200 using llvm::sys::path::convert_to_slash
;
201 ASSERT_EQ(Deps
.size(), 6u);
202 EXPECT_EQ(convert_to_slash(Deps
[0]), "/root/test.cpp");
203 EXPECT_EQ(convert_to_slash(Deps
[1]), "/root/header.h");
204 EXPECT_EQ(convert_to_slash(Deps
[2]), "/root/symlink.h");
205 EXPECT_EQ(convert_to_slash(Deps
[3]), "/root/test.cpp");
206 EXPECT_EQ(convert_to_slash(Deps
[4]), "/root/header.h");
207 EXPECT_EQ(convert_to_slash(Deps
[5]), "/root/symlink.h");
210 TEST(DependencyScanner
, ScanDepsWithFS
) {
211 std::vector
<std::string
> CommandLine
= {"clang",
213 "x86_64-apple-macosx10.7",
218 StringRef CWD
= "/root";
220 auto VFS
= new llvm::vfs::InMemoryFileSystem();
221 VFS
->setCurrentWorkingDirectory(CWD
);
222 auto Sept
= llvm::sys::path::get_separator();
223 std::string HeaderPath
=
224 std::string(llvm::formatv("{0}root{0}header.h", Sept
));
225 std::string TestPath
= std::string(llvm::formatv("{0}root{0}test.cpp", Sept
));
227 VFS
->addFile(HeaderPath
, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
228 VFS
->addFile(TestPath
, 0,
229 llvm::MemoryBuffer::getMemBuffer("#include \"header.h\"\n"));
231 DependencyScanningService
Service(ScanningMode::DependencyDirectivesScan
,
232 ScanningOutputFormat::Make
);
233 DependencyScanningTool
ScanTool(Service
, VFS
);
237 ScanTool
.getDependencyFile(CommandLine
, CWD
).moveInto(DepFile
),
239 using llvm::sys::path::convert_to_slash
;
240 EXPECT_EQ(convert_to_slash(DepFile
),
241 "test.cpp.o: /root/test.cpp /root/header.h\n");
244 TEST(DependencyScanner
, ScanDepsWithModuleLookup
) {
245 std::vector
<std::string
> CommandLine
= {
248 "x86_64-apple-macosx10.7",
254 "-I/root/SomeSources",
256 StringRef CWD
= "/root";
258 auto VFS
= new llvm::vfs::InMemoryFileSystem();
259 VFS
->setCurrentWorkingDirectory(CWD
);
260 auto Sept
= llvm::sys::path::get_separator();
261 std::string OtherPath
=
262 std::string(llvm::formatv("{0}root{0}SomeSources{0}other.h", Sept
));
263 std::string TestPath
= std::string(llvm::formatv("{0}root{0}test.m", Sept
));
265 VFS
->addFile(OtherPath
, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
266 VFS
->addFile(TestPath
, 0, llvm::MemoryBuffer::getMemBuffer("@import Foo;\n"));
268 struct InterceptorFS
: llvm::vfs::ProxyFileSystem
{
269 std::vector
<std::string
> StatPaths
;
270 std::vector
<std::string
> ReadFiles
;
272 InterceptorFS(IntrusiveRefCntPtr
<FileSystem
> UnderlyingFS
)
273 : ProxyFileSystem(UnderlyingFS
) {}
275 llvm::ErrorOr
<llvm::vfs::Status
> status(const Twine
&Path
) override
{
276 StatPaths
.push_back(Path
.str());
277 return ProxyFileSystem::status(Path
);
280 llvm::ErrorOr
<std::unique_ptr
<llvm::vfs::File
>>
281 openFileForRead(const Twine
&Path
) override
{
282 ReadFiles
.push_back(Path
.str());
283 return ProxyFileSystem::openFileForRead(Path
);
287 auto InterceptFS
= llvm::makeIntrusiveRefCnt
<InterceptorFS
>(VFS
);
289 DependencyScanningService
Service(ScanningMode::DependencyDirectivesScan
,
290 ScanningOutputFormat::Make
);
291 DependencyScanningTool
ScanTool(Service
, InterceptFS
);
293 // This will fail with "fatal error: module 'Foo' not found" but it doesn't
294 // matter, the point of the test is to check that files are not read
298 ScanTool
.getDependencyFile(CommandLine
, CWD
).moveInto(DepFile
),
301 EXPECT_TRUE(llvm::find(InterceptFS
->StatPaths
, OtherPath
) ==
302 InterceptFS
->StatPaths
.end());
303 EXPECT_EQ(InterceptFS
->ReadFiles
, std::vector
<std::string
>{"test.m"});