388 lines · cpp
1//===- DependencyScannerTest.cpp ------------------------------------------===//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-exception6//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/DependencyScanning/DependencyScanningWorker.h"19#include "clang/Tooling/Tooling.h"20#include "llvm/ADT/STLExtras.h"21#include "llvm/MC/TargetRegistry.h"22#include "llvm/Support/FormatVariadic.h"23#include "llvm/Support/Path.h"24#include "llvm/Support/TargetSelect.h"25#include "llvm/Testing/Support/Error.h"26#include "gtest/gtest.h"27#include <algorithm>28#include <string>29 30using namespace clang;31using namespace tooling;32using namespace dependencies;33 34namespace {35 36/// Prints out all of the gathered dependencies into a string.37class TestFileCollector : public DependencyFileGenerator {38public:39 TestFileCollector(DependencyOutputOptions &Opts,40 std::vector<std::string> &Deps)41 : DependencyFileGenerator(Opts), Deps(Deps) {}42 43 void finishedMainFile(DiagnosticsEngine &Diags) override {44 auto NewDeps = getDependencies();45 llvm::append_range(Deps, NewDeps);46 }47 48private:49 std::vector<std::string> &Deps;50};51 52// FIXME: Use the regular Service/Worker/Collector APIs instead of53// reimplementing the action.54class TestDependencyScanningAction : public tooling::ToolAction {55public:56 TestDependencyScanningAction(std::vector<std::string> &Deps) : Deps(Deps) {}57 58 bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,59 FileManager *FileMgr,60 std::shared_ptr<PCHContainerOperations> PCHContainerOps,61 DiagnosticConsumer *DiagConsumer) override {62 CompilerInstance Compiler(std::move(Invocation),63 std::move(PCHContainerOps));64 Compiler.setVirtualFileSystem(FileMgr->getVirtualFileSystemPtr());65 Compiler.setFileManager(FileMgr);66 67 Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);68 if (!Compiler.hasDiagnostics())69 return false;70 71 Compiler.createSourceManager();72 Compiler.addDependencyCollector(std::make_shared<TestFileCollector>(73 Compiler.getInvocation().getDependencyOutputOpts(), Deps));74 75 auto Action = std::make_unique<PreprocessOnlyAction>();76 return Compiler.ExecuteAction(*Action);77 }78 79private:80 std::vector<std::string> &Deps;81};82 83} // namespace84 85TEST(DependencyScanner, ScanDepsReuseFilemanager) {86 std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"};87 StringRef CWD = "/root";88 FixedCompilationDatabase CDB(CWD, Compilation);89 90 auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();91 VFS->setCurrentWorkingDirectory(CWD);92 auto Sept = llvm::sys::path::get_separator();93 std::string HeaderPath =94 std::string(llvm::formatv("{0}root{0}header.h", Sept));95 std::string SymlinkPath =96 std::string(llvm::formatv("{0}root{0}symlink.h", Sept));97 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));98 99 VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));100 VFS->addHardLink(SymlinkPath, HeaderPath);101 VFS->addFile(TestPath, 0,102 llvm::MemoryBuffer::getMemBuffer(103 "#include \"symlink.h\"\n#include \"header.h\"\n"));104 105 ClangTool Tool(CDB, {"test.cpp"}, std::make_shared<PCHContainerOperations>(),106 VFS);107 Tool.clearArgumentsAdjusters();108 std::vector<std::string> Deps;109 TestDependencyScanningAction Action(Deps);110 Tool.run(&Action);111 using llvm::sys::path::convert_to_slash;112 // The first invocation should return dependencies in order of access.113 ASSERT_EQ(Deps.size(), 3u);114 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");115 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h");116 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h");117 118 // The file manager should still have two FileEntries, as one file is a119 // hardlink.120 FileManager &Files = Tool.getFiles();121 EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u);122 123 Deps.clear();124 Tool.run(&Action);125 // The second invocation should have the same order of dependencies.126 ASSERT_EQ(Deps.size(), 3u);127 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");128 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h");129 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h");130 131 EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u);132}133 134TEST(DependencyScanner, ScanDepsReuseFilemanagerSkippedFile) {135 std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"};136 StringRef CWD = "/root";137 FixedCompilationDatabase CDB(CWD, Compilation);138 139 auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();140 VFS->setCurrentWorkingDirectory(CWD);141 auto Sept = llvm::sys::path::get_separator();142 std::string HeaderPath =143 std::string(llvm::formatv("{0}root{0}header.h", Sept));144 std::string SymlinkPath =145 std::string(llvm::formatv("{0}root{0}symlink.h", Sept));146 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));147 std::string Test2Path =148 std::string(llvm::formatv("{0}root{0}test2.cpp", Sept));149 150 VFS->addFile(HeaderPath, 0,151 llvm::MemoryBuffer::getMemBuffer("#pragma once\n"));152 VFS->addHardLink(SymlinkPath, HeaderPath);153 VFS->addFile(TestPath, 0,154 llvm::MemoryBuffer::getMemBuffer(155 "#include \"header.h\"\n#include \"symlink.h\"\n"));156 VFS->addFile(Test2Path, 0,157 llvm::MemoryBuffer::getMemBuffer(158 "#include \"symlink.h\"\n#include \"header.h\"\n"));159 160 ClangTool Tool(CDB, {"test.cpp", "test2.cpp"},161 std::make_shared<PCHContainerOperations>(), VFS);162 Tool.clearArgumentsAdjusters();163 std::vector<std::string> Deps;164 TestDependencyScanningAction Action(Deps);165 Tool.run(&Action);166 using llvm::sys::path::convert_to_slash;167 ASSERT_EQ(Deps.size(), 6u);168 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");169 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h");170 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h");171 EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test2.cpp");172 EXPECT_EQ(convert_to_slash(Deps[4]), "/root/symlink.h");173 EXPECT_EQ(convert_to_slash(Deps[5]), "/root/header.h");174}175 176TEST(DependencyScanner, ScanDepsReuseFilemanagerHasInclude) {177 std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"};178 StringRef CWD = "/root";179 FixedCompilationDatabase CDB(CWD, Compilation);180 181 auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();182 VFS->setCurrentWorkingDirectory(CWD);183 auto Sept = llvm::sys::path::get_separator();184 std::string HeaderPath =185 std::string(llvm::formatv("{0}root{0}header.h", Sept));186 std::string SymlinkPath =187 std::string(llvm::formatv("{0}root{0}symlink.h", Sept));188 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));189 190 VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));191 VFS->addHardLink(SymlinkPath, HeaderPath);192 VFS->addFile(193 TestPath, 0,194 llvm::MemoryBuffer::getMemBuffer("#if __has_include(\"header.h\") && "195 "__has_include(\"symlink.h\")\n#endif"));196 197 ClangTool Tool(CDB, {"test.cpp", "test.cpp"},198 std::make_shared<PCHContainerOperations>(), VFS);199 Tool.clearArgumentsAdjusters();200 std::vector<std::string> Deps;201 TestDependencyScanningAction Action(Deps);202 Tool.run(&Action);203 using llvm::sys::path::convert_to_slash;204 ASSERT_EQ(Deps.size(), 6u);205 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp");206 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h");207 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h");208 EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test.cpp");209 EXPECT_EQ(convert_to_slash(Deps[4]), "/root/header.h");210 EXPECT_EQ(convert_to_slash(Deps[5]), "/root/symlink.h");211}212 213TEST(DependencyScanner, ScanDepsWithFS) {214 std::vector<std::string> CommandLine = {"clang",215 "-target",216 "x86_64-apple-macosx10.7",217 "-c",218 "test.cpp",219 "-o"220 "test.cpp.o"};221 StringRef CWD = "/root";222 223 auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();224 VFS->setCurrentWorkingDirectory(CWD);225 auto Sept = llvm::sys::path::get_separator();226 std::string HeaderPath =227 std::string(llvm::formatv("{0}root{0}header.h", Sept));228 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));229 230 VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));231 VFS->addFile(TestPath, 0,232 llvm::MemoryBuffer::getMemBuffer("#include \"header.h\"\n"));233 234 DependencyScanningService Service(ScanningMode::DependencyDirectivesScan,235 ScanningOutputFormat::Make);236 DependencyScanningTool ScanTool(Service, VFS);237 238 std::string DepFile;239 ASSERT_THAT_ERROR(240 ScanTool.getDependencyFile(CommandLine, CWD).moveInto(DepFile),241 llvm::Succeeded());242 using llvm::sys::path::convert_to_slash;243 EXPECT_EQ(convert_to_slash(DepFile),244 "test.cpp.o: /root/test.cpp /root/header.h\n");245}246 247TEST(DependencyScanner, ScanDepsWithModuleLookup) {248 std::vector<std::string> CommandLine = {249 "clang",250 "-target",251 "x86_64-apple-macosx10.7",252 "-c",253 "test.m",254 "-o"255 "test.m.o",256 "-fmodules",257 "-I/root/SomeSources",258 };259 StringRef CWD = "/root";260 261 auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();262 VFS->setCurrentWorkingDirectory(CWD);263 auto Sept = llvm::sys::path::get_separator();264 std::string OtherPath =265 std::string(llvm::formatv("{0}root{0}SomeSources{0}other.h", Sept));266 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.m", Sept));267 268 VFS->addFile(OtherPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));269 VFS->addFile(TestPath, 0, llvm::MemoryBuffer::getMemBuffer("@import Foo;\n"));270 271 struct InterceptorFS : llvm::vfs::ProxyFileSystem {272 std::vector<std::string> StatPaths;273 std::vector<std::string> ReadFiles;274 275 InterceptorFS(IntrusiveRefCntPtr<FileSystem> UnderlyingFS)276 : ProxyFileSystem(UnderlyingFS) {}277 278 llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override {279 StatPaths.push_back(Path.str());280 return ProxyFileSystem::status(Path);281 }282 283 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>284 openFileForRead(const Twine &Path) override {285 ReadFiles.push_back(Path.str());286 return ProxyFileSystem::openFileForRead(Path);287 }288 };289 290 auto InterceptFS = llvm::makeIntrusiveRefCnt<InterceptorFS>(VFS);291 292 DependencyScanningService Service(ScanningMode::DependencyDirectivesScan,293 ScanningOutputFormat::Make);294 DependencyScanningTool ScanTool(Service, InterceptFS);295 296 // This will fail with "fatal error: module 'Foo' not found" but it doesn't297 // matter, the point of the test is to check that files are not read298 // unnecessarily.299 std::string DepFile;300 ASSERT_THAT_ERROR(301 ScanTool.getDependencyFile(CommandLine, CWD).moveInto(DepFile),302 llvm::Failed());303 304 EXPECT_TRUE(!llvm::is_contained(InterceptFS->StatPaths, OtherPath));305 EXPECT_EQ(InterceptFS->ReadFiles, std::vector<std::string>{"test.m"});306}307 308TEST(DependencyScanner, ScanDepsWithDiagConsumer) {309 StringRef CWD = "/root";310 311 auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();312 VFS->setCurrentWorkingDirectory(CWD);313 auto Sept = llvm::sys::path::get_separator();314 std::string HeaderPath =315 std::string(llvm::formatv("{0}root{0}header.h", Sept));316 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept));317 std::string AsmPath = std::string(llvm::formatv("{0}root{0}test.s", Sept));318 319 VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n"));320 VFS->addFile(TestPath, 0,321 llvm::MemoryBuffer::getMemBuffer("#include \"header.h\"\n"));322 VFS->addFile(AsmPath, 0, llvm::MemoryBuffer::getMemBuffer(""));323 324 DependencyScanningService Service(ScanningMode::DependencyDirectivesScan,325 ScanningOutputFormat::Make);326 DependencyScanningWorker Worker(Service, VFS);327 328 llvm::DenseSet<ModuleID> AlreadySeen;329 FullDependencyConsumer DC(AlreadySeen);330 CallbackActionController AC(nullptr);331 332 struct EnsureFinishedConsumer : public DiagnosticConsumer {333 bool Finished = false;334 void finish() override { Finished = true; }335 };336 337 {338 // Check that a successful scan calls DiagConsumer.finish().339 std::vector<std::string> Args = {"clang",340 "-target",341 "x86_64-apple-macosx10.7",342 "-c",343 "test.cpp",344 "-o"345 "test.cpp.o"};346 347 EnsureFinishedConsumer DiagConsumer;348 bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer);349 350 EXPECT_TRUE(Success);351 EXPECT_EQ(DiagConsumer.getNumErrors(), 0u);352 EXPECT_TRUE(DiagConsumer.Finished);353 }354 355 {356 // Check that an invalid command-line, which never enters the scanning357 // action calls DiagConsumer.finish().358 std::vector<std::string> Args = {"clang", "-invalid-arg"};359 EnsureFinishedConsumer DiagConsumer;360 bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer);361 362 EXPECT_FALSE(Success);363 EXPECT_GE(DiagConsumer.getNumErrors(), 1u);364 EXPECT_TRUE(DiagConsumer.Finished);365 }366 367 {368 // Check that a valid command line that produces no scanning jobs calls369 // DiagConsumer.finish().370 std::vector<std::string> Args = {"clang",371 "-target",372 "x86_64-apple-macosx10.7",373 "-c",374 "-x",375 "assembler",376 "test.s",377 "-o"378 "test.cpp.o"};379 380 EnsureFinishedConsumer DiagConsumer;381 bool Success = Worker.computeDependencies(CWD, Args, DC, AC, DiagConsumer);382 383 EXPECT_FALSE(Success);384 EXPECT_EQ(DiagConsumer.getNumErrors(), 1u);385 EXPECT_TRUE(DiagConsumer.Finished);386 }387}388