1 //===- unittest/Tooling/ToolingTest.cpp - Tooling unit tests --------------===//
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:
TestFileCollector(DependencyOutputOptions & Opts,std::vector<std::string> & Deps)38   TestFileCollector(DependencyOutputOptions &Opts,
39                     std::vector<std::string> &Deps)
40       : DependencyFileGenerator(Opts), Deps(Deps) {}
41 
finishedMainFile(DiagnosticsEngine & Diags)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:
TestDependencyScanningAction(std::vector<std::string> & Deps)53   TestDependencyScanningAction(std::vector<std::string> &Deps) : Deps(Deps) {}
54 
runInvocation(std::shared_ptr<CompilerInvocation> Invocation,FileManager * FileMgr,std::shared_ptr<PCHContainerOperations> PCHContainerOps,DiagnosticConsumer * DiagConsumer)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(DiagConsumer, /*ShouldOwnClient=*/false);
64     if (!Compiler.hasDiagnostics())
65       return false;
66 
67     Compiler.createSourceManager(*FileMgr);
68     Compiler.addDependencyCollector(std::make_shared<TestFileCollector>(
69         Compiler.getInvocation().getDependencyOutputOpts(), Deps));
70 
71     auto Action = std::make_unique<PreprocessOnlyAction>();
72     return Compiler.ExecuteAction(*Action);
73   }
74 
75 private:
76   std::vector<std::string> &Deps;
77 };
78 
79 } // namespace
80 
TEST(DependencyScanner,ScanDepsReuseFilemanager)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 HeaderPath =
90       std::string(llvm::formatv("{0}root{0}header.h", Sept));
91   std::string SymlinkPath =
92       std::string(llvm::formatv("{0}root{0}symlink.h", Sept));
93   std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));
94 
95   VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
96   VFS->addHardLink(SymlinkPath, HeaderPath);
97   VFS->addFile(TestPath, 0,
98                llvm::MemoryBuffer::getMemBuffer(
99                    "#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);
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);
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 
TEST(DependencyScanner,ScanDepsReuseFilemanagerSkippedFile)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 HeaderPath =
139       std::string(llvm::formatv("{0}root{0}header.h", Sept));
140   std::string SymlinkPath =
141       std::string(llvm::formatv("{0}root{0}symlink.h", Sept));
142   std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));
143   std::string Test2Path =
144       std::string(llvm::formatv("{0}root{0}test2.cpp", Sept));
145 
146   VFS->addFile(HeaderPath, 0,
147                llvm::MemoryBuffer::getMemBuffer("#pragma once\n"));
148   VFS->addHardLink(SymlinkPath, HeaderPath);
149   VFS->addFile(TestPath, 0,
150                llvm::MemoryBuffer::getMemBuffer(
151                    "#include \"header.h\"\n#include \"symlink.h\"\n"));
152   VFS->addFile(Test2Path, 0,
153                llvm::MemoryBuffer::getMemBuffer(
154                    "#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);
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 
TEST(DependencyScanner,ScanDepsReuseFilemanagerHasInclude)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 HeaderPath =
181       std::string(llvm::formatv("{0}root{0}header.h", Sept));
182   std::string SymlinkPath =
183       std::string(llvm::formatv("{0}root{0}symlink.h", Sept));
184   std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));
185 
186   VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
187   VFS->addHardLink(SymlinkPath, HeaderPath);
188   VFS->addFile(
189       TestPath, 0,
190       llvm::MemoryBuffer::getMemBuffer("#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);
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 
TEST(DependencyScanner,ScanDepsWithFS)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 HeaderPath =
223       std::string(llvm::formatv("{0}root{0}header.h", Sept));
224   std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));
225 
226   VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
227   VFS->addFile(TestPath, 0,
228                llvm::MemoryBuffer::getMemBuffer("#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