diff options
Diffstat (limited to 'unittests/Tooling/StencilTest.cpp')
-rw-r--r-- | unittests/Tooling/StencilTest.cpp | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/unittests/Tooling/StencilTest.cpp b/unittests/Tooling/StencilTest.cpp new file mode 100644 index 0000000000..ffdca0562e --- /dev/null +++ b/unittests/Tooling/StencilTest.cpp @@ -0,0 +1,223 @@ +//===- unittest/Tooling/StencilTest.cpp -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Stencil.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/FixIt.h" +#include "clang/Tooling/Tooling.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace tooling; +using namespace ast_matchers; + +namespace { +using ::testing::AllOf; +using ::testing::Eq; +using ::testing::HasSubstr; +using MatchResult = MatchFinder::MatchResult; +using tooling::stencil::node; +using tooling::stencil::sNode; +using tooling::stencil::text; + +// In tests, we can't directly match on llvm::Expected since its accessors +// mutate the object. So, we collapse it to an Optional. +static llvm::Optional<std::string> toOptional(llvm::Expected<std::string> V) { + if (V) + return *V; + ADD_FAILURE() << "Losing error in conversion to IsSomething: " + << llvm::toString(V.takeError()); + return llvm::None; +} + +// A very simple matcher for llvm::Optional values. +MATCHER_P(IsSomething, ValueMatcher, "") { + if (!arg) + return false; + return ::testing::ExplainMatchResult(ValueMatcher, *arg, result_listener); +} + +// Create a valid translation-unit from a statement. +static std::string wrapSnippet(llvm::Twine StatementCode) { + return ("auto stencil_test_snippet = []{" + StatementCode + "};").str(); +} + +static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) { + return varDecl(hasName("stencil_test_snippet"), + hasDescendant(compoundStmt(hasAnySubstatement(Matcher)))); +} + +struct TestMatch { + // The AST unit from which `result` is built. We bundle it because it backs + // the result. Users are not expected to access it. + std::unique_ptr<ASTUnit> AstUnit; + // The result to use in the test. References `ast_unit`. + MatchResult Result; +}; + +// Matches `Matcher` against the statement `StatementCode` and returns the +// result. Handles putting the statement inside a function and modifying the +// matcher correspondingly. `Matcher` should match `StatementCode` exactly -- +// that is, produce exactly one match. +static llvm::Optional<TestMatch> matchStmt(llvm::Twine StatementCode, + StatementMatcher Matcher) { + auto AstUnit = buildASTFromCode(wrapSnippet(StatementCode)); + if (AstUnit == nullptr) { + ADD_FAILURE() << "AST construction failed"; + return llvm::None; + } + ASTContext &Context = AstUnit->getASTContext(); + auto Matches = ast_matchers::match(wrapMatcher(Matcher), Context); + // We expect a single, exact match for the statement. + if (Matches.size() != 1) { + ADD_FAILURE() << "Wrong number of matches: " << Matches.size(); + return llvm::None; + } + return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)}; +} + +class StencilTest : public ::testing::Test { +protected: + // Verifies that the given stencil fails when evaluated on a valid match + // result. Binds a statement to "stmt", a (non-member) ctor-initializer to + // "init", an expression to "expr" and a (nameless) declaration to "decl". + void testError(const Stencil &Stencil, + ::testing::Matcher<std::string> Matcher) { + const std::string Snippet = R"cc( + struct A {}; + class F : public A { + public: + F(int) {} + }; + F(1); + )cc"; + auto StmtMatch = matchStmt( + Snippet, + stmt(hasDescendant( + cxxConstructExpr( + hasDeclaration(decl(hasDescendant(cxxCtorInitializer( + isBaseInitializer()) + .bind("init"))) + .bind("decl"))) + .bind("expr"))) + .bind("stmt")); + ASSERT_TRUE(StmtMatch); + if (auto ResultOrErr = Stencil.eval(StmtMatch->Result)) { + ADD_FAILURE() << "Expected failure but succeeded: " << *ResultOrErr; + } else { + auto Err = llvm::handleErrors(ResultOrErr.takeError(), + [&Matcher](const llvm::StringError &Err) { + EXPECT_THAT(Err.getMessage(), Matcher); + }); + if (Err) { + ADD_FAILURE() << "Unhandled error: " << llvm::toString(std::move(Err)); + } + } + } + + // Tests failures caused by references to unbound nodes. `unbound_id` is the + // id that will cause the failure. + void testUnboundNodeError(const Stencil &Stencil, llvm::StringRef UnboundId) { + testError(Stencil, AllOf(HasSubstr(UnboundId), HasSubstr("not bound"))); + } +}; + +TEST_F(StencilTest, SingleStatement) { + StringRef Condition("C"), Then("T"), Else("E"); + const std::string Snippet = R"cc( + if (true) + return 1; + else + return 0; + )cc"; + auto StmtMatch = matchStmt( + Snippet, ifStmt(hasCondition(expr().bind(Condition)), + hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else)))); + ASSERT_TRUE(StmtMatch); + // Invert the if-then-else. + auto Stencil = Stencil::cat("if (!", node(Condition), ") ", sNode(Else), + " else ", sNode(Then)); + EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)), + IsSomething(Eq("if (!true) return 0; else return 1;"))); +} + +TEST_F(StencilTest, SingleStatementCallOperator) { + StringRef Condition("C"), Then("T"), Else("E"); + const std::string Snippet = R"cc( + if (true) + return 1; + else + return 0; + )cc"; + auto StmtMatch = matchStmt( + Snippet, ifStmt(hasCondition(expr().bind(Condition)), + hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else)))); + ASSERT_TRUE(StmtMatch); + // Invert the if-then-else. + Stencil S = Stencil::cat("if (!", node(Condition), ") ", sNode(Else), + " else ", sNode(Then)); + EXPECT_THAT(toOptional(S(StmtMatch->Result)), + IsSomething(Eq("if (!true) return 0; else return 1;"))); +} + +TEST_F(StencilTest, UnboundNode) { + const std::string Snippet = R"cc( + if (true) + return 1; + else + return 0; + )cc"; + auto StmtMatch = matchStmt(Snippet, ifStmt(hasCondition(stmt().bind("a1")), + hasThen(stmt().bind("a2")))); + ASSERT_TRUE(StmtMatch); + auto Stencil = Stencil::cat("if(!", sNode("a1"), ") ", node("UNBOUND"), ";"); + auto ResultOrErr = Stencil.eval(StmtMatch->Result); + EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError())) + << "Expected unbound node, got " << *ResultOrErr; +} + +// Tests that a stencil with a single parameter (`Id`) evaluates to the expected +// string, when `Id` is bound to the expression-statement in `Snippet`. +void testExpr(StringRef Id, StringRef Snippet, const Stencil &Stencil, + StringRef Expected) { + auto StmtMatch = matchStmt(Snippet, expr().bind(Id)); + ASSERT_TRUE(StmtMatch); + EXPECT_THAT(toOptional(Stencil.eval(StmtMatch->Result)), + IsSomething(Expected)); +} + +TEST_F(StencilTest, NodeOp) { + StringRef Id = "id"; + testExpr(Id, "3;", Stencil::cat(node(Id)), "3"); +} + +TEST_F(StencilTest, SNodeOp) { + StringRef Id = "id"; + testExpr(Id, "3;", Stencil::cat(sNode(Id)), "3;"); +} + +TEST(StencilEqualityTest, Equality) { + using stencil::dPrint; + auto Lhs = Stencil::cat("foo", node("node"), dPrint("dprint_id")); + auto Rhs = Lhs; + EXPECT_EQ(Lhs, Rhs); +} + +TEST(StencilEqualityTest, InEqualityDifferentOrdering) { + auto Lhs = Stencil::cat("foo", node("node")); + auto Rhs = Stencil::cat(node("node"), "foo"); + EXPECT_NE(Lhs, Rhs); +} + +TEST(StencilEqualityTest, InEqualityDifferentSizes) { + auto Lhs = Stencil::cat("foo", node("node"), "bar", "baz"); + auto Rhs = Stencil::cat("foo", node("node"), "bar"); + EXPECT_NE(Lhs, Rhs); +} +} // namespace |