xref: /llvm-project/clang/unittests/Tooling/DependencyScanning/DependencyScannerTest.cpp (revision df9a14d7bbf1180e4f1474254c9d7ed6bcb4ce55)
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(Deps.end(), NewDeps.begin(), 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(FileMgr->getVirtualFileSystem(), DiagConsumer,
64                                /*ShouldOwnClient=*/false);
65     if (!Compiler.hasDiagnostics())
66       return false;
67 
68     Compiler.createSourceManager(*FileMgr);
69     Compiler.addDependencyCollector(std::make_shared<TestFileCollector>(
70         Compiler.getInvocation().getDependencyOutputOpts(), Deps));
71 
72     auto Action = std::make_unique<PreprocessOnlyAction>();
73     return Compiler.ExecuteAction(*Action);
74   }
75 
76 private:
77   std::vector<std::string> &Deps;
78 };
79 
80 } // namespace
81 
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);
86 
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));
95 
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"));
101 
102   ClangTool Tool(CDB, {"test.cpp"}, std::make_shared<PCHContainerOperations>(),
103                  VFS);
104   Tool.clearArgumentsAdjusters();
105   std::vector<std::string> Deps;
106   TestDependencyScanningAction Action(Deps);
107   Tool.run(&Action);
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");
114 
115   // The file manager should still have two FileEntries, as one file is a
116   // hardlink.
117   FileManager &Files = Tool.getFiles();
118   EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u);
119 
120   Deps.clear();
121   Tool.run(&Action);
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");
127 
128   EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u);
129 }
130 
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);
135 
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));
146 
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"));
156 
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);
162   Tool.run(&Action);
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");
171 }
172 
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);
177 
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));
186 
187   VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
188   VFS->addHardLink(SymlinkPath, HeaderPath);
189   VFS->addFile(
190       TestPath, 0,
191       llvm::MemoryBuffer::getMemBuffer("#if __has_include(\"header.h\") && "
192                                        "__has_include(\"symlink.h\")\n#endif"));
193 
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);
199   Tool.run(&Action);
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");
208 }
209 
210 TEST(DependencyScanner, ScanDepsWithFS) {
211   std::vector<std::string> CommandLine = {"clang",
212                                           "-target",
213                                           "x86_64-apple-macosx10.7",
214                                           "-c",
215                                           "test.cpp",
216                                           "-o"
217                                           "test.cpp.o"};
218   StringRef CWD = "/root";
219 
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));
226 
227   VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
228   VFS->addFile(TestPath, 0,
229                llvm::MemoryBuffer::getMemBuffer("#include \"header.h\"\n"));
230 
231   DependencyScanningService Service(ScanningMode::DependencyDirectivesScan,
232                                     ScanningOutputFormat::Make);
233   DependencyScanningTool ScanTool(Service, VFS);
234 
235   std::string DepFile;
236   ASSERT_THAT_ERROR(
237       ScanTool.getDependencyFile(CommandLine, CWD).moveInto(DepFile),
238       llvm::Succeeded());
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");
242 }
243 
244 TEST(DependencyScanner, ScanDepsWithModuleLookup) {
245   std::vector<std::string> CommandLine = {
246       "clang",
247       "-target",
248       "x86_64-apple-macosx10.7",
249       "-c",
250       "test.m",
251       "-o"
252       "test.m.o",
253       "-fmodules",
254       "-I/root/SomeSources",
255   };
256   StringRef CWD = "/root";
257 
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));
264 
265   VFS->addFile(OtherPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
266   VFS->addFile(TestPath, 0, llvm::MemoryBuffer::getMemBuffer("@import Foo;\n"));
267 
268   struct InterceptorFS : llvm::vfs::ProxyFileSystem {
269     std::vector<std::string> StatPaths;
270     std::vector<std::string> ReadFiles;
271 
272     InterceptorFS(IntrusiveRefCntPtr<FileSystem> UnderlyingFS)
273         : ProxyFileSystem(UnderlyingFS) {}
274 
275     llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override {
276       StatPaths.push_back(Path.str());
277       return ProxyFileSystem::status(Path);
278     }
279 
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);
284     }
285   };
286 
287   auto InterceptFS = llvm::makeIntrusiveRefCnt<InterceptorFS>(VFS);
288 
289   DependencyScanningService Service(ScanningMode::DependencyDirectivesScan,
290                                     ScanningOutputFormat::Make);
291   DependencyScanningTool ScanTool(Service, InterceptFS);
292 
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
295   // unnecessarily.
296   std::string DepFile;
297   ASSERT_THAT_ERROR(
298       ScanTool.getDependencyFile(CommandLine, CWD).moveInto(DepFile),
299       llvm::Failed());
300 
301   EXPECT_TRUE(llvm::find(InterceptFS->StatPaths, OtherPath) ==
302               InterceptFS->StatPaths.end());
303   EXPECT_EQ(InterceptFS->ReadFiles, std::vector<std::string>{"test.m"});
304 }
305