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/Tooling.h"
18 #include "llvm/ADT/STLExtras.h"
19 #include "llvm/Support/FormatVariadic.h"
20 #include "llvm/Support/Path.h"
21 #include "llvm/Support/TargetRegistry.h"
22 #include "llvm/Support/TargetSelect.h"
23 #include "gtest/gtest.h"
24 #include <algorithm>
25 #include <string>
26 
27 namespace clang {
28 namespace tooling {
29 
30 namespace {
31 
32 /// Prints out all of the gathered dependencies into a string.
33 class TestFileCollector : public DependencyFileGenerator {
34 public:
TestFileCollector(DependencyOutputOptions & Opts,std::vector<std::string> & Deps)35   TestFileCollector(DependencyOutputOptions &Opts,
36                     std::vector<std::string> &Deps)
37       : DependencyFileGenerator(Opts), Deps(Deps) {}
38 
finishedMainFile(DiagnosticsEngine & Diags)39   void finishedMainFile(DiagnosticsEngine &Diags) override {
40     auto NewDeps = getDependencies();
41     Deps.insert(Deps.end(), NewDeps.begin(), NewDeps.end());
42   }
43 
44 private:
45   std::vector<std::string> &Deps;
46 };
47 
48 class TestDependencyScanningAction : public tooling::ToolAction {
49 public:
TestDependencyScanningAction(std::vector<std::string> & Deps)50   TestDependencyScanningAction(std::vector<std::string> &Deps) : Deps(Deps) {}
51 
runInvocation(std::shared_ptr<CompilerInvocation> Invocation,FileManager * FileMgr,std::shared_ptr<PCHContainerOperations> PCHContainerOps,DiagnosticConsumer * DiagConsumer)52   bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
53                      FileManager *FileMgr,
54                      std::shared_ptr<PCHContainerOperations> PCHContainerOps,
55                      DiagnosticConsumer *DiagConsumer) override {
56     CompilerInstance Compiler(std::move(PCHContainerOps));
57     Compiler.setInvocation(std::move(Invocation));
58     Compiler.setFileManager(FileMgr);
59 
60     Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);
61     if (!Compiler.hasDiagnostics())
62       return false;
63 
64     Compiler.createSourceManager(*FileMgr);
65     Compiler.addDependencyCollector(std::make_shared<TestFileCollector>(
66         Compiler.getInvocation().getDependencyOutputOpts(), Deps));
67 
68     auto Action = std::make_unique<PreprocessOnlyAction>();
69     return Compiler.ExecuteAction(*Action);
70   }
71 
72 private:
73   std::vector<std::string> &Deps;
74 };
75 
76 } // namespace
77 
TEST(DependencyScanner,ScanDepsReuseFilemanager)78 TEST(DependencyScanner, ScanDepsReuseFilemanager) {
79   std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"};
80   StringRef CWD = "/root";
81   FixedCompilationDatabase CDB(CWD, Compilation);
82 
83   auto VFS = new llvm::vfs::InMemoryFileSystem();
84   VFS->setCurrentWorkingDirectory(CWD);
85   auto Sept = llvm::sys::path::get_separator();
86   std::string HeaderPath = llvm::formatv("{0}root{0}header.h", Sept);
87   std::string SymlinkPath = llvm::formatv("{0}root{0}symlink.h", Sept);
88   std::string TestPath = llvm::formatv("{0}root{0}test.cpp", Sept);
89 
90   VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
91   VFS->addHardLink(SymlinkPath, HeaderPath);
92   VFS->addFile(TestPath, 0,
93                llvm::MemoryBuffer::getMemBuffer(
94                    "#include \"symlink.h\"\n#include \"header.h\"\n"));
95 
96   ClangTool Tool(CDB, {"test.cpp"}, std::make_shared<PCHContainerOperations>(),
97                  VFS);
98   Tool.clearArgumentsAdjusters();
99   std::vector<std::string> Deps;
100   TestDependencyScanningAction Action(Deps);
101   Tool.run(&Action);
102   using llvm::sys::path::convert_to_slash;
103   // The first invocation should return dependencies in order of access.
104   ASSERT_EQ(Deps.size(), 3u);
105   EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");
106   EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h");
107   EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h");
108 
109   // The file manager should still have two FileEntries, as one file is a
110   // hardlink.
111   FileManager &Files = Tool.getFiles();
112   EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u);
113 
114   Deps.clear();
115   Tool.run(&Action);
116   // The second invocation should have the same order of dependencies.
117   ASSERT_EQ(Deps.size(), 3u);
118   EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");
119   EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h");
120   EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h");
121 
122   EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u);
123 }
124 
TEST(DependencyScanner,ScanDepsReuseFilemanagerSkippedFile)125 TEST(DependencyScanner, ScanDepsReuseFilemanagerSkippedFile) {
126   std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"};
127   StringRef CWD = "/root";
128   FixedCompilationDatabase CDB(CWD, Compilation);
129 
130   auto VFS = new llvm::vfs::InMemoryFileSystem();
131   VFS->setCurrentWorkingDirectory(CWD);
132   auto Sept = llvm::sys::path::get_separator();
133   std::string HeaderPath = llvm::formatv("{0}root{0}header.h", Sept);
134   std::string SymlinkPath = llvm::formatv("{0}root{0}symlink.h", Sept);
135   std::string TestPath = llvm::formatv("{0}root{0}test.cpp", Sept);
136   std::string Test2Path = llvm::formatv("{0}root{0}test2.cpp", Sept);
137 
138   VFS->addFile(HeaderPath, 0,
139                llvm::MemoryBuffer::getMemBuffer("#pragma once\n"));
140   VFS->addHardLink(SymlinkPath, HeaderPath);
141   VFS->addFile(TestPath, 0,
142                llvm::MemoryBuffer::getMemBuffer(
143                    "#include \"header.h\"\n#include \"symlink.h\"\n"));
144   VFS->addFile(Test2Path, 0,
145                llvm::MemoryBuffer::getMemBuffer(
146                    "#include \"symlink.h\"\n#include \"header.h\"\n"));
147 
148   ClangTool Tool(CDB, {"test.cpp", "test2.cpp"},
149                  std::make_shared<PCHContainerOperations>(), VFS);
150   Tool.clearArgumentsAdjusters();
151   std::vector<std::string> Deps;
152   TestDependencyScanningAction Action(Deps);
153   Tool.run(&Action);
154   using llvm::sys::path::convert_to_slash;
155   ASSERT_EQ(Deps.size(), 6u);
156   EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");
157   EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h");
158   EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h");
159   EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test2.cpp");
160   EXPECT_EQ(convert_to_slash(Deps[4]), "/root/symlink.h");
161   EXPECT_EQ(convert_to_slash(Deps[5]), "/root/header.h");
162 }
163 
TEST(DependencyScanner,ScanDepsReuseFilemanagerHasInclude)164 TEST(DependencyScanner, ScanDepsReuseFilemanagerHasInclude) {
165   std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"};
166   StringRef CWD = "/root";
167   FixedCompilationDatabase CDB(CWD, Compilation);
168 
169   auto VFS = new llvm::vfs::InMemoryFileSystem();
170   VFS->setCurrentWorkingDirectory(CWD);
171   auto Sept = llvm::sys::path::get_separator();
172   std::string HeaderPath = llvm::formatv("{0}root{0}header.h", Sept);
173   std::string SymlinkPath = llvm::formatv("{0}root{0}symlink.h", Sept);
174   std::string TestPath = llvm::formatv("{0}root{0}test.cpp", Sept);
175 
176   VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));
177   VFS->addHardLink(SymlinkPath, HeaderPath);
178   VFS->addFile(
179       TestPath, 0,
180       llvm::MemoryBuffer::getMemBuffer("#if __has_include(\"header.h\") && "
181                                        "__has_include(\"symlink.h\")\n#endif"));
182 
183   ClangTool Tool(CDB, {"test.cpp", "test.cpp"},
184                  std::make_shared<PCHContainerOperations>(), VFS);
185   Tool.clearArgumentsAdjusters();
186   std::vector<std::string> Deps;
187   TestDependencyScanningAction Action(Deps);
188   Tool.run(&Action);
189   using llvm::sys::path::convert_to_slash;
190   ASSERT_EQ(Deps.size(), 6u);
191   EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");
192   EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h");
193   EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h");
194   EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test.cpp");
195   EXPECT_EQ(convert_to_slash(Deps[4]), "/root/header.h");
196   EXPECT_EQ(convert_to_slash(Deps[5]), "/root/symlink.h");
197 }
198 
199 } // end namespace tooling
200 } // end namespace clang
201