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: 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(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 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 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 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 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