//===- unittest/Tooling/ToolingTest.cpp - Tooling unit tests --------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #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/Config/llvm-config.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 { /// Takes an ast consumer and returns it from CreateASTConsumer. This only /// works with single translation unit compilations. class TestAction : public clang::ASTFrontendAction { public: /// Takes ownership of TestConsumer. explicit TestAction(std::unique_ptr TestConsumer) : TestConsumer(std::move(TestConsumer)) {} protected: std::unique_ptr CreateASTConsumer(clang::CompilerInstance &compiler, StringRef dummy) override { /// TestConsumer will be deleted by the framework calling us. return std::move(TestConsumer); } private: std::unique_ptr TestConsumer; }; class FindTopLevelDeclConsumer : public clang::ASTConsumer { public: explicit FindTopLevelDeclConsumer(bool *FoundTopLevelDecl) : FoundTopLevelDecl(FoundTopLevelDecl) {} bool HandleTopLevelDecl(clang::DeclGroupRef DeclGroup) override { *FoundTopLevelDecl = true; return true; } private: bool * const FoundTopLevelDecl; }; } // end namespace TEST(runToolOnCode, FindsNoTopLevelDeclOnEmptyCode) { bool FoundTopLevelDecl = false; EXPECT_TRUE( runToolOnCode(new TestAction(llvm::make_unique( &FoundTopLevelDecl)), "")); EXPECT_FALSE(FoundTopLevelDecl); } namespace { class FindClassDeclXConsumer : public clang::ASTConsumer { public: FindClassDeclXConsumer(bool *FoundClassDeclX) : FoundClassDeclX(FoundClassDeclX) {} bool HandleTopLevelDecl(clang::DeclGroupRef GroupRef) override { if (CXXRecordDecl* Record = dyn_cast( *GroupRef.begin())) { if (Record->getName() == "X") { *FoundClassDeclX = true; } } return true; } private: bool *FoundClassDeclX; }; bool FindClassDeclX(ASTUnit *AST) { for (std::vector::iterator i = AST->top_level_begin(), e = AST->top_level_end(); i != e; ++i) { if (CXXRecordDecl* Record = dyn_cast(*i)) { if (Record->getName() == "X") { return true; } } } return false; } } // end namespace TEST(runToolOnCode, FindsClassDecl) { bool FoundClassDeclX = false; EXPECT_TRUE( runToolOnCode(new TestAction(llvm::make_unique( &FoundClassDeclX)), "class X;")); EXPECT_TRUE(FoundClassDeclX); FoundClassDeclX = false; EXPECT_TRUE( runToolOnCode(new TestAction(llvm::make_unique( &FoundClassDeclX)), "class Y;")); EXPECT_FALSE(FoundClassDeclX); } TEST(buildASTFromCode, FindsClassDecl) { std::unique_ptr AST = buildASTFromCode("class X;"); ASSERT_TRUE(AST.get()); EXPECT_TRUE(FindClassDeclX(AST.get())); AST = buildASTFromCode("class Y;"); ASSERT_TRUE(AST.get()); EXPECT_FALSE(FindClassDeclX(AST.get())); } TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromType) { std::unique_ptr Factory( newFrontendActionFactory()); std::unique_ptr Action(Factory->create()); EXPECT_TRUE(Action.get() != nullptr); } struct IndependentFrontendActionCreator { std::unique_ptr newASTConsumer() { return llvm::make_unique(nullptr); } }; TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) { IndependentFrontendActionCreator Creator; std::unique_ptr Factory( newFrontendActionFactory(&Creator)); std::unique_ptr Action(Factory->create()); EXPECT_TRUE(Action.get() != nullptr); } TEST(ToolInvocation, TestMapVirtualFile) { llvm::IntrusiveRefCntPtr OverlayFileSystem( new vfs::OverlayFileSystem(vfs::getRealFileSystem())); llvm::IntrusiveRefCntPtr InMemoryFileSystem( new vfs::InMemoryFileSystem); OverlayFileSystem->pushOverlay(InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), OverlayFileSystem)); std::vector Args; Args.push_back("tool-executable"); Args.push_back("-Idef"); Args.push_back("-fsyntax-only"); Args.push_back("test.cpp"); clang::tooling::ToolInvocation Invocation(Args, new SyntaxOnlyAction, Files.get()); InMemoryFileSystem->addFile( "test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("#include \n")); InMemoryFileSystem->addFile("def/abc", 0, llvm::MemoryBuffer::getMemBuffer("\n")); EXPECT_TRUE(Invocation.run()); } TEST(ToolInvocation, TestVirtualModulesCompilation) { // FIXME: Currently, this only tests that we don't exit with an error if a // mapped module.map is found on the include path. In the future, expand this // test to run a full modules enabled compilation, so we make sure we can // rerun modules compilations with a virtual file system. llvm::IntrusiveRefCntPtr OverlayFileSystem( new vfs::OverlayFileSystem(vfs::getRealFileSystem())); llvm::IntrusiveRefCntPtr InMemoryFileSystem( new vfs::InMemoryFileSystem); OverlayFileSystem->pushOverlay(InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), OverlayFileSystem)); std::vector Args; Args.push_back("tool-executable"); Args.push_back("-Idef"); Args.push_back("-fsyntax-only"); Args.push_back("test.cpp"); clang::tooling::ToolInvocation Invocation(Args, new SyntaxOnlyAction, Files.get()); InMemoryFileSystem->addFile( "test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("#include \n")); InMemoryFileSystem->addFile("def/abc", 0, llvm::MemoryBuffer::getMemBuffer("\n")); // Add a module.map file in the include directory of our header, so we trigger // the module.map header search logic. InMemoryFileSystem->addFile("def/module.map", 0, llvm::MemoryBuffer::getMemBuffer("\n")); EXPECT_TRUE(Invocation.run()); } struct VerifyEndCallback : public SourceFileCallbacks { VerifyEndCallback() : BeginCalled(0), EndCalled(0), Matched(false) {} bool handleBeginSource(CompilerInstance &CI) override { ++BeginCalled; return true; } void handleEndSource() override { ++EndCalled; } std::unique_ptr newASTConsumer() { return llvm::make_unique(&Matched); } unsigned BeginCalled; unsigned EndCalled; bool Matched; }; #if !defined(LLVM_ON_WIN32) TEST(newFrontendActionFactory, InjectsSourceFileCallbacks) { VerifyEndCallback EndCallback; FixedCompilationDatabase Compilations("/", std::vector()); std::vector Sources; Sources.push_back("/a.cc"); Sources.push_back("/b.cc"); ClangTool Tool(Compilations, Sources); Tool.mapVirtualFile("/a.cc", "void a() {}"); Tool.mapVirtualFile("/b.cc", "void b() {}"); std::unique_ptr Action( newFrontendActionFactory(&EndCallback, &EndCallback)); Tool.run(Action.get()); EXPECT_TRUE(EndCallback.Matched); EXPECT_EQ(2u, EndCallback.BeginCalled); EXPECT_EQ(2u, EndCallback.EndCalled); } #endif struct SkipBodyConsumer : public clang::ASTConsumer { /// Skip the 'skipMe' function. bool shouldSkipFunctionBody(Decl *D) override { NamedDecl *F = dyn_cast(D); return F && F->getNameAsString() == "skipMe"; } }; struct SkipBodyAction : public clang::ASTFrontendAction { std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, StringRef) override { Compiler.getFrontendOpts().SkipFunctionBodies = true; return llvm::make_unique(); } }; TEST(runToolOnCode, TestSkipFunctionBody) { std::vector Args = {"-std=c++11"}; std::vector Args2 = {"-fno-delayed-template-parsing"}; EXPECT_TRUE(runToolOnCode(new SkipBodyAction, "int skipMe() { an_error_here }")); EXPECT_FALSE(runToolOnCode(new SkipBodyAction, "int skipMeNot() { an_error_here }")); // Test constructors with initializers EXPECT_TRUE(runToolOnCodeWithArgs( new SkipBodyAction, "struct skipMe { skipMe() : an_error() { more error } };", Args)); EXPECT_TRUE(runToolOnCodeWithArgs( new SkipBodyAction, "struct skipMe { skipMe(); };" "skipMe::skipMe() : an_error([](){;}) { more error }", Args)); EXPECT_TRUE(runToolOnCodeWithArgs( new SkipBodyAction, "struct skipMe { skipMe(); };" "skipMe::skipMe() : an_error{[](){;}} { more error }", Args)); EXPECT_TRUE(runToolOnCodeWithArgs( new SkipBodyAction, "struct skipMe { skipMe(); };" "skipMe::skipMe() : a(e)>>(), f{}, g() { error }", Args)); EXPECT_TRUE(runToolOnCodeWithArgs( new SkipBodyAction, "struct skipMe { skipMe() : bases()... { error } };", Args)); EXPECT_FALSE(runToolOnCodeWithArgs( new SkipBodyAction, "struct skipMeNot { skipMeNot() : an_error() { } };", Args)); EXPECT_FALSE(runToolOnCodeWithArgs(new SkipBodyAction, "struct skipMeNot { skipMeNot(); };" "skipMeNot::skipMeNot() : an_error() { }", Args)); // Try/catch EXPECT_TRUE(runToolOnCode( new SkipBodyAction, "void skipMe() try { an_error() } catch(error) { error };")); EXPECT_TRUE(runToolOnCode( new SkipBodyAction, "struct S { void skipMe() try { an_error() } catch(error) { error } };")); EXPECT_TRUE( runToolOnCode(new SkipBodyAction, "void skipMe() try { an_error() } catch(error) { error; }" "catch(error) { error } catch (error) { }")); EXPECT_FALSE(runToolOnCode( new SkipBodyAction, "void skipMe() try something;")); // don't crash while parsing // Template EXPECT_TRUE(runToolOnCode( new SkipBodyAction, "template int skipMe() { an_error_here }" "int x = skipMe();")); EXPECT_FALSE(runToolOnCodeWithArgs( new SkipBodyAction, "template int skipMeNot() { an_error_here }", Args2)); } TEST(runToolOnCodeWithArgs, TestNoDepFile) { llvm::SmallString<32> DepFilePath; ASSERT_FALSE( llvm::sys::fs::createTemporaryFile("depfile", "d", DepFilePath)); std::vector Args; Args.push_back("-MMD"); Args.push_back("-MT"); Args.push_back(DepFilePath.str()); Args.push_back("-MF"); Args.push_back(DepFilePath.str()); EXPECT_TRUE(runToolOnCodeWithArgs(new SkipBodyAction, "", Args)); EXPECT_FALSE(llvm::sys::fs::exists(DepFilePath.str())); EXPECT_FALSE(llvm::sys::fs::remove(DepFilePath.str())); } struct CheckColoredDiagnosticsAction : public clang::ASTFrontendAction { CheckColoredDiagnosticsAction(bool ShouldShowColor) : ShouldShowColor(ShouldShowColor) {} std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, StringRef) override { if (Compiler.getDiagnosticOpts().ShowColors != ShouldShowColor) Compiler.getDiagnostics().Report( Compiler.getDiagnostics().getCustomDiagID( DiagnosticsEngine::Fatal, "getDiagnosticOpts().ShowColors != ShouldShowColor")); return llvm::make_unique(); } private: bool ShouldShowColor = true; }; TEST(runToolOnCodeWithArgs, DiagnosticsColor) { EXPECT_TRUE(runToolOnCodeWithArgs(new CheckColoredDiagnosticsAction(true), "", {"-fcolor-diagnostics"})); EXPECT_TRUE(runToolOnCodeWithArgs(new CheckColoredDiagnosticsAction(false), "", {"-fno-color-diagnostics"})); EXPECT_TRUE( runToolOnCodeWithArgs(new CheckColoredDiagnosticsAction(true), "", {"-fno-color-diagnostics", "-fcolor-diagnostics"})); EXPECT_TRUE( runToolOnCodeWithArgs(new CheckColoredDiagnosticsAction(false), "", {"-fcolor-diagnostics", "-fno-color-diagnostics"})); EXPECT_TRUE(runToolOnCodeWithArgs( new CheckColoredDiagnosticsAction(true), "", {"-fno-color-diagnostics", "-fdiagnostics-color=always"})); // Check that this test would fail if ShowColors is not what it should. EXPECT_FALSE(runToolOnCodeWithArgs(new CheckColoredDiagnosticsAction(false), "", {"-fcolor-diagnostics"})); } TEST(ClangToolTest, ArgumentAdjusters) { FixedCompilationDatabase Compilations("/", std::vector()); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "void a() {}"); std::unique_ptr Action( newFrontendActionFactory()); bool Found = false; bool Ran = false; ArgumentsAdjuster CheckSyntaxOnlyAdjuster = [&Found, &Ran](const CommandLineArguments &Args, StringRef /*unused*/) { Ran = true; if (std::find(Args.begin(), Args.end(), "-fsyntax-only") != Args.end()) Found = true; return Args; }; Tool.appendArgumentsAdjuster(CheckSyntaxOnlyAdjuster); Tool.run(Action.get()); EXPECT_TRUE(Ran); EXPECT_TRUE(Found); Ran = Found = false; Tool.clearArgumentsAdjusters(); Tool.appendArgumentsAdjuster(CheckSyntaxOnlyAdjuster); Tool.appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster()); Tool.run(Action.get()); EXPECT_TRUE(Ran); EXPECT_FALSE(Found); } namespace { /// Find a target name such that looking for it in TargetRegistry by that name /// returns the same target. We expect that there is at least one target /// configured with this property. std::string getAnyTarget() { llvm::InitializeAllTargets(); for (const auto &Target : llvm::TargetRegistry::targets()) { std::string Error; StringRef TargetName(Target.getName()); if (TargetName == "x86-64") TargetName = "x86_64"; if (llvm::TargetRegistry::lookupTarget(TargetName, Error) == &Target) { return TargetName; } } return ""; } } TEST(addTargetAndModeForProgramName, AddsTargetAndMode) { std::string Target = getAnyTarget(); ASSERT_FALSE(Target.empty()); std::vector Args = {"clang", "-foo"}; addTargetAndModeForProgramName(Args, ""); EXPECT_EQ((std::vector{"clang", "-foo"}), Args); addTargetAndModeForProgramName(Args, Target + "-g++"); EXPECT_EQ((std::vector{"clang", "-target", Target, "--driver-mode=g++", "-foo"}), Args); } TEST(addTargetAndModeForProgramName, PathIgnored) { std::string Target = getAnyTarget(); ASSERT_FALSE(Target.empty()); SmallString<32> ToolPath; llvm::sys::path::append(ToolPath, "foo", "bar", Target + "-g++"); std::vector Args = {"clang", "-foo"}; addTargetAndModeForProgramName(Args, ToolPath); EXPECT_EQ((std::vector{"clang", "-target", Target, "--driver-mode=g++", "-foo"}), Args); } TEST(addTargetAndModeForProgramName, IgnoresExistingTarget) { std::string Target = getAnyTarget(); ASSERT_FALSE(Target.empty()); std::vector Args = {"clang", "-foo", "-target", "something"}; addTargetAndModeForProgramName(Args, Target + "-g++"); EXPECT_EQ((std::vector{"clang", "--driver-mode=g++", "-foo", "-target", "something"}), Args); std::vector ArgsAlt = {"clang", "-foo", "-target=something"}; addTargetAndModeForProgramName(ArgsAlt, Target + "-g++"); EXPECT_EQ((std::vector{"clang", "--driver-mode=g++", "-foo", "-target=something"}), ArgsAlt); } TEST(addTargetAndModeForProgramName, IgnoresExistingMode) { std::string Target = getAnyTarget(); ASSERT_FALSE(Target.empty()); std::vector Args = {"clang", "-foo", "--driver-mode=abc"}; addTargetAndModeForProgramName(Args, Target + "-g++"); EXPECT_EQ((std::vector{"clang", "-target", Target, "-foo", "--driver-mode=abc"}), Args); std::vector ArgsAlt = {"clang", "-foo", "--driver-mode", "abc"}; addTargetAndModeForProgramName(ArgsAlt, Target + "-g++"); EXPECT_EQ((std::vector{"clang", "-target", Target, "-foo", "--driver-mode", "abc"}), ArgsAlt); } #ifndef LLVM_ON_WIN32 TEST(ClangToolTest, BuildASTs) { FixedCompilationDatabase Compilations("/", std::vector()); std::vector Sources; Sources.push_back("/a.cc"); Sources.push_back("/b.cc"); ClangTool Tool(Compilations, Sources); Tool.mapVirtualFile("/a.cc", "void a() {}"); Tool.mapVirtualFile("/b.cc", "void b() {}"); std::vector> ASTs; EXPECT_EQ(0, Tool.buildASTs(ASTs)); EXPECT_EQ(2u, ASTs.size()); } struct TestDiagnosticConsumer : public DiagnosticConsumer { TestDiagnosticConsumer() : NumDiagnosticsSeen(0) {} void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override { ++NumDiagnosticsSeen; } unsigned NumDiagnosticsSeen; }; TEST(ClangToolTest, InjectDiagnosticConsumer) { FixedCompilationDatabase Compilations("/", std::vector()); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "int x = undeclared;"); TestDiagnosticConsumer Consumer; Tool.setDiagnosticConsumer(&Consumer); std::unique_ptr Action( newFrontendActionFactory()); Tool.run(Action.get()); EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen); } TEST(ClangToolTest, InjectDiagnosticConsumerInBuildASTs) { FixedCompilationDatabase Compilations("/", std::vector()); ClangTool Tool(Compilations, std::vector(1, "/a.cc")); Tool.mapVirtualFile("/a.cc", "int x = undeclared;"); TestDiagnosticConsumer Consumer; Tool.setDiagnosticConsumer(&Consumer); std::vector> ASTs; Tool.buildASTs(ASTs); EXPECT_EQ(1u, ASTs.size()); EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen); } #endif } // end namespace tooling } // end namespace clang