//===- unittest/Tooling/ToolingTest.cpp - Tooling unit tests --------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/AST/ASTConsumer.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclGroup.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" #include "gtest/gtest.h" #include #include namespace clang { namespace tooling { namespace { /// Prints out all of the gathered dependencies into a string. class TestFileCollector : public DependencyFileGenerator { public: TestFileCollector(DependencyOutputOptions &Opts, std::vector &Deps) : DependencyFileGenerator(Opts), Deps(Deps) {} void finishedMainFile(DiagnosticsEngine &Diags) override { auto NewDeps = getDependencies(); Deps.insert(Deps.end(), NewDeps.begin(), NewDeps.end()); } private: std::vector &Deps; }; class TestDependencyScanningAction : public tooling::ToolAction { public: TestDependencyScanningAction(std::vector &Deps) : Deps(Deps) {} bool runInvocation(std::shared_ptr Invocation, FileManager *FileMgr, std::shared_ptr PCHContainerOps, DiagnosticConsumer *DiagConsumer) override { CompilerInstance Compiler(std::move(PCHContainerOps)); Compiler.setInvocation(std::move(Invocation)); Compiler.setFileManager(FileMgr); Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); if (!Compiler.hasDiagnostics()) return false; Compiler.createSourceManager(*FileMgr); Compiler.addDependencyCollector(std::make_shared( Compiler.getInvocation().getDependencyOutputOpts(), Deps)); auto Action = std::make_unique(); return Compiler.ExecuteAction(*Action); } private: std::vector &Deps; }; } // namespace TEST(DependencyScanner, ScanDepsReuseFilemanager) { std::vector Compilation = {"-c", "-E", "-MT", "test.cpp.o"}; StringRef CWD = "/root"; FixedCompilationDatabase CDB(CWD, Compilation); auto VFS = new llvm::vfs::InMemoryFileSystem(); VFS->setCurrentWorkingDirectory(CWD); auto Sept = llvm::sys::path::get_separator(); std::string HeaderPath = llvm::formatv("{0}root{0}header.h", Sept); std::string SymlinkPath = llvm::formatv("{0}root{0}symlink.h", Sept); std::string TestPath = llvm::formatv("{0}root{0}test.cpp", Sept); VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n")); VFS->addHardLink(SymlinkPath, HeaderPath); VFS->addFile(TestPath, 0, llvm::MemoryBuffer::getMemBuffer( "#include \"symlink.h\"\n#include \"header.h\"\n")); ClangTool Tool(CDB, {"test.cpp"}, std::make_shared(), VFS); Tool.clearArgumentsAdjusters(); std::vector Deps; TestDependencyScanningAction Action(Deps); Tool.run(&Action); using llvm::sys::path::convert_to_slash; // The first invocation should return dependencies in order of access. ASSERT_EQ(Deps.size(), 3u); EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h"); EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h"); // The file manager should still have two FileEntries, as one file is a // hardlink. FileManager &Files = Tool.getFiles(); EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u); Deps.clear(); Tool.run(&Action); // The second invocation should have the same order of dependencies. ASSERT_EQ(Deps.size(), 3u); EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h"); EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h"); EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u); } TEST(DependencyScanner, ScanDepsReuseFilemanagerSkippedFile) { std::vector Compilation = {"-c", "-E", "-MT", "test.cpp.o"}; StringRef CWD = "/root"; FixedCompilationDatabase CDB(CWD, Compilation); auto VFS = new llvm::vfs::InMemoryFileSystem(); VFS->setCurrentWorkingDirectory(CWD); auto Sept = llvm::sys::path::get_separator(); std::string HeaderPath = llvm::formatv("{0}root{0}header.h", Sept); std::string SymlinkPath = llvm::formatv("{0}root{0}symlink.h", Sept); std::string TestPath = llvm::formatv("{0}root{0}test.cpp", Sept); std::string Test2Path = llvm::formatv("{0}root{0}test2.cpp", Sept); VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("#pragma once\n")); VFS->addHardLink(SymlinkPath, HeaderPath); VFS->addFile(TestPath, 0, llvm::MemoryBuffer::getMemBuffer( "#include \"header.h\"\n#include \"symlink.h\"\n")); VFS->addFile(Test2Path, 0, llvm::MemoryBuffer::getMemBuffer( "#include \"symlink.h\"\n#include \"header.h\"\n")); ClangTool Tool(CDB, {"test.cpp", "test2.cpp"}, std::make_shared(), VFS); Tool.clearArgumentsAdjusters(); std::vector Deps; TestDependencyScanningAction Action(Deps); Tool.run(&Action); using llvm::sys::path::convert_to_slash; ASSERT_EQ(Deps.size(), 6u); EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h"); EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h"); EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test2.cpp"); EXPECT_EQ(convert_to_slash(Deps[4]), "/root/symlink.h"); EXPECT_EQ(convert_to_slash(Deps[5]), "/root/header.h"); } TEST(DependencyScanner, ScanDepsReuseFilemanagerHasInclude) { std::vector Compilation = {"-c", "-E", "-MT", "test.cpp.o"}; StringRef CWD = "/root"; FixedCompilationDatabase CDB(CWD, Compilation); auto VFS = new llvm::vfs::InMemoryFileSystem(); VFS->setCurrentWorkingDirectory(CWD); auto Sept = llvm::sys::path::get_separator(); std::string HeaderPath = llvm::formatv("{0}root{0}header.h", Sept); std::string SymlinkPath = llvm::formatv("{0}root{0}symlink.h", Sept); std::string TestPath = llvm::formatv("{0}root{0}test.cpp", Sept); VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n")); VFS->addHardLink(SymlinkPath, HeaderPath); VFS->addFile( TestPath, 0, llvm::MemoryBuffer::getMemBuffer("#if __has_include(\"header.h\") && " "__has_include(\"symlink.h\")\n#endif")); ClangTool Tool(CDB, {"test.cpp", "test.cpp"}, std::make_shared(), VFS); Tool.clearArgumentsAdjusters(); std::vector Deps; TestDependencyScanningAction Action(Deps); Tool.run(&Action); using llvm::sys::path::convert_to_slash; ASSERT_EQ(Deps.size(), 6u); EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h"); EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h"); EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test.cpp"); EXPECT_EQ(convert_to_slash(Deps[4]), "/root/header.h"); EXPECT_EQ(convert_to_slash(Deps[5]), "/root/symlink.h"); } } // end namespace tooling } // end namespace clang