2019-04-19 01:23:01 +08:00
|
|
|
//===- 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;
|
2019-05-23 02:03:00 +08:00
|
|
|
using stencil::cat;
|
|
|
|
using stencil::dPrint;
|
|
|
|
using stencil::text;
|
2019-04-19 01:23:01 +08:00
|
|
|
|
|
|
|
// 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.
|
2019-05-23 02:03:00 +08:00
|
|
|
auto Stencil = cat("if (!", node(Condition), ") ", statement(Else), " else ",
|
|
|
|
statement(Then));
|
2019-04-19 01:23:01 +08:00
|
|
|
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.
|
2019-05-23 02:03:00 +08:00
|
|
|
Stencil S = cat("if (!", node(Condition), ") ", statement(Else), " else ",
|
|
|
|
statement(Then));
|
2019-04-19 01:23:01 +08:00
|
|
|
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);
|
2019-05-23 02:03:00 +08:00
|
|
|
auto Stencil = cat("if(!", node("a1"), ") ", node("UNBOUND"), ";");
|
2019-04-19 01:23:01 +08:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2019-05-23 02:03:00 +08:00
|
|
|
TEST_F(StencilTest, SelectionOp) {
|
2019-04-19 01:23:01 +08:00
|
|
|
StringRef Id = "id";
|
2019-05-23 02:03:00 +08:00
|
|
|
testExpr(Id, "3;", cat(node(Id)), "3");
|
2019-04-19 01:23:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(StencilEqualityTest, Equality) {
|
2019-05-23 02:03:00 +08:00
|
|
|
auto Lhs = cat("foo", dPrint("dprint_id"));
|
|
|
|
auto Rhs = cat("foo", dPrint("dprint_id"));
|
2019-04-19 01:23:01 +08:00
|
|
|
EXPECT_EQ(Lhs, Rhs);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(StencilEqualityTest, InEqualityDifferentOrdering) {
|
2019-05-23 02:03:00 +08:00
|
|
|
auto Lhs = cat("foo", dPrint("node"));
|
|
|
|
auto Rhs = cat(dPrint("node"), "foo");
|
2019-04-19 01:23:01 +08:00
|
|
|
EXPECT_NE(Lhs, Rhs);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(StencilEqualityTest, InEqualityDifferentSizes) {
|
2019-05-23 02:03:00 +08:00
|
|
|
auto Lhs = cat("foo", dPrint("node"), "bar", "baz");
|
|
|
|
auto Rhs = cat("foo", dPrint("node"), "bar");
|
2019-04-19 01:23:01 +08:00
|
|
|
EXPECT_NE(Lhs, Rhs);
|
|
|
|
}
|
2019-05-23 02:03:00 +08:00
|
|
|
|
|
|
|
// node is opaque and therefore cannot be examined for equality.
|
|
|
|
TEST(StencilEqualityTest, InEqualitySelection) {
|
|
|
|
auto S1 = cat(node("node"));
|
|
|
|
auto S2 = cat(node("node"));
|
|
|
|
EXPECT_NE(S1, S2);
|
|
|
|
}
|
2019-04-19 01:23:01 +08:00
|
|
|
} // namespace
|