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