diff options
author | dim <dim@FreeBSD.org> | 2012-08-15 20:02:54 +0000 |
---|---|---|
committer | dim <dim@FreeBSD.org> | 2012-08-15 20:02:54 +0000 |
commit | 554bcb69c2d785a011a30e7db87a36a87fe7db10 (patch) | |
tree | 9abb1a658a297776086f4e0dfa6ca533de02104e /unittests/Tooling | |
parent | bb67ca86b31f67faee50bd10c3b036d65751745a (diff) | |
download | FreeBSD-src-554bcb69c2d785a011a30e7db87a36a87fe7db10.zip FreeBSD-src-554bcb69c2d785a011a30e7db87a36a87fe7db10.tar.gz |
Vendor import of clang trunk r161861:
http://llvm.org/svn/llvm-project/cfe/trunk@161861
Diffstat (limited to 'unittests/Tooling')
-rw-r--r-- | unittests/Tooling/CMakeLists.txt | 22 | ||||
-rw-r--r-- | unittests/Tooling/CommentHandlerTest.cpp | 221 | ||||
-rw-r--r-- | unittests/Tooling/CompilationDatabaseTest.cpp | 58 | ||||
-rw-r--r-- | unittests/Tooling/Makefile | 7 | ||||
-rw-r--r-- | unittests/Tooling/RecursiveASTVisitorTest.cpp | 388 | ||||
-rw-r--r-- | unittests/Tooling/RefactoringCallbacksTest.cpp | 100 | ||||
-rw-r--r-- | unittests/Tooling/RefactoringTest.cpp | 305 | ||||
-rw-r--r-- | unittests/Tooling/RewriterTest.cpp | 37 | ||||
-rw-r--r-- | unittests/Tooling/RewriterTestContext.h | 125 | ||||
-rw-r--r-- | unittests/Tooling/TestVisitor.h | 144 | ||||
-rw-r--r-- | unittests/Tooling/ToolingTest.cpp | 25 |
11 files changed, 1427 insertions, 5 deletions
diff --git a/unittests/Tooling/CMakeLists.txt b/unittests/Tooling/CMakeLists.txt new file mode 100644 index 0000000..4eaf339 --- /dev/null +++ b/unittests/Tooling/CMakeLists.txt @@ -0,0 +1,22 @@ +set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + asmparser + support + mc + ) + +add_clang_unittest(ToolingTests + CommentHandlerTest.cpp + CompilationDatabaseTest.cpp + ToolingTest.cpp + RecursiveASTVisitorTest.cpp + RefactoringTest.cpp + RewriterTest.cpp + RefactoringCallbacksTest.cpp + ) + +target_link_libraries(ToolingTests + clangAST + clangTooling + clangRewrite + ) diff --git a/unittests/Tooling/CommentHandlerTest.cpp b/unittests/Tooling/CommentHandlerTest.cpp new file mode 100644 index 0000000..f0f7797 --- /dev/null +++ b/unittests/Tooling/CommentHandlerTest.cpp @@ -0,0 +1,221 @@ +//===- unittest/Tooling/CommentHandlerTest.cpp -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TestVisitor.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { + +struct Comment { + Comment(const std::string &Message, unsigned Line, unsigned Col) + : Message(Message), Line(Line), Col(Col) { } + + std::string Message; + unsigned Line, Col; +}; + +class CommentVerifier; +typedef std::vector<Comment> CommentList; + +class CommentHandlerVisitor : public TestVisitor<CommentHandlerVisitor>, + public CommentHandler { + typedef TestVisitor<CommentHandlerVisitor> base; + +public: + CommentHandlerVisitor() : base(), PP(0), Verified(false) { } + + ~CommentHandlerVisitor() { + EXPECT_TRUE(Verified) << "CommentVerifier not accessed"; + } + + virtual bool HandleComment(Preprocessor &PP, SourceRange Loc) { + assert(&PP == this->PP && "Preprocessor changed!"); + + SourceLocation Start = Loc.getBegin(); + SourceManager &SM = PP.getSourceManager(); + std::string C(SM.getCharacterData(Start), + SM.getCharacterData(Loc.getEnd())); + + bool Invalid; + unsigned CLine = SM.getSpellingLineNumber(Start, &Invalid); + EXPECT_TRUE(!Invalid) << "Invalid line number on comment " << C; + + unsigned CCol = SM.getSpellingColumnNumber(Start, &Invalid); + EXPECT_TRUE(!Invalid) << "Invalid column number on comment " << C; + + Comments.push_back(Comment(C, CLine, CCol)); + return false; + } + + CommentVerifier GetVerifier(); + +protected: + virtual ASTFrontendAction* CreateTestAction() { + return new CommentHandlerAction(this); + } + +private: + Preprocessor *PP; + CommentList Comments; + bool Verified; + + class CommentHandlerAction : public base::TestAction { + public: + CommentHandlerAction(CommentHandlerVisitor *Visitor) + : TestAction(Visitor) { } + + virtual bool BeginSourceFileAction(CompilerInstance &CI, + StringRef FileName) { + CommentHandlerVisitor *V = + static_cast<CommentHandlerVisitor*>(this->Visitor); + V->PP = &CI.getPreprocessor(); + V->PP->addCommentHandler(V); + return true; + } + + virtual void EndSourceFileAction() { + CommentHandlerVisitor *V = + static_cast<CommentHandlerVisitor*>(this->Visitor); + V->PP->removeCommentHandler(V); + } + }; +}; + +class CommentVerifier { + CommentList::const_iterator Current; + CommentList::const_iterator End; + Preprocessor *PP; + +public: + CommentVerifier(const CommentList &Comments, Preprocessor *PP) + : Current(Comments.begin()), End(Comments.end()), PP(PP) + { } + + ~CommentVerifier() { + if (Current != End) { + EXPECT_TRUE(Current == End) << "Unexpected comment \"" + << Current->Message << "\" at line " << Current->Line << ", column " + << Current->Col; + } + } + + void Match(const char *Message, unsigned Line, unsigned Col) { + EXPECT_TRUE(Current != End) << "Comment " << Message << " not found"; + if (Current == End) return; + + const Comment &C = *Current; + EXPECT_TRUE(C.Message == Message && C.Line == Line && C.Col == Col) + << "Expected comment \"" << Message + << "\" at line " << Line << ", column " << Col + << "\nActual comment \"" << C.Message + << "\" at line " << C.Line << ", column " << C.Col; + + ++Current; + } +}; + +CommentVerifier CommentHandlerVisitor::GetVerifier() { + Verified = true; + return CommentVerifier(Comments, PP); +} + + +TEST(CommentHandlerTest, BasicTest1) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver("class X {}; int main() { return 0; }")); + CommentVerifier Verifier = Visitor.GetVerifier(); +} + +TEST(CommentHandlerTest, BasicTest2) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver( + "class X {}; int main() { /* comment */ return 0; }")); + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match("/* comment */", 1, 26); +} + +TEST(CommentHandlerTest, BasicTest3) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver( + "class X {}; // comment 1\n" + "int main() {\n" + " // comment 2\n" + " return 0;\n" + "}")); + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match("// comment 1", 1, 13); + Verifier.Match("// comment 2", 3, 3); +} + +TEST(CommentHandlerTest, IfBlock1) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver( + "#if 0\n" + "// ignored comment\n" + "#endif\n" + "// visible comment\n")); + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match("// visible comment", 4, 1); +} + +TEST(CommentHandlerTest, IfBlock2) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver( + "#define TEST // visible_1\n" + "#ifndef TEST // visible_2\n" + " // ignored_3\n" + "# ifdef UNDEFINED // ignored_4\n" + "# endif // ignored_5\n" + "#elif defined(TEST) // visible_6\n" + "# if 1 // visible_7\n" + " // visible_8\n" + "# else // visible_9\n" + " // ignored_10\n" + "# ifndef TEST // ignored_11\n" + "# endif // ignored_12\n" + "# endif // visible_13\n" + "#endif // visible_14\n")); + + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match("// visible_1", 1, 21); + Verifier.Match("// visible_2", 2, 21); + Verifier.Match("// visible_6", 6, 21); + Verifier.Match("// visible_7", 7, 21); + Verifier.Match("// visible_8", 8, 21); + Verifier.Match("// visible_9", 9, 21); + Verifier.Match("// visible_13", 13, 21); + Verifier.Match("// visible_14", 14, 21); +} + +TEST(CommentHandlerTest, IfBlock3) { + const char *Source = + "/* commented out ...\n" + "#if 0\n" + "// enclosed\n" + "#endif */"; + + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver(Source)); + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match(Source, 1, 1); +} + +TEST(CommentHandlerTest, PPDirectives) { + CommentHandlerVisitor Visitor; + EXPECT_TRUE(Visitor.runOver( + "#warning Y // ignored_1\n" // #warning takes whole line as message + "#undef MACRO // visible_2\n" + "#line 1 // visible_3\n")); + + CommentVerifier Verifier = Visitor.GetVerifier(); + Verifier.Match("// visible_2", 2, 14); + Verifier.Match("// visible_3", 3, 14); +} + +} // end namespace clang diff --git a/unittests/Tooling/CompilationDatabaseTest.cpp b/unittests/Tooling/CompilationDatabaseTest.cpp index 68d2896..591d48d 100644 --- a/unittests/Tooling/CompilationDatabaseTest.cpp +++ b/unittests/Tooling/CompilationDatabaseTest.cpp @@ -18,6 +18,55 @@ namespace clang { namespace tooling { +static void expectFailure(StringRef JSONDatabase, StringRef Explanation) { + std::string ErrorMessage; + EXPECT_EQ(NULL, JSONCompilationDatabase::loadFromBuffer(JSONDatabase, + ErrorMessage)) + << "Expected an error because of: " << Explanation; +} + +TEST(JSONCompilationDatabase, ErrsOnInvalidFormat) { + expectFailure("", "Empty database"); + expectFailure("{", "Invalid JSON"); + expectFailure("[[]]", "Array instead of object"); + expectFailure("[{\"a\":[]}]", "Array instead of value"); + expectFailure("[{\"a\":\"b\"}]", "Unknown key"); + expectFailure("[{[]:\"\"}]", "Incorrectly typed entry"); + expectFailure("[{}]", "Empty entry"); + expectFailure("[{\"directory\":\"\",\"command\":\"\"}]", "Missing file"); + expectFailure("[{\"directory\":\"\",\"file\":\"\"}]", "Missing command"); + expectFailure("[{\"command\":\"\",\"file\":\"\"}]", "Missing directory"); +} + +static std::vector<std::string> getAllFiles(StringRef JSONDatabase, + std::string &ErrorMessage) { + llvm::OwningPtr<CompilationDatabase> Database( + JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage)); + if (!Database) { + ADD_FAILURE() << ErrorMessage; + return std::vector<std::string>(); + } + return Database->getAllFiles(); +} + +TEST(JSONCompilationDatabase, GetAllFiles) { + std::string ErrorMessage; + EXPECT_EQ(std::vector<std::string>(), + getAllFiles("[]", ErrorMessage)) << ErrorMessage; + + std::vector<std::string> expected_files; + expected_files.push_back("file1"); + expected_files.push_back("file2"); + EXPECT_EQ(expected_files, getAllFiles( + "[{\"directory\":\"dir\"," + "\"command\":\"command\"," + "\"file\":\"file1\"}," + " {\"directory\":\"dir\"," + "\"command\":\"command\"," + "\"file\":\"file2\"}]", + ErrorMessage)) << ErrorMessage; +} + static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName, StringRef JSONDatabase, std::string &ErrorMessage) { @@ -235,6 +284,15 @@ TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) { EXPECT_EQ(ExpectedCommandLine, Result[0].CommandLine); } +TEST(FixedCompilationDatabase, GetAllFiles) { + std::vector<std::string> CommandLine; + CommandLine.push_back("one"); + CommandLine.push_back("two"); + FixedCompilationDatabase Database(".", CommandLine); + + EXPECT_EQ(0ul, Database.getAllFiles().size()); +} + TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) { int Argc = 0; llvm::OwningPtr<FixedCompilationDatabase> Database( diff --git a/unittests/Tooling/Makefile b/unittests/Tooling/Makefile index 0829da5..5d2224d 100644 --- a/unittests/Tooling/Makefile +++ b/unittests/Tooling/Makefile @@ -9,9 +9,10 @@ CLANG_LEVEL = ../.. TESTNAME = Tooling -LINK_COMPONENTS := support mc +include $(CLANG_LEVEL)/../../Makefile.config +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser support mc USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ - clangParse.a clangSema.a clangAnalysis.a clangEdit.a clangAST.a \ - clangLex.a clangBasic.a + clangParse.a clangRewrite.a clangSema.a clangAnalysis.a clangEdit.a \ + clangAST.a clangASTMatchers.a clangLex.a clangBasic.a include $(CLANG_LEVEL)/unittests/Makefile diff --git a/unittests/Tooling/RecursiveASTVisitorTest.cpp b/unittests/Tooling/RecursiveASTVisitorTest.cpp new file mode 100644 index 0000000..f3ba646 --- /dev/null +++ b/unittests/Tooling/RecursiveASTVisitorTest.cpp @@ -0,0 +1,388 @@ +//===- unittest/Tooling/RecursiveASTVisitorTest.cpp -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TestVisitor.h" + +namespace clang { + +class TypeLocVisitor : public ExpectedLocationVisitor<TypeLocVisitor> { +public: + bool VisitTypeLoc(TypeLoc TypeLocation) { + Match(TypeLocation.getType().getAsString(), TypeLocation.getBeginLoc()); + return true; + } +}; + +class DeclRefExprVisitor : public ExpectedLocationVisitor<DeclRefExprVisitor> { +public: + bool VisitDeclRefExpr(DeclRefExpr *Reference) { + Match(Reference->getNameInfo().getAsString(), Reference->getLocation()); + return true; + } +}; + +class VarDeclVisitor : public ExpectedLocationVisitor<VarDeclVisitor> { +public: + bool VisitVarDecl(VarDecl *Variable) { + Match(Variable->getNameAsString(), Variable->getLocStart()); + return true; + } +}; + +class CXXMemberCallVisitor + : public ExpectedLocationVisitor<CXXMemberCallVisitor> { +public: + bool VisitCXXMemberCallExpr(CXXMemberCallExpr *Call) { + Match(Call->getMethodDecl()->getQualifiedNameAsString(), + Call->getLocStart()); + return true; + } +}; + +class NamedDeclVisitor + : public ExpectedLocationVisitor<NamedDeclVisitor> { +public: + bool VisitNamedDecl(NamedDecl *Decl) { + std::string NameWithTemplateArgs; + Decl->getNameForDiagnostic(NameWithTemplateArgs, + Decl->getASTContext().getPrintingPolicy(), + true); + Match(NameWithTemplateArgs, Decl->getLocation()); + return true; + } +}; + +class CXXOperatorCallExprTraverser + : public ExpectedLocationVisitor<CXXOperatorCallExprTraverser> { +public: + // Use Traverse, not Visit, to check that data recursion optimization isn't + // bypassing the call of this function. + bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr *CE) { + Match(getOperatorSpelling(CE->getOperator()), CE->getExprLoc()); + return ExpectedLocationVisitor<CXXOperatorCallExprTraverser>:: + TraverseCXXOperatorCallExpr(CE); + } +}; + +class ParenExprVisitor : public ExpectedLocationVisitor<ParenExprVisitor> { +public: + bool VisitParenExpr(ParenExpr *Parens) { + Match("", Parens->getExprLoc()); + return true; + } +}; + +class TemplateArgumentLocTraverser + : public ExpectedLocationVisitor<TemplateArgumentLocTraverser> { +public: + bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &ArgLoc) { + std::string ArgStr; + llvm::raw_string_ostream Stream(ArgStr); + const TemplateArgument &Arg = ArgLoc.getArgument(); + + Arg.print(Context->getPrintingPolicy(), Stream); + Match(Stream.str(), ArgLoc.getLocation()); + return ExpectedLocationVisitor<TemplateArgumentLocTraverser>:: + TraverseTemplateArgumentLoc(ArgLoc); + } +}; + +class CXXBoolLiteralExprVisitor + : public ExpectedLocationVisitor<CXXBoolLiteralExprVisitor> { +public: + bool VisitCXXBoolLiteralExpr(CXXBoolLiteralExpr *BE) { + if (BE->getValue()) + Match("true", BE->getLocation()); + else + Match("false", BE->getLocation()); + return true; + } +}; + +TEST(RecursiveASTVisitor, VisitsBaseClassDeclarations) { + TypeLocVisitor Visitor; + Visitor.ExpectMatch("class X", 1, 30); + EXPECT_TRUE(Visitor.runOver("class X {}; class Y : public X {};")); +} + +TEST(RecursiveASTVisitor, VisitsCXXBaseSpecifiersOfForwardDeclaredClass) { + TypeLocVisitor Visitor; + Visitor.ExpectMatch("class X", 3, 18); + EXPECT_TRUE(Visitor.runOver( + "class Y;\n" + "class X {};\n" + "class Y : public X {};")); +} + +TEST(RecursiveASTVisitor, VisitsCXXBaseSpecifiersWithIncompleteInnerClass) { + TypeLocVisitor Visitor; + Visitor.ExpectMatch("class X", 2, 18); + EXPECT_TRUE(Visitor.runOver( + "class X {};\n" + "class Y : public X { class Z; };")); +} + +TEST(RecursiveASTVisitor, VisitsCXXBaseSpecifiersOfSelfReferentialType) { + TypeLocVisitor Visitor; + Visitor.ExpectMatch("X<class Y>", 2, 18); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> class X {};\n" + "class Y : public X<Y> {};")); +} + +TEST(RecursiveASTVisitor, VisitsBaseClassTemplateArguments) { + DeclRefExprVisitor Visitor; + Visitor.ExpectMatch("x", 2, 3); + EXPECT_TRUE(Visitor.runOver( + "void x(); template <void (*T)()> class X {};\nX<x> y;")); +} + +TEST(RecursiveASTVisitor, VisitsCXXForRangeStmtRange) { + DeclRefExprVisitor Visitor; + Visitor.ExpectMatch("x", 2, 25); + Visitor.ExpectMatch("x", 2, 30); + EXPECT_TRUE(Visitor.runOver( + "int x[5];\n" + "void f() { for (int i : x) { x[0] = 1; } }")); +} + +TEST(RecursiveASTVisitor, VisitsCXXForRangeStmtLoopVariable) { + VarDeclVisitor Visitor; + Visitor.ExpectMatch("i", 2, 17); + EXPECT_TRUE(Visitor.runOver( + "int x[5];\n" + "void f() { for (int i : x) {} }")); +} + +TEST(RecursiveASTVisitor, VisitsCallExpr) { + DeclRefExprVisitor Visitor; + Visitor.ExpectMatch("x", 1, 22); + EXPECT_TRUE(Visitor.runOver( + "void x(); void y() { x(); }")); +} + +TEST(RecursiveASTVisitor, VisitsCallInTemplateInstantiation) { + CXXMemberCallVisitor Visitor; + Visitor.ExpectMatch("Y::x", 3, 3); + EXPECT_TRUE(Visitor.runOver( + "struct Y { void x(); };\n" + "template<typename T> void y(T t) {\n" + " t.x();\n" + "}\n" + "void foo() { y<Y>(Y()); }")); +} + +TEST(RecursiveASTVisitor, VisitsCallInNestedFunctionTemplateInstantiation) { + CXXMemberCallVisitor Visitor; + Visitor.ExpectMatch("Y::x", 4, 5); + EXPECT_TRUE(Visitor.runOver( + "struct Y { void x(); };\n" + "template<typename T> struct Z {\n" + " template<typename U> static void f() {\n" + " T().x();\n" + " }\n" + "};\n" + "void foo() { Z<Y>::f<int>(); }")); +} + +TEST(RecursiveASTVisitor, VisitsCallInNestedClassTemplateInstantiation) { + CXXMemberCallVisitor Visitor; + Visitor.ExpectMatch("A::x", 5, 7); + EXPECT_TRUE(Visitor.runOver( + "template <typename T1> struct X {\n" + " template <typename T2> struct Y {\n" + " void f() {\n" + " T2 y;\n" + " y.x();\n" + " }\n" + " };\n" + "};\n" + "struct A { void x(); };\n" + "int main() {\n" + " (new X<A>::Y<A>())->f();\n" + "}")); +} + +/* FIXME: According to Richard Smith this is a bug in the AST. +TEST(RecursiveASTVisitor, VisitsBaseClassTemplateArgumentsInInstantiation) { + DeclRefExprVisitor Visitor; + Visitor.ExpectMatch("x", 3, 43); + EXPECT_TRUE(Visitor.runOver( + "template <typename T> void x();\n" + "template <void (*T)()> class X {};\n" + "template <typename T> class Y : public X< x<T> > {};\n" + "Y<int> y;")); +} +*/ + +TEST(RecursiveASTVisitor, VisitsCallInPartialTemplateSpecialization) { + CXXMemberCallVisitor Visitor; + Visitor.ExpectMatch("A::x", 6, 20); + EXPECT_TRUE(Visitor.runOver( + "template <typename T1> struct X {\n" + " template <typename T2, bool B> struct Y { void g(); };\n" + "};\n" + "template <typename T1> template <typename T2>\n" + "struct X<T1>::Y<T2, true> {\n" + " void f() { T2 y; y.x(); }\n" + "};\n" + "struct A { void x(); };\n" + "int main() {\n" + " (new X<A>::Y<A, true>())->f();\n" + "}\n")); +} + +TEST(RecursiveASTVisitor, VisitsExplicitTemplateSpecialization) { + CXXMemberCallVisitor Visitor; + Visitor.ExpectMatch("A::f", 4, 5); + EXPECT_TRUE(Visitor.runOver( + "struct A {\n" + " void f() const {}\n" + " template<class T> void g(const T& t) const {\n" + " t.f();\n" + " }\n" + "};\n" + "template void A::g(const A& a) const;\n")); +} + +TEST(RecursiveASTVisitor, VisitsPartialTemplateSpecialization) { + // From cfe-commits/Week-of-Mon-20100830/033998.html + // Contrary to the approach suggested in that email, we visit all + // specializations when we visit the primary template. Visiting them when we + // visit the associated specialization is problematic for specializations of + // template members of class templates. + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("A<bool>", 1, 26); + Visitor.ExpectMatch("A<char *>", 2, 26); + EXPECT_TRUE(Visitor.runOver( + "template <class T> class A {};\n" + "template <class T> class A<T*> {};\n" + "A<bool> ab;\n" + "A<char*> acp;\n")); +} + +TEST(RecursiveASTVisitor, VisitsUndefinedClassTemplateSpecialization) { + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("A<int>", 1, 29); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> struct A;\n" + "A<int> *p;\n")); +} + +TEST(RecursiveASTVisitor, VisitsNestedUndefinedClassTemplateSpecialization) { + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("A<int>::B<char>", 2, 31); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> struct A {\n" + " template<typename U> struct B;\n" + "};\n" + "A<int>::B<char> *p;\n")); +} + +TEST(RecursiveASTVisitor, VisitsUndefinedFunctionTemplateSpecialization) { + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("A<int>", 1, 26); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> int A();\n" + "int k = A<int>();\n")); +} + +TEST(RecursiveASTVisitor, VisitsNestedUndefinedFunctionTemplateSpecialization) { + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("A<int>::B<char>", 2, 35); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> struct A {\n" + " template<typename U> static int B();\n" + "};\n" + "int k = A<int>::B<char>();\n")); +} + +TEST(RecursiveASTVisitor, NoRecursionInSelfFriend) { + // From cfe-commits/Week-of-Mon-20100830/033977.html + NamedDeclVisitor Visitor; + Visitor.ExpectMatch("vector_iterator<int>", 2, 7); + EXPECT_TRUE(Visitor.runOver( + "template<typename Container>\n" + "class vector_iterator {\n" + " template <typename C> friend class vector_iterator;\n" + "};\n" + "vector_iterator<int> it_int;\n")); +} + +TEST(RecursiveASTVisitor, TraversesOverloadedOperator) { + CXXOperatorCallExprTraverser Visitor; + Visitor.ExpectMatch("()", 4, 9); + EXPECT_TRUE(Visitor.runOver( + "struct A {\n" + " int operator()();\n" + "} a;\n" + "int k = a();\n")); +} + +TEST(RecursiveASTVisitor, VisitsParensDuringDataRecursion) { + ParenExprVisitor Visitor; + Visitor.ExpectMatch("", 1, 9); + EXPECT_TRUE(Visitor.runOver("int k = (4) + 9;\n")); +} + +TEST(RecursiveASTVisitor, VisitsClassTemplateNonTypeParmDefaultArgument) { + CXXBoolLiteralExprVisitor Visitor; + Visitor.ExpectMatch("true", 2, 19); + EXPECT_TRUE(Visitor.runOver( + "template<bool B> class X;\n" + "template<bool B = true> class Y;\n" + "template<bool B> class Y {};\n")); +} + +TEST(RecursiveASTVisitor, VisitsClassTemplateTypeParmDefaultArgument) { + TypeLocVisitor Visitor; + Visitor.ExpectMatch("class X", 2, 23); + EXPECT_TRUE(Visitor.runOver( + "class X;\n" + "template<typename T = X> class Y;\n" + "template<typename T> class Y {};\n")); +} + +TEST(RecursiveASTVisitor, VisitsClassTemplateTemplateParmDefaultArgument) { + TemplateArgumentLocTraverser Visitor; + Visitor.ExpectMatch("X", 2, 40); + EXPECT_TRUE(Visitor.runOver( + "template<typename T> class X;\n" + "template<template <typename> class T = X> class Y;\n" + "template<template <typename> class T> class Y {};\n")); +} + +// A visitor that visits implicit declarations and matches constructors. +class ImplicitCtorVisitor + : public ExpectedLocationVisitor<ImplicitCtorVisitor> { +public: + bool shouldVisitImplicitCode() const { return true; } + + bool VisitCXXConstructorDecl(CXXConstructorDecl* Ctor) { + if (Ctor->isImplicit()) { // Was not written in source code + if (const CXXRecordDecl* Class = Ctor->getParent()) { + Match(Class->getName(), Ctor->getLocation()); + } + } + return true; + } +}; + +TEST(RecursiveASTVisitor, VisitsImplicitCopyConstructors) { + ImplicitCtorVisitor Visitor; + Visitor.ExpectMatch("Simple", 2, 8); + // Note: Clang lazily instantiates implicit declarations, so we need + // to use them in order to force them to appear in the AST. + EXPECT_TRUE(Visitor.runOver( + "struct WithCtor { WithCtor(); }; \n" + "struct Simple { Simple(); WithCtor w; }; \n" + "int main() { Simple s; Simple t(s); }\n")); +} + +} // end namespace clang diff --git a/unittests/Tooling/RefactoringCallbacksTest.cpp b/unittests/Tooling/RefactoringCallbacksTest.cpp new file mode 100644 index 0000000..00eb193 --- /dev/null +++ b/unittests/Tooling/RefactoringCallbacksTest.cpp @@ -0,0 +1,100 @@ +//===- unittest/ASTMatchers/RefactoringCallbacksTest.cpp ------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/RefactoringCallbacks.h" +#include "gtest/gtest.h" +#include "RewriterTestContext.h" + +namespace clang { +namespace tooling { + +using namespace ast_matchers; + +template <typename T> +void expectRewritten(const std::string &Code, + const std::string &Expected, + const T &AMatcher, + RefactoringCallback &Callback) { + MatchFinder Finder; + Finder.addMatcher(AMatcher, &Callback); + OwningPtr<tooling::FrontendActionFactory> Factory( + tooling::newFrontendActionFactory(&Finder)); + ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), Code)) + << "Parsing error in \"" << Code << "\""; + RewriterTestContext Context; + FileID ID = Context.createInMemoryFile("input.cc", Code); + EXPECT_TRUE(tooling::applyAllReplacements(Callback.getReplacements(), + Context.Rewrite)); + EXPECT_EQ(Expected, Context.getRewrittenText(ID)); +} + +TEST(RefactoringCallbacksTest, ReplacesStmtsWithString) { + std::string Code = "void f() { int i = 1; }"; + std::string Expected = "void f() { ; }"; + ReplaceStmtWithText Callback("id", ";"); + expectRewritten(Code, Expected, id("id", declarationStatement()), Callback); +} + +TEST(RefactoringCallbacksTest, ReplacesStmtsInCalledMacros) { + std::string Code = "#define A void f() { int i = 1; }\nA"; + std::string Expected = "#define A void f() { ; }\nA"; + ReplaceStmtWithText Callback("id", ";"); + expectRewritten(Code, Expected, id("id", declarationStatement()), Callback); +} + +TEST(RefactoringCallbacksTest, IgnoresStmtsInUncalledMacros) { + std::string Code = "#define A void f() { int i = 1; }"; + std::string Expected = "#define A void f() { int i = 1; }"; + ReplaceStmtWithText Callback("id", ";"); + expectRewritten(Code, Expected, id("id", declarationStatement()), Callback); +} + +TEST(RefactoringCallbacksTest, ReplacesInteger) { + std::string Code = "void f() { int i = 1; }"; + std::string Expected = "void f() { int i = 2; }"; + ReplaceStmtWithText Callback("id", "2"); + expectRewritten(Code, Expected, id("id", expression(integerLiteral())), + Callback); +} + +TEST(RefactoringCallbacksTest, ReplacesStmtWithStmt) { + std::string Code = "void f() { int i = false ? 1 : i * 2; }"; + std::string Expected = "void f() { int i = i * 2; }"; + ReplaceStmtWithStmt Callback("always-false", "should-be"); + expectRewritten(Code, Expected, + id("always-false", conditionalOperator( + hasCondition(boolLiteral(equals(false))), + hasFalseExpression(id("should-be", expression())))), + Callback); +} + +TEST(RefactoringCallbacksTest, ReplacesIfStmt) { + std::string Code = "bool a; void f() { if (a) f(); else a = true; }"; + std::string Expected = "bool a; void f() { f(); }"; + ReplaceIfStmtWithItsBody Callback("id", true); + expectRewritten(Code, Expected, + id("id", ifStmt( + hasCondition(implicitCast(hasSourceExpression( + declarationReference(to(variable(hasName("a"))))))))), + Callback); +} + +TEST(RefactoringCallbacksTest, RemovesEntireIfOnEmptyElse) { + std::string Code = "void f() { if (false) int i = 0; }"; + std::string Expected = "void f() { }"; + ReplaceIfStmtWithItsBody Callback("id", false); + expectRewritten(Code, Expected, + id("id", ifStmt(hasCondition(boolLiteral(equals(false))))), + Callback); +} + +} // end namespace ast_matchers +} // end namespace clang diff --git a/unittests/Tooling/RefactoringTest.cpp b/unittests/Tooling/RefactoringTest.cpp new file mode 100644 index 0000000..8d96955 --- /dev/null +++ b/unittests/Tooling/RefactoringTest.cpp @@ -0,0 +1,305 @@ +//===- unittest/Tooling/RefactoringTest.cpp - Refactoring unit tests ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RewriterTestContext.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclGroup.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/DiagnosticOptions.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Rewriter.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Path.h" +#include "gtest/gtest.h" + +namespace clang { +namespace tooling { + +class ReplacementTest : public ::testing::Test { + protected: + Replacement createReplacement(SourceLocation Start, unsigned Length, + llvm::StringRef ReplacementText) { + return Replacement(Context.Sources, Start, Length, ReplacementText); + } + + RewriterTestContext Context; +}; + +TEST_F(ReplacementTest, CanDeleteAllText) { + FileID ID = Context.createInMemoryFile("input.cpp", "text"); + SourceLocation Location = Context.getLocation(ID, 1, 1); + Replacement Replace(createReplacement(Location, 4, "")); + EXPECT_TRUE(Replace.apply(Context.Rewrite)); + EXPECT_EQ("", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, CanDeleteAllTextInTextWithNewlines) { + FileID ID = Context.createInMemoryFile("input.cpp", "line1\nline2\nline3"); + SourceLocation Location = Context.getLocation(ID, 1, 1); + Replacement Replace(createReplacement(Location, 17, "")); + EXPECT_TRUE(Replace.apply(Context.Rewrite)); + EXPECT_EQ("", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, CanAddText) { + FileID ID = Context.createInMemoryFile("input.cpp", ""); + SourceLocation Location = Context.getLocation(ID, 1, 1); + Replacement Replace(createReplacement(Location, 0, "result")); + EXPECT_TRUE(Replace.apply(Context.Rewrite)); + EXPECT_EQ("result", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, CanReplaceTextAtPosition) { + FileID ID = Context.createInMemoryFile("input.cpp", + "line1\nline2\nline3\nline4"); + SourceLocation Location = Context.getLocation(ID, 2, 3); + Replacement Replace(createReplacement(Location, 12, "x")); + EXPECT_TRUE(Replace.apply(Context.Rewrite)); + EXPECT_EQ("line1\nlixne4", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, CanReplaceTextAtPositionMultipleTimes) { + FileID ID = Context.createInMemoryFile("input.cpp", + "line1\nline2\nline3\nline4"); + SourceLocation Location1 = Context.getLocation(ID, 2, 3); + Replacement Replace1(createReplacement(Location1, 12, "x\ny\n")); + EXPECT_TRUE(Replace1.apply(Context.Rewrite)); + EXPECT_EQ("line1\nlix\ny\nne4", Context.getRewrittenText(ID)); + + // Since the original source has not been modified, the (4, 4) points to the + // 'e' in the original content. + SourceLocation Location2 = Context.getLocation(ID, 4, 4); + Replacement Replace2(createReplacement(Location2, 1, "f")); + EXPECT_TRUE(Replace2.apply(Context.Rewrite)); + EXPECT_EQ("line1\nlix\ny\nnf4", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, ApplyFailsForNonExistentLocation) { + Replacement Replace("nonexistent-file.cpp", 0, 1, ""); + EXPECT_FALSE(Replace.apply(Context.Rewrite)); +} + +TEST_F(ReplacementTest, CanRetrivePath) { + Replacement Replace("/path/to/file.cpp", 0, 1, ""); + EXPECT_EQ("/path/to/file.cpp", Replace.getFilePath()); +} + +TEST_F(ReplacementTest, ReturnsInvalidPath) { + Replacement Replace1(Context.Sources, SourceLocation(), 0, ""); + EXPECT_TRUE(Replace1.getFilePath().empty()); + + Replacement Replace2; + EXPECT_TRUE(Replace2.getFilePath().empty()); +} + +TEST_F(ReplacementTest, CanApplyReplacements) { + FileID ID = Context.createInMemoryFile("input.cpp", + "line1\nline2\nline3\nline4"); + Replacements Replaces; + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), + 5, "replaced")); + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 3, 1), + 5, "other")); + EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite)); + EXPECT_EQ("line1\nreplaced\nother\nline4", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, SkipsDuplicateReplacements) { + FileID ID = Context.createInMemoryFile("input.cpp", + "line1\nline2\nline3\nline4"); + Replacements Replaces; + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), + 5, "replaced")); + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), + 5, "replaced")); + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), + 5, "replaced")); + EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite)); + EXPECT_EQ("line1\nreplaced\nline3\nline4", Context.getRewrittenText(ID)); +} + +TEST_F(ReplacementTest, ApplyAllFailsIfOneApplyFails) { + // This test depends on the value of the file name of an invalid source + // location being in the range ]a, z[. + FileID IDa = Context.createInMemoryFile("a.cpp", "text"); + FileID IDz = Context.createInMemoryFile("z.cpp", "text"); + Replacements Replaces; + Replaces.insert(Replacement(Context.Sources, Context.getLocation(IDa, 1, 1), + 4, "a")); + Replaces.insert(Replacement(Context.Sources, SourceLocation(), + 5, "2")); + Replaces.insert(Replacement(Context.Sources, Context.getLocation(IDz, 1, 1), + 4, "z")); + EXPECT_FALSE(applyAllReplacements(Replaces, Context.Rewrite)); + EXPECT_EQ("a", Context.getRewrittenText(IDa)); + EXPECT_EQ("z", Context.getRewrittenText(IDz)); +} + +class FlushRewrittenFilesTest : public ::testing::Test { + public: + FlushRewrittenFilesTest() { + std::string ErrorInfo; + TemporaryDirectory = llvm::sys::Path::GetTemporaryDirectory(&ErrorInfo); + assert(ErrorInfo.empty()); + } + + ~FlushRewrittenFilesTest() { + std::string ErrorInfo; + TemporaryDirectory.eraseFromDisk(true, &ErrorInfo); + assert(ErrorInfo.empty()); + } + + FileID createFile(llvm::StringRef Name, llvm::StringRef Content) { + llvm::SmallString<1024> Path(TemporaryDirectory.str()); + llvm::sys::path::append(Path, Name); + std::string ErrorInfo; + llvm::raw_fd_ostream OutStream(Path.c_str(), + ErrorInfo, llvm::raw_fd_ostream::F_Binary); + assert(ErrorInfo.empty()); + OutStream << Content; + OutStream.close(); + const FileEntry *File = Context.Files.getFile(Path); + assert(File != NULL); + return Context.Sources.createFileID(File, SourceLocation(), SrcMgr::C_User); + } + + std::string getFileContentFromDisk(llvm::StringRef Name) { + llvm::SmallString<1024> Path(TemporaryDirectory.str()); + llvm::sys::path::append(Path, Name); + // We need to read directly from the FileManager without relaying through + // a FileEntry, as otherwise we'd read through an already opened file + // descriptor, which might not see the changes made. + // FIXME: Figure out whether there is a way to get the SourceManger to + // reopen the file. + return Context.Files.getBufferForFile(Path, NULL)->getBuffer(); + } + + llvm::sys::Path TemporaryDirectory; + RewriterTestContext Context; +}; + +TEST_F(FlushRewrittenFilesTest, StoresChangesOnDisk) { + FileID ID = createFile("input.cpp", "line1\nline2\nline3\nline4"); + Replacements Replaces; + Replaces.insert(Replacement(Context.Sources, Context.getLocation(ID, 2, 1), + 5, "replaced")); + EXPECT_TRUE(applyAllReplacements(Replaces, Context.Rewrite)); + EXPECT_FALSE(Context.Rewrite.overwriteChangedFiles()); + EXPECT_EQ("line1\nreplaced\nline3\nline4", + getFileContentFromDisk("input.cpp")); +} + +namespace { +template <typename T> +class TestVisitor : public clang::RecursiveASTVisitor<T> { +public: + bool runOver(StringRef Code) { + return runToolOnCode(new TestAction(this), Code); + } + +protected: + clang::SourceManager *SM; + +private: + class FindConsumer : public clang::ASTConsumer { + public: + FindConsumer(TestVisitor *Visitor) : Visitor(Visitor) {} + + virtual void HandleTranslationUnit(clang::ASTContext &Context) { + Visitor->TraverseDecl(Context.getTranslationUnitDecl()); + } + + private: + TestVisitor *Visitor; + }; + + class TestAction : public clang::ASTFrontendAction { + public: + TestAction(TestVisitor *Visitor) : Visitor(Visitor) {} + + virtual clang::ASTConsumer* CreateASTConsumer( + clang::CompilerInstance& compiler, llvm::StringRef dummy) { + Visitor->SM = &compiler.getSourceManager(); + /// TestConsumer will be deleted by the framework calling us. + return new FindConsumer(Visitor); + } + + private: + TestVisitor *Visitor; + }; +}; +} // end namespace + +void expectReplacementAt(const Replacement &Replace, + StringRef File, unsigned Offset, unsigned Length) { + ASSERT_TRUE(Replace.isApplicable()); + EXPECT_EQ(File, Replace.getFilePath()); + EXPECT_EQ(Offset, Replace.getOffset()); + EXPECT_EQ(Length, Replace.getLength()); +} + +class ClassDeclXVisitor : public TestVisitor<ClassDeclXVisitor> { +public: + bool VisitCXXRecordDecl(CXXRecordDecl *Record) { + if (Record->getName() == "X") { + Replace = Replacement(*SM, Record, ""); + } + return true; + } + Replacement Replace; +}; + +TEST(Replacement, CanBeConstructedFromNode) { + ClassDeclXVisitor ClassDeclX; + EXPECT_TRUE(ClassDeclX.runOver(" class X;")); + expectReplacementAt(ClassDeclX.Replace, "input.cc", 5, 7); +} + +TEST(Replacement, ReplacesAtSpellingLocation) { + ClassDeclXVisitor ClassDeclX; + EXPECT_TRUE(ClassDeclX.runOver("#define A(Y) Y\nA(class X);")); + expectReplacementAt(ClassDeclX.Replace, "input.cc", 17, 7); +} + +class CallToFVisitor : public TestVisitor<CallToFVisitor> { +public: + bool VisitCallExpr(CallExpr *Call) { + if (Call->getDirectCallee()->getName() == "F") { + Replace = Replacement(*SM, Call, ""); + } + return true; + } + Replacement Replace; +}; + +TEST(Replacement, FunctionCall) { + CallToFVisitor CallToF; + EXPECT_TRUE(CallToF.runOver("void F(); void G() { F(); }")); + expectReplacementAt(CallToF.Replace, "input.cc", 21, 3); +} + +TEST(Replacement, TemplatedFunctionCall) { + CallToFVisitor CallToF; + EXPECT_TRUE(CallToF.runOver( + "template <typename T> void F(); void G() { F<int>(); }")); + expectReplacementAt(CallToF.Replace, "input.cc", 43, 8); +} + +} // end namespace tooling +} // end namespace clang diff --git a/unittests/Tooling/RewriterTest.cpp b/unittests/Tooling/RewriterTest.cpp new file mode 100644 index 0000000..c53e50a --- /dev/null +++ b/unittests/Tooling/RewriterTest.cpp @@ -0,0 +1,37 @@ +//===- unittest/Tooling/RewriterTest.cpp ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RewriterTestContext.h" +#include "gtest/gtest.h" + +namespace clang { + +TEST(Rewriter, OverwritesChangedFiles) { + RewriterTestContext Context; + FileID ID = Context.createOnDiskFile("t.cpp", "line1\nline2\nline3\nline4"); + Context.Rewrite.ReplaceText(Context.getLocation(ID, 2, 1), 5, "replaced"); + EXPECT_FALSE(Context.Rewrite.overwriteChangedFiles()); + EXPECT_EQ("line1\nreplaced\nline3\nline4", + Context.getFileContentFromDisk("t.cpp")); +} + +TEST(Rewriter, ContinuesOverwritingFilesOnError) { + RewriterTestContext Context; + FileID FailingID = Context.createInMemoryFile("invalid/failing.cpp", "test"); + Context.Rewrite.ReplaceText(Context.getLocation(FailingID, 1, 2), 1, "other"); + FileID WorkingID = Context.createOnDiskFile( + "working.cpp", "line1\nline2\nline3\nline4"); + Context.Rewrite.ReplaceText(Context.getLocation(WorkingID, 2, 1), 5, + "replaced"); + EXPECT_TRUE(Context.Rewrite.overwriteChangedFiles()); + EXPECT_EQ("line1\nreplaced\nline3\nline4", + Context.getFileContentFromDisk("working.cpp")); +} + +} // end namespace clang diff --git a/unittests/Tooling/RewriterTestContext.h b/unittests/Tooling/RewriterTestContext.h new file mode 100644 index 0000000..f68be6b --- /dev/null +++ b/unittests/Tooling/RewriterTestContext.h @@ -0,0 +1,125 @@ +//===--- RewriterTestContext.h ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a utility class for Rewriter related tests. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_REWRITER_TEST_CONTEXT_H +#define LLVM_CLANG_REWRITER_TEST_CONTEXT_H + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/DiagnosticOptions.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Rewriter.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { + +/// \brief A class that sets up a ready to use Rewriter. +/// +/// Useful in unit tests that need a Rewriter. Creates all dependencies +/// of a Rewriter with default values for testing and provides convenience +/// methods, which help with writing tests that change files. +class RewriterTestContext { + public: + RewriterTestContext() + : Diagnostics(llvm::IntrusiveRefCntPtr<DiagnosticIDs>()), + DiagnosticPrinter(llvm::outs(), DiagnosticOptions()), + Files((FileSystemOptions())), + Sources(Diagnostics, Files), + Rewrite(Sources, Options) { + Diagnostics.setClient(&DiagnosticPrinter, false); + } + + ~RewriterTestContext() { + if (!TemporaryDirectory.empty()) { + uint32_t RemovedCount = 0; + llvm::sys::fs::remove_all(TemporaryDirectory.str(), RemovedCount); + } + } + + FileID createInMemoryFile(StringRef Name, StringRef Content) { + const llvm::MemoryBuffer *Source = + llvm::MemoryBuffer::getMemBuffer(Content); + const FileEntry *Entry = + Files.getVirtualFile(Name, Source->getBufferSize(), 0); + Sources.overrideFileContents(Entry, Source, true); + assert(Entry != NULL); + return Sources.createFileID(Entry, SourceLocation(), SrcMgr::C_User); + } + + FileID createOnDiskFile(StringRef Name, StringRef Content) { + if (TemporaryDirectory.empty()) { + int FD; + bool error = + llvm::sys::fs::unique_file("rewriter-test-%%-%%-%%-%%/anchor", FD, + TemporaryDirectory); + assert(!error); (void)error; + llvm::raw_fd_ostream Closer(FD, /*shouldClose=*/true); + TemporaryDirectory = llvm::sys::path::parent_path(TemporaryDirectory); + } + llvm::SmallString<1024> Path(TemporaryDirectory); + llvm::sys::path::append(Path, Name); + std::string ErrorInfo; + llvm::raw_fd_ostream OutStream(Path.c_str(), + ErrorInfo, llvm::raw_fd_ostream::F_Binary); + assert(ErrorInfo.empty()); + OutStream << Content; + OutStream.close(); + const FileEntry *File = Files.getFile(Path); + assert(File != NULL); + return Sources.createFileID(File, SourceLocation(), SrcMgr::C_User); + } + + SourceLocation getLocation(FileID ID, unsigned Line, unsigned Column) { + SourceLocation Result = Sources.translateFileLineCol( + Sources.getFileEntryForID(ID), Line, Column); + assert(Result.isValid()); + return Result; + } + + std::string getRewrittenText(FileID ID) { + std::string Result; + llvm::raw_string_ostream OS(Result); + Rewrite.getEditBuffer(ID).write(OS); + OS.flush(); + return Result; + } + + std::string getFileContentFromDisk(StringRef Name) { + llvm::SmallString<1024> Path(TemporaryDirectory.str()); + llvm::sys::path::append(Path, Name); + // We need to read directly from the FileManager without relaying through + // a FileEntry, as otherwise we'd read through an already opened file + // descriptor, which might not see the changes made. + // FIXME: Figure out whether there is a way to get the SourceManger to + // reopen the file. + return Files.getBufferForFile(Path, NULL)->getBuffer(); + } + + DiagnosticsEngine Diagnostics; + TextDiagnosticPrinter DiagnosticPrinter; + FileManager Files; + SourceManager Sources; + LangOptions Options; + Rewriter Rewrite; + + // Will be set once on disk files are generated. + SmallString<128> TemporaryDirectory; +}; + +} // end namespace clang + +#endif diff --git a/unittests/Tooling/TestVisitor.h b/unittests/Tooling/TestVisitor.h new file mode 100644 index 0000000..d439d81 --- /dev/null +++ b/unittests/Tooling/TestVisitor.h @@ -0,0 +1,144 @@ +//===--- TestVisitor.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a utility class for RecursiveASTVisitor related tests. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TEST_VISITOR_H +#define LLVM_CLANG_TEST_VISITOR_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { + +/// \brief Base class for simple RecursiveASTVisitor based tests. +/// +/// This is a drop-in replacement for RecursiveASTVisitor itself, with the +/// additional capability of running it over a snippet of code. +/// +/// Visits template instantiations by default. +template <typename T> +class TestVisitor : public RecursiveASTVisitor<T> { +public: + TestVisitor() { } + + virtual ~TestVisitor() { } + + /// \brief Runs the current AST visitor over the given code. + bool runOver(StringRef Code) { + return tooling::runToolOnCode(CreateTestAction(), Code); + } + + bool shouldVisitTemplateInstantiations() const { + return true; + } + +protected: + virtual ASTFrontendAction* CreateTestAction() { + return new TestAction(this); + } + + class FindConsumer : public ASTConsumer { + public: + FindConsumer(TestVisitor *Visitor) : Visitor(Visitor) {} + + virtual void HandleTranslationUnit(clang::ASTContext &Context) { + Visitor->Context = &Context; + Visitor->TraverseDecl(Context.getTranslationUnitDecl()); + } + + private: + TestVisitor *Visitor; + }; + + class TestAction : public ASTFrontendAction { + public: + TestAction(TestVisitor *Visitor) : Visitor(Visitor) {} + + virtual clang::ASTConsumer* CreateASTConsumer( + CompilerInstance&, llvm::StringRef dummy) { + /// TestConsumer will be deleted by the framework calling us. + return new FindConsumer(Visitor); + } + + protected: + TestVisitor *Visitor; + }; + + ASTContext *Context; +}; + + +/// \brief A RecursiveASTVisitor for testing the RecursiveASTVisitor itself. +/// +/// Allows simple creation of test visitors running matches on only a small +/// subset of the Visit* methods. +template <typename T, template <typename> class Visitor = TestVisitor> +class ExpectedLocationVisitor : public Visitor<T> { +public: + ExpectedLocationVisitor() + : ExpectedLine(0), ExpectedColumn(0), Found(false) {} + + virtual ~ExpectedLocationVisitor() { + EXPECT_TRUE(Found) + << "Expected \"" << ExpectedMatch << "\" at " << ExpectedLine + << ":" << ExpectedColumn << PartialMatches; + } + + /// \brief Expect 'Match' to occur at the given 'Line' and 'Column'. + void ExpectMatch(Twine Match, unsigned Line, unsigned Column) { + ExpectedMatch = Match.str(); + ExpectedLine = Line; + ExpectedColumn = Column; + } + +protected: + /// \brief Convenience method to simplify writing test visitors. + /// + /// Sets 'Found' to true if 'Name' and 'Location' match the expected + /// values. If only a partial match is found, record the information + /// to produce nice error output when a test fails. + /// + /// Implementations are required to call this with appropriate values + /// for 'Name' during visitation. + void Match(StringRef Name, SourceLocation Location) { + FullSourceLoc FullLocation = this->Context->getFullLoc(Location); + if (Name == ExpectedMatch && + FullLocation.isValid() && + FullLocation.getSpellingLineNumber() == ExpectedLine && + FullLocation.getSpellingColumnNumber() == ExpectedColumn) { + EXPECT_TRUE(!Found); + Found = true; + } else if (Name == ExpectedMatch || + (FullLocation.isValid() && + FullLocation.getSpellingLineNumber() == ExpectedLine && + FullLocation.getSpellingColumnNumber() == ExpectedColumn)) { + // If we did not match, record information about partial matches. + llvm::raw_string_ostream Stream(PartialMatches); + Stream << ", partial match: \"" << Name << "\" at "; + Location.print(Stream, this->Context->getSourceManager()); + } + } + + std::string ExpectedMatch; + unsigned ExpectedLine; + unsigned ExpectedColumn; + std::string PartialMatches; + bool Found; +}; +} + +#endif /* LLVM_CLANG_TEST_VISITOR_H */ diff --git a/unittests/Tooling/ToolingTest.cpp b/unittests/Tooling/ToolingTest.cpp index c7b2210..fb3af26 100644 --- a/unittests/Tooling/ToolingTest.cpp +++ b/unittests/Tooling/ToolingTest.cpp @@ -15,6 +15,7 @@ #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Tooling.h" #include "gtest/gtest.h" +#include <string> namespace clang { namespace tooling { @@ -52,11 +53,16 @@ class FindTopLevelDeclConsumer : public clang::ASTConsumer { }; } // end namespace -TEST(runToolOnCode, FindsTopLevelDeclOnEmptyCode) { +TEST(runToolOnCode, FindsNoTopLevelDeclOnEmptyCode) { bool FoundTopLevelDecl = false; EXPECT_TRUE(runToolOnCode( new TestAction(new FindTopLevelDeclConsumer(&FoundTopLevelDecl)), "")); +#if !defined(_MSC_VER) + EXPECT_FALSE(FoundTopLevelDecl); +#else + // FIXME: LangOpts.MicrosoftExt appends "class type_info;" EXPECT_TRUE(FoundTopLevelDecl); +#endif } namespace { @@ -98,7 +104,9 @@ TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromType) { } struct IndependentFrontendActionCreator { - FrontendAction *newFrontendAction() { return new SyntaxOnlyAction; } + ASTConsumer *newASTConsumer() { + return new FindTopLevelDeclConsumer(NULL); + } }; TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) { @@ -109,5 +117,18 @@ TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) { EXPECT_TRUE(Action.get() != NULL); } +TEST(ToolInvocation, TestMapVirtualFile) { + clang::FileManager Files((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); + Invocation.mapVirtualFile("test.cpp", "#include <abc>\n"); + Invocation.mapVirtualFile("def/abc", "\n"); + EXPECT_TRUE(Invocation.run()); +} + } // end namespace tooling } // end namespace clang |