123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- //===- 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/Transformer/Stencil.h"
- #include "clang/ASTMatchers/ASTMatchers.h"
- #include "clang/Tooling/FixIt.h"
- #include "clang/Tooling/Tooling.h"
- #include "llvm/Support/Error.h"
- #include "llvm/Testing/Support/Error.h"
- #include "gmock/gmock.h"
- #include "gtest/gtest.h"
- using namespace clang;
- using namespace transformer;
- using namespace ast_matchers;
- namespace {
- using ::llvm::Failed;
- using ::llvm::HasValue;
- using ::llvm::StringError;
- using ::testing::AllOf;
- using ::testing::Eq;
- using ::testing::HasSubstr;
- using MatchResult = MatchFinder::MatchResult;
- // Create a valid translation-unit from a statement.
- static std::string wrapSnippet(StringRef StatementCode) {
- return ("struct S { int field; }; 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 one of the statements in
- // `StatementCode` exactly -- that is, produce exactly one match. However,
- // `StatementCode` may contain other statements not described by `Matcher`.
- static llvm::Optional<TestMatch> matchStmt(StringRef StatementCode,
- StatementMatcher Matcher) {
- auto AstUnit = tooling::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 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, 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 = cat("if (!", node(Condition), ") ", statement(Else), " else ",
- statement(Then));
- EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result),
- HasValue("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 = cat("if (!", node(Condition), ") ", statement(Else), " else ",
- statement(Then));
- EXPECT_THAT_EXPECTED(S(StmtMatch->Result),
- HasValue("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 = cat("if(!", node("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_EXPECTED(Stencil.eval(StmtMatch->Result), HasValue(Expected));
- }
- void testFailure(StringRef Id, StringRef Snippet, const Stencil &Stencil,
- testing::Matcher<std::string> MessageMatcher) {
- auto StmtMatch = matchStmt(Snippet, expr().bind(Id));
- ASSERT_TRUE(StmtMatch);
- EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result),
- Failed<StringError>(testing::Property(
- &StringError::getMessage, MessageMatcher)));
- }
- TEST_F(StencilTest, SelectionOp) {
- StringRef Id = "id";
- testExpr(Id, "3;", cat(node(Id)), "3");
- }
- TEST_F(StencilTest, IfBoundOpBound) {
- StringRef Id = "id";
- testExpr(Id, "3;", cat(ifBound(Id, text("5"), text("7"))), "5");
- }
- TEST_F(StencilTest, IfBoundOpUnbound) {
- StringRef Id = "id";
- testExpr(Id, "3;", cat(ifBound("other", text("5"), text("7"))), "7");
- }
- TEST_F(StencilTest, ExpressionOpNoParens) {
- StringRef Id = "id";
- testExpr(Id, "3;", cat(expression(Id)), "3");
- }
- // Don't parenthesize a parens expression.
- TEST_F(StencilTest, ExpressionOpNoParensParens) {
- StringRef Id = "id";
- testExpr(Id, "(3);", cat(expression(Id)), "(3)");
- }
- TEST_F(StencilTest, ExpressionOpBinaryOpParens) {
- StringRef Id = "id";
- testExpr(Id, "3+4;", cat(expression(Id)), "(3+4)");
- }
- // `expression` shares code with other ops, so we get sufficient coverage of the
- // error handling code with this test. If that changes in the future, more error
- // tests should be added.
- TEST_F(StencilTest, ExpressionOpUnbound) {
- StringRef Id = "id";
- testFailure(Id, "3;", cat(expression("ACACA")),
- AllOf(HasSubstr("ACACA"), HasSubstr("not bound")));
- }
- TEST_F(StencilTest, DerefPointer) {
- StringRef Id = "id";
- testExpr(Id, "int *x; x;", cat(deref(Id)), "*x");
- }
- TEST_F(StencilTest, DerefBinOp) {
- StringRef Id = "id";
- testExpr(Id, "int *x; x + 1;", cat(deref(Id)), "*(x + 1)");
- }
- TEST_F(StencilTest, DerefAddressExpr) {
- StringRef Id = "id";
- testExpr(Id, "int x; &x;", cat(deref(Id)), "x");
- }
- TEST_F(StencilTest, AddressOfValue) {
- StringRef Id = "id";
- testExpr(Id, "int x; x;", cat(addressOf(Id)), "&x");
- }
- TEST_F(StencilTest, AddressOfDerefExpr) {
- StringRef Id = "id";
- testExpr(Id, "int *x; *x;", cat(addressOf(Id)), "x");
- }
- TEST_F(StencilTest, AccessOpValue) {
- StringRef Snippet = R"cc(
- S x;
- x;
- )cc";
- StringRef Id = "id";
- testExpr(Id, Snippet, cat(access(Id, "field")), "x.field");
- }
- TEST_F(StencilTest, AccessOpValueExplicitText) {
- StringRef Snippet = R"cc(
- S x;
- x;
- )cc";
- StringRef Id = "id";
- testExpr(Id, Snippet, cat(access(Id, text("field"))), "x.field");
- }
- TEST_F(StencilTest, AccessOpValueAddress) {
- StringRef Snippet = R"cc(
- S x;
- &x;
- )cc";
- StringRef Id = "id";
- testExpr(Id, Snippet, cat(access(Id, "field")), "x.field");
- }
- TEST_F(StencilTest, AccessOpPointer) {
- StringRef Snippet = R"cc(
- S *x;
- x;
- )cc";
- StringRef Id = "id";
- testExpr(Id, Snippet, cat(access(Id, "field")), "x->field");
- }
- TEST_F(StencilTest, AccessOpPointerDereference) {
- StringRef Snippet = R"cc(
- S *x;
- *x;
- )cc";
- StringRef Id = "id";
- testExpr(Id, Snippet, cat(access(Id, "field")), "x->field");
- }
- TEST_F(StencilTest, AccessOpExplicitThis) {
- using clang::ast_matchers::hasObjectExpression;
- using clang::ast_matchers::memberExpr;
- // Set up the code so we can bind to a use of this.
- StringRef Snippet = R"cc(
- class C {
- public:
- int x;
- int foo() { return this->x; }
- };
- )cc";
- auto StmtMatch =
- matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr(
- hasObjectExpression(expr().bind("obj")))))));
- ASSERT_TRUE(StmtMatch);
- const Stencil Stencil = cat(access("obj", "field"));
- EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result),
- HasValue("this->field"));
- }
- TEST_F(StencilTest, AccessOpImplicitThis) {
- using clang::ast_matchers::hasObjectExpression;
- using clang::ast_matchers::memberExpr;
- // Set up the code so we can bind to a use of (implicit) this.
- StringRef Snippet = R"cc(
- class C {
- public:
- int x;
- int foo() { return x; }
- };
- )cc";
- auto StmtMatch =
- matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr(
- hasObjectExpression(expr().bind("obj")))))));
- ASSERT_TRUE(StmtMatch);
- const Stencil Stencil = cat(access("obj", "field"));
- EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), HasValue("field"));
- }
- TEST_F(StencilTest, RunOp) {
- StringRef Id = "id";
- auto SimpleFn = [Id](const MatchResult &R) {
- return std::string(R.Nodes.getNodeAs<Stmt>(Id) != nullptr ? "Bound"
- : "Unbound");
- };
- testExpr(Id, "3;", cat(run(SimpleFn)), "Bound");
- }
- TEST(StencilToStringTest, RawTextOp) {
- auto S = cat("foo bar baz");
- StringRef Expected = R"("foo bar baz")";
- EXPECT_EQ(S.toString(), Expected);
- }
- TEST(StencilToStringTest, RawTextOpEscaping) {
- auto S = cat("foo \"bar\" baz\\n");
- StringRef Expected = R"("foo \"bar\" baz\\n")";
- EXPECT_EQ(S.toString(), Expected);
- }
- TEST(StencilToStringTest, DebugPrintNodeOp) {
- auto S = cat(dPrint("Id"));
- StringRef Expected = R"repr(dPrint("Id"))repr";
- EXPECT_EQ(S.toString(), Expected);
- }
- TEST(StencilToStringTest, ExpressionOp) {
- auto S = cat(expression("Id"));
- StringRef Expected = R"repr(expression("Id"))repr";
- EXPECT_EQ(S.toString(), Expected);
- }
- TEST(StencilToStringTest, DerefOp) {
- auto S = cat(deref("Id"));
- StringRef Expected = R"repr(deref("Id"))repr";
- EXPECT_EQ(S.toString(), Expected);
- }
- TEST(StencilToStringTest, AddressOfOp) {
- auto S = cat(addressOf("Id"));
- StringRef Expected = R"repr(addressOf("Id"))repr";
- EXPECT_EQ(S.toString(), Expected);
- }
- TEST(StencilToStringTest, SelectionOp) {
- auto S1 = cat(node("node1"));
- EXPECT_EQ(S1.toString(), "selection(...)");
- }
- TEST(StencilToStringTest, AccessOp) {
- auto S = cat(access("Id", text("memberData")));
- StringRef Expected = R"repr(access("Id", "memberData"))repr";
- EXPECT_EQ(S.toString(), Expected);
- }
- TEST(StencilToStringTest, AccessOpStencilPart) {
- auto S = cat(access("Id", access("subId", "memberData")));
- StringRef Expected = R"repr(access("Id", access("subId", "memberData")))repr";
- EXPECT_EQ(S.toString(), Expected);
- }
- TEST(StencilToStringTest, IfBoundOp) {
- auto S = cat(ifBound("Id", text("trueText"), access("exprId", "memberData")));
- StringRef Expected =
- R"repr(ifBound("Id", "trueText", access("exprId", "memberData")))repr";
- EXPECT_EQ(S.toString(), Expected);
- }
- TEST(StencilToStringTest, RunOp) {
- auto F1 = [](const MatchResult &R) { return "foo"; };
- auto S1 = cat(run(F1));
- EXPECT_EQ(S1.toString(), "run(...)");
- }
- TEST(StencilToStringTest, MultipleOp) {
- auto S = cat("foo", access("x", "m()"), "bar",
- ifBound("x", text("t"), access("e", "f")));
- StringRef Expected = R"repr("foo", access("x", "m()"), "bar", )repr"
- R"repr(ifBound("x", "t", access("e", "f")))repr";
- EXPECT_EQ(S.toString(), Expected);
- }
- } // namespace
|