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: 35 TestFileCollector(DependencyOutputOptions &Opts, 36 std::vector<std::string> &Deps) 37 : DependencyFileGenerator(Opts), Deps(Deps) {} 38 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: 50 TestDependencyScanningAction(std::vector<std::string> &Deps) : Deps(Deps) {} 51 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 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 = 87 std::string(llvm::formatv("{0}root{0}header.h", Sept)); 88 std::string SymlinkPath = 89 std::string(llvm::formatv("{0}root{0}symlink.h", Sept)); 90 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept)); 91 92 VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n")); 93 VFS->addHardLink(SymlinkPath, HeaderPath); 94 VFS->addFile(TestPath, 0, 95 llvm::MemoryBuffer::getMemBuffer( 96 "#include \"symlink.h\"\n#include \"header.h\"\n")); 97 98 ClangTool Tool(CDB, {"test.cpp"}, std::make_shared<PCHContainerOperations>(), 99 VFS); 100 Tool.clearArgumentsAdjusters(); 101 std::vector<std::string> Deps; 102 TestDependencyScanningAction Action(Deps); 103 Tool.run(&Action); 104 using llvm::sys::path::convert_to_slash; 105 // The first invocation should return dependencies in order of access. 106 ASSERT_EQ(Deps.size(), 3u); 107 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); 108 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h"); 109 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h"); 110 111 // The file manager should still have two FileEntries, as one file is a 112 // hardlink. 113 FileManager &Files = Tool.getFiles(); 114 EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u); 115 116 Deps.clear(); 117 Tool.run(&Action); 118 // The second invocation should have the same order of dependencies. 119 ASSERT_EQ(Deps.size(), 3u); 120 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); 121 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h"); 122 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h"); 123 124 EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u); 125 } 126 127 TEST(DependencyScanner, ScanDepsReuseFilemanagerSkippedFile) { 128 std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"}; 129 StringRef CWD = "/root"; 130 FixedCompilationDatabase CDB(CWD, Compilation); 131 132 auto VFS = new llvm::vfs::InMemoryFileSystem(); 133 VFS->setCurrentWorkingDirectory(CWD); 134 auto Sept = llvm::sys::path::get_separator(); 135 std::string HeaderPath = 136 std::string(llvm::formatv("{0}root{0}header.h", Sept)); 137 std::string SymlinkPath = 138 std::string(llvm::formatv("{0}root{0}symlink.h", Sept)); 139 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept)); 140 std::string Test2Path = 141 std::string(llvm::formatv("{0}root{0}test2.cpp", Sept)); 142 143 VFS->addFile(HeaderPath, 0, 144 llvm::MemoryBuffer::getMemBuffer("#pragma once\n")); 145 VFS->addHardLink(SymlinkPath, HeaderPath); 146 VFS->addFile(TestPath, 0, 147 llvm::MemoryBuffer::getMemBuffer( 148 "#include \"header.h\"\n#include \"symlink.h\"\n")); 149 VFS->addFile(Test2Path, 0, 150 llvm::MemoryBuffer::getMemBuffer( 151 "#include \"symlink.h\"\n#include \"header.h\"\n")); 152 153 ClangTool Tool(CDB, {"test.cpp", "test2.cpp"}, 154 std::make_shared<PCHContainerOperations>(), VFS); 155 Tool.clearArgumentsAdjusters(); 156 std::vector<std::string> Deps; 157 TestDependencyScanningAction Action(Deps); 158 Tool.run(&Action); 159 using llvm::sys::path::convert_to_slash; 160 ASSERT_EQ(Deps.size(), 6u); 161 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); 162 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h"); 163 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h"); 164 EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test2.cpp"); 165 EXPECT_EQ(convert_to_slash(Deps[4]), "/root/symlink.h"); 166 EXPECT_EQ(convert_to_slash(Deps[5]), "/root/header.h"); 167 } 168 169 TEST(DependencyScanner, ScanDepsReuseFilemanagerHasInclude) { 170 std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"}; 171 StringRef CWD = "/root"; 172 FixedCompilationDatabase CDB(CWD, Compilation); 173 174 auto VFS = new llvm::vfs::InMemoryFileSystem(); 175 VFS->setCurrentWorkingDirectory(CWD); 176 auto Sept = llvm::sys::path::get_separator(); 177 std::string HeaderPath = 178 std::string(llvm::formatv("{0}root{0}header.h", Sept)); 179 std::string SymlinkPath = 180 std::string(llvm::formatv("{0}root{0}symlink.h", Sept)); 181 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept)); 182 183 VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n")); 184 VFS->addHardLink(SymlinkPath, HeaderPath); 185 VFS->addFile( 186 TestPath, 0, 187 llvm::MemoryBuffer::getMemBuffer("#if __has_include(\"header.h\") && " 188 "__has_include(\"symlink.h\")\n#endif")); 189 190 ClangTool Tool(CDB, {"test.cpp", "test.cpp"}, 191 std::make_shared<PCHContainerOperations>(), VFS); 192 Tool.clearArgumentsAdjusters(); 193 std::vector<std::string> Deps; 194 TestDependencyScanningAction Action(Deps); 195 Tool.run(&Action); 196 using llvm::sys::path::convert_to_slash; 197 ASSERT_EQ(Deps.size(), 6u); 198 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); 199 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h"); 200 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h"); 201 EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test.cpp"); 202 EXPECT_EQ(convert_to_slash(Deps[4]), "/root/header.h"); 203 EXPECT_EQ(convert_to_slash(Deps[5]), "/root/symlink.h"); 204 } 205 206 } // end namespace tooling 207 } // end namespace clang 208