llvm-project/clang/unittests/Tooling/ToolingTest.cpp

348 lines
11 KiB
C++
Raw Normal View History

//===- 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 "gtest/gtest.h"
#include <string>
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(clang::ASTConsumer *TestConsumer)
: TestConsumer(TestConsumer) {}
protected:
virtual clang::ASTConsumer* CreateASTConsumer(
clang::CompilerInstance& compiler, StringRef dummy) {
/// TestConsumer will be deleted by the framework calling us.
return TestConsumer;
}
private:
clang::ASTConsumer * const TestConsumer;
};
class FindTopLevelDeclConsumer : public clang::ASTConsumer {
public:
explicit FindTopLevelDeclConsumer(bool *FoundTopLevelDecl)
: FoundTopLevelDecl(FoundTopLevelDecl) {}
virtual bool HandleTopLevelDecl(clang::DeclGroupRef DeclGroup) {
*FoundTopLevelDecl = true;
return true;
}
private:
bool * const FoundTopLevelDecl;
};
} // end namespace
TEST(runToolOnCode, FindsNoTopLevelDeclOnEmptyCode) {
bool FoundTopLevelDecl = false;
EXPECT_TRUE(runToolOnCode(
new TestAction(new FindTopLevelDeclConsumer(&FoundTopLevelDecl)), ""));
EXPECT_FALSE(FoundTopLevelDecl);
}
namespace {
class FindClassDeclXConsumer : public clang::ASTConsumer {
public:
FindClassDeclXConsumer(bool *FoundClassDeclX)
: FoundClassDeclX(FoundClassDeclX) {}
virtual bool HandleTopLevelDecl(clang::DeclGroupRef GroupRef) {
if (CXXRecordDecl* Record = dyn_cast<clang::CXXRecordDecl>(
*GroupRef.begin())) {
if (Record->getName() == "X") {
*FoundClassDeclX = true;
}
}
return true;
}
private:
bool *FoundClassDeclX;
};
bool FindClassDeclX(ASTUnit *AST) {
for (std::vector<Decl *>::iterator i = AST->top_level_begin(),
e = AST->top_level_end();
i != e; ++i) {
if (CXXRecordDecl* Record = dyn_cast<clang::CXXRecordDecl>(*i)) {
if (Record->getName() == "X") {
return true;
}
}
}
return false;
}
} // end namespace
TEST(runToolOnCode, FindsClassDecl) {
bool FoundClassDeclX = false;
EXPECT_TRUE(runToolOnCode(new TestAction(
new FindClassDeclXConsumer(&FoundClassDeclX)), "class X;"));
EXPECT_TRUE(FoundClassDeclX);
FoundClassDeclX = false;
EXPECT_TRUE(runToolOnCode(new TestAction(
new FindClassDeclXConsumer(&FoundClassDeclX)), "class Y;"));
EXPECT_FALSE(FoundClassDeclX);
}
TEST(buildASTFromCode, FindsClassDecl) {
OwningPtr<ASTUnit> AST(buildASTFromCode("class X;"));
ASSERT_TRUE(AST.get());
EXPECT_TRUE(FindClassDeclX(AST.get()));
AST.reset(buildASTFromCode("class Y;"));
ASSERT_TRUE(AST.get());
EXPECT_FALSE(FindClassDeclX(AST.get()));
}
TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromType) {
OwningPtr<FrontendActionFactory> Factory(
newFrontendActionFactory<SyntaxOnlyAction>());
OwningPtr<FrontendAction> Action(Factory->create());
EXPECT_TRUE(Action.get() != NULL);
}
struct IndependentFrontendActionCreator {
ASTConsumer *newASTConsumer() {
return new FindTopLevelDeclConsumer(NULL);
}
};
TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) {
IndependentFrontendActionCreator Creator;
OwningPtr<FrontendActionFactory> Factory(
newFrontendActionFactory(&Creator));
OwningPtr<FrontendAction> Action(Factory->create());
EXPECT_TRUE(Action.get() != NULL);
}
TEST(ToolInvocation, TestMapVirtualFile) {
IntrusiveRefCntPtr<clang::FileManager> Files(
new clang::FileManager(clang::FileSystemOptions()));
std::vector<std::string> 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.getPtr());
Invocation.mapVirtualFile("test.cpp", "#include <abc>\n");
Invocation.mapVirtualFile("def/abc", "\n");
EXPECT_TRUE(Invocation.run());
}
Use the same SourceManager for ModuleMaps and compilations. This allows using virtual file mappings on the original SourceManager to map in virtual module.map files. Without this patch, the ModuleMap search will find a module.map file (as the FileEntry exists in the FileManager), but will be unable to get the content from the SourceManager (as ModuleMap previously created its own SourceManager). Two problems needed to be fixed which this patch exposed: 1. Storing the inferred module map When writing out a module, the ASTWriter stores the names of the files in the main source manager; when loading the AST again, the ASTReader errs out if such a file is found missing, unless it is overridden. Previously CompilerInstance's compileModule method would store the inferred module map to a temporary file; the problem with this approach is that now that the module map is handled by the main source manager, the ASTWriter stores the name of the temporary module map as source to the compilation; later, when the module is loaded, the temporary file has already been deleted, which leads to a compilation error. This patch changes the inferred module map to instead inject a virtual file into the source manager. This both saves some disk IO, and works with how the ASTWriter/ASTReader handle overridden source files. 2. Changing test input in test/Modules/Inputs/* Now that the module map file is handled by the main source manager, the VerifyDiagnosticConsumer will not ignore diagnostics created while parsing the module map file. The module test test/Modules/renamed.m uses -I test/Modules/Inputs and triggers recursive loading of all module maps in test/Modules/Inputs, some of which had conflicting names, thus leading errors while parsing the module maps. Those diagnostics already occur on trunk, but before this patch they would not break the test, as they were ignored by the VerifyDiagnosticConsumer. This patch thus changes the module maps that have been recently introduced which broke the invariant of compatible modules maps in test/Modules/Inputs. llvm-svn: 193314
2013-10-24 15:51:24 +08:00
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.
IntrusiveRefCntPtr<clang::FileManager> Files(
new clang::FileManager(clang::FileSystemOptions()));
Use the same SourceManager for ModuleMaps and compilations. This allows using virtual file mappings on the original SourceManager to map in virtual module.map files. Without this patch, the ModuleMap search will find a module.map file (as the FileEntry exists in the FileManager), but will be unable to get the content from the SourceManager (as ModuleMap previously created its own SourceManager). Two problems needed to be fixed which this patch exposed: 1. Storing the inferred module map When writing out a module, the ASTWriter stores the names of the files in the main source manager; when loading the AST again, the ASTReader errs out if such a file is found missing, unless it is overridden. Previously CompilerInstance's compileModule method would store the inferred module map to a temporary file; the problem with this approach is that now that the module map is handled by the main source manager, the ASTWriter stores the name of the temporary module map as source to the compilation; later, when the module is loaded, the temporary file has already been deleted, which leads to a compilation error. This patch changes the inferred module map to instead inject a virtual file into the source manager. This both saves some disk IO, and works with how the ASTWriter/ASTReader handle overridden source files. 2. Changing test input in test/Modules/Inputs/* Now that the module map file is handled by the main source manager, the VerifyDiagnosticConsumer will not ignore diagnostics created while parsing the module map file. The module test test/Modules/renamed.m uses -I test/Modules/Inputs and triggers recursive loading of all module maps in test/Modules/Inputs, some of which had conflicting names, thus leading errors while parsing the module maps. Those diagnostics already occur on trunk, but before this patch they would not break the test, as they were ignored by the VerifyDiagnosticConsumer. This patch thus changes the module maps that have been recently introduced which broke the invariant of compatible modules maps in test/Modules/Inputs. llvm-svn: 193314
2013-10-24 15:51:24 +08:00
std::vector<std::string> 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.getPtr());
Use the same SourceManager for ModuleMaps and compilations. This allows using virtual file mappings on the original SourceManager to map in virtual module.map files. Without this patch, the ModuleMap search will find a module.map file (as the FileEntry exists in the FileManager), but will be unable to get the content from the SourceManager (as ModuleMap previously created its own SourceManager). Two problems needed to be fixed which this patch exposed: 1. Storing the inferred module map When writing out a module, the ASTWriter stores the names of the files in the main source manager; when loading the AST again, the ASTReader errs out if such a file is found missing, unless it is overridden. Previously CompilerInstance's compileModule method would store the inferred module map to a temporary file; the problem with this approach is that now that the module map is handled by the main source manager, the ASTWriter stores the name of the temporary module map as source to the compilation; later, when the module is loaded, the temporary file has already been deleted, which leads to a compilation error. This patch changes the inferred module map to instead inject a virtual file into the source manager. This both saves some disk IO, and works with how the ASTWriter/ASTReader handle overridden source files. 2. Changing test input in test/Modules/Inputs/* Now that the module map file is handled by the main source manager, the VerifyDiagnosticConsumer will not ignore diagnostics created while parsing the module map file. The module test test/Modules/renamed.m uses -I test/Modules/Inputs and triggers recursive loading of all module maps in test/Modules/Inputs, some of which had conflicting names, thus leading errors while parsing the module maps. Those diagnostics already occur on trunk, but before this patch they would not break the test, as they were ignored by the VerifyDiagnosticConsumer. This patch thus changes the module maps that have been recently introduced which broke the invariant of compatible modules maps in test/Modules/Inputs. llvm-svn: 193314
2013-10-24 15:51:24 +08:00
Invocation.mapVirtualFile("test.cpp", "#include <abc>\n");
Invocation.mapVirtualFile("def/abc", "\n");
// Add a module.map file in the include directory of our header, so we trigger
// the module.map header search logic.
Invocation.mapVirtualFile("def/module.map", "\n");
EXPECT_TRUE(Invocation.run());
}
struct VerifyEndCallback : public SourceFileCallbacks {
VerifyEndCallback() : BeginCalled(0), EndCalled(0), Matched(false) {}
virtual bool handleBeginSource(CompilerInstance &CI,
StringRef Filename) override {
++BeginCalled;
return true;
}
virtual void handleEndSource() {
++EndCalled;
}
ASTConsumer *newASTConsumer() {
return new FindTopLevelDeclConsumer(&Matched);
}
unsigned BeginCalled;
unsigned EndCalled;
bool Matched;
};
#if !defined(_WIN32)
TEST(newFrontendActionFactory, InjectsSourceFileCallbacks) {
VerifyEndCallback EndCallback;
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
std::vector<std::string> 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() {}");
Tool.run(newFrontendActionFactory(&EndCallback, &EndCallback));
EXPECT_TRUE(EndCallback.Matched);
EXPECT_EQ(2u, EndCallback.BeginCalled);
EXPECT_EQ(2u, EndCallback.EndCalled);
}
#endif
struct SkipBodyConsumer : public clang::ASTConsumer {
/// Skip the 'skipMe' function.
virtual bool shouldSkipFunctionBody(Decl *D) {
FunctionDecl *F = dyn_cast<FunctionDecl>(D);
return F && F->getNameAsString() == "skipMe";
}
};
struct SkipBodyAction : public clang::ASTFrontendAction {
virtual ASTConsumer *CreateASTConsumer(CompilerInstance &Compiler,
StringRef) {
Compiler.getFrontendOpts().SkipFunctionBodies = true;
return new SkipBodyConsumer;
}
};
TEST(runToolOnCode, TestSkipFunctionBody) {
EXPECT_TRUE(runToolOnCode(new SkipBodyAction,
"int skipMe() { an_error_here }"));
EXPECT_FALSE(runToolOnCode(new SkipBodyAction,
"int skipMeNot() { an_error_here }"));
}
TEST(runToolOnCodeWithArgs, TestNoDepFile) {
llvm::SmallString<32> DepFilePath;
ASSERT_FALSE(
llvm::sys::fs::createTemporaryFile("depfile", "d", DepFilePath));
std::vector<std::string> 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 CheckSyntaxOnlyAdjuster: public ArgumentsAdjuster {
bool &Found;
bool &Ran;
CheckSyntaxOnlyAdjuster(bool &Found, bool &Ran) : Found(Found), Ran(Ran) { }
virtual CommandLineArguments
Adjust(const CommandLineArguments &Args) override {
Ran = true;
for (unsigned I = 0, E = Args.size(); I != E; ++I) {
if (Args[I] == "-fsyntax-only") {
Found = true;
break;
}
}
return Args;
}
};
TEST(ClangToolTest, ArgumentAdjusters) {
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "void a() {}");
bool Found = false;
bool Ran = false;
Tool.appendArgumentsAdjuster(new CheckSyntaxOnlyAdjuster(Found, Ran));
Tool.run(newFrontendActionFactory<SyntaxOnlyAction>());
EXPECT_TRUE(Ran);
EXPECT_TRUE(Found);
Ran = Found = false;
Tool.clearArgumentsAdjusters();
Tool.appendArgumentsAdjuster(new CheckSyntaxOnlyAdjuster(Found, Ran));
Tool.appendArgumentsAdjuster(new ClangSyntaxOnlyAdjuster());
Tool.run(newFrontendActionFactory<SyntaxOnlyAction>());
EXPECT_TRUE(Ran);
EXPECT_FALSE(Found);
}
#ifndef _WIN32
TEST(ClangToolTest, BuildASTs) {
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
std::vector<std::string> 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<ASTUnit *> ASTs;
EXPECT_EQ(0, Tool.buildASTs(ASTs));
EXPECT_EQ(2u, ASTs.size());
llvm::DeleteContainerPointers(ASTs);
}
struct TestDiagnosticConsumer : public DiagnosticConsumer {
TestDiagnosticConsumer() : NumDiagnosticsSeen(0) {}
virtual void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) {
++NumDiagnosticsSeen;
}
unsigned NumDiagnosticsSeen;
};
TEST(ClangToolTest, InjectDiagnosticConsumer) {
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "int x = undeclared;");
TestDiagnosticConsumer Consumer;
Tool.setDiagnosticConsumer(&Consumer);
Tool.run(newFrontendActionFactory<SyntaxOnlyAction>());
EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen);
}
TEST(ClangToolTest, InjectDiagnosticConsumerInBuildASTs) {
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "int x = undeclared;");
TestDiagnosticConsumer Consumer;
Tool.setDiagnosticConsumer(&Consumer);
std::vector<ASTUnit*> ASTs;
Tool.buildASTs(ASTs);
EXPECT_EQ(1u, ASTs.size());
EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen);
}
#endif
} // end namespace tooling
} // end namespace clang