1 //===- unittest/Tooling/StencilTest.cpp -----------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "clang/Tooling/Refactoring/Stencil.h" 10 #include "clang/ASTMatchers/ASTMatchers.h" 11 #include "clang/Tooling/FixIt.h" 12 #include "clang/Tooling/Tooling.h" 13 #include "llvm/Support/Error.h" 14 #include "llvm/Testing/Support/Error.h" 15 #include "gmock/gmock.h" 16 #include "gtest/gtest.h" 17 18 using namespace clang; 19 using namespace tooling; 20 using namespace ast_matchers; 21 22 namespace { 23 using ::llvm::HasValue; 24 using ::testing::AllOf; 25 using ::testing::Eq; 26 using ::testing::HasSubstr; 27 using MatchResult = MatchFinder::MatchResult; 28 using stencil::access; 29 using stencil::cat; 30 using stencil::dPrint; 31 using stencil::ifBound; 32 using stencil::run; 33 using stencil::text; 34 35 // Create a valid translation-unit from a statement. 36 static std::string wrapSnippet(StringRef StatementCode) { 37 return ("struct S { int field; }; auto stencil_test_snippet = []{" + 38 StatementCode + "};") 39 .str(); 40 } 41 42 static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) { 43 return varDecl(hasName("stencil_test_snippet"), 44 hasDescendant(compoundStmt(hasAnySubstatement(Matcher)))); 45 } 46 47 struct TestMatch { 48 // The AST unit from which `result` is built. We bundle it because it backs 49 // the result. Users are not expected to access it. 50 std::unique_ptr<ASTUnit> AstUnit; 51 // The result to use in the test. References `ast_unit`. 52 MatchResult Result; 53 }; 54 55 // Matches `Matcher` against the statement `StatementCode` and returns the 56 // result. Handles putting the statement inside a function and modifying the 57 // matcher correspondingly. `Matcher` should match one of the statements in 58 // `StatementCode` exactly -- that is, produce exactly one match. However, 59 // `StatementCode` may contain other statements not described by `Matcher`. 60 static llvm::Optional<TestMatch> matchStmt(StringRef StatementCode, 61 StatementMatcher Matcher) { 62 auto AstUnit = buildASTFromCode(wrapSnippet(StatementCode)); 63 if (AstUnit == nullptr) { 64 ADD_FAILURE() << "AST construction failed"; 65 return llvm::None; 66 } 67 ASTContext &Context = AstUnit->getASTContext(); 68 auto Matches = ast_matchers::match(wrapMatcher(Matcher), Context); 69 // We expect a single, exact match for the statement. 70 if (Matches.size() != 1) { 71 ADD_FAILURE() << "Wrong number of matches: " << Matches.size(); 72 return llvm::None; 73 } 74 return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)}; 75 } 76 77 class StencilTest : public ::testing::Test { 78 protected: 79 // Verifies that the given stencil fails when evaluated on a valid match 80 // result. Binds a statement to "stmt", a (non-member) ctor-initializer to 81 // "init", an expression to "expr" and a (nameless) declaration to "decl". 82 void testError(const Stencil &Stencil, 83 ::testing::Matcher<std::string> Matcher) { 84 const std::string Snippet = R"cc( 85 struct A {}; 86 class F : public A { 87 public: 88 F(int) {} 89 }; 90 F(1); 91 )cc"; 92 auto StmtMatch = matchStmt( 93 Snippet, 94 stmt(hasDescendant( 95 cxxConstructExpr( 96 hasDeclaration(decl(hasDescendant(cxxCtorInitializer( 97 isBaseInitializer()) 98 .bind("init"))) 99 .bind("decl"))) 100 .bind("expr"))) 101 .bind("stmt")); 102 ASSERT_TRUE(StmtMatch); 103 if (auto ResultOrErr = Stencil.eval(StmtMatch->Result)) { 104 ADD_FAILURE() << "Expected failure but succeeded: " << *ResultOrErr; 105 } else { 106 auto Err = llvm::handleErrors(ResultOrErr.takeError(), 107 [&Matcher](const llvm::StringError &Err) { 108 EXPECT_THAT(Err.getMessage(), Matcher); 109 }); 110 if (Err) { 111 ADD_FAILURE() << "Unhandled error: " << llvm::toString(std::move(Err)); 112 } 113 } 114 } 115 116 // Tests failures caused by references to unbound nodes. `unbound_id` is the 117 // id that will cause the failure. 118 void testUnboundNodeError(const Stencil &Stencil, StringRef UnboundId) { 119 testError(Stencil, AllOf(HasSubstr(UnboundId), HasSubstr("not bound"))); 120 } 121 }; 122 123 TEST_F(StencilTest, SingleStatement) { 124 StringRef Condition("C"), Then("T"), Else("E"); 125 const std::string Snippet = R"cc( 126 if (true) 127 return 1; 128 else 129 return 0; 130 )cc"; 131 auto StmtMatch = matchStmt( 132 Snippet, ifStmt(hasCondition(expr().bind(Condition)), 133 hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else)))); 134 ASSERT_TRUE(StmtMatch); 135 // Invert the if-then-else. 136 auto Stencil = cat("if (!", node(Condition), ") ", statement(Else), " else ", 137 statement(Then)); 138 EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), 139 HasValue("if (!true) return 0; else return 1;")); 140 } 141 142 TEST_F(StencilTest, SingleStatementCallOperator) { 143 StringRef Condition("C"), Then("T"), Else("E"); 144 const std::string Snippet = R"cc( 145 if (true) 146 return 1; 147 else 148 return 0; 149 )cc"; 150 auto StmtMatch = matchStmt( 151 Snippet, ifStmt(hasCondition(expr().bind(Condition)), 152 hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else)))); 153 ASSERT_TRUE(StmtMatch); 154 // Invert the if-then-else. 155 Stencil S = cat("if (!", node(Condition), ") ", statement(Else), " else ", 156 statement(Then)); 157 EXPECT_THAT_EXPECTED(S(StmtMatch->Result), 158 HasValue("if (!true) return 0; else return 1;")); 159 } 160 161 TEST_F(StencilTest, UnboundNode) { 162 const std::string Snippet = R"cc( 163 if (true) 164 return 1; 165 else 166 return 0; 167 )cc"; 168 auto StmtMatch = matchStmt(Snippet, ifStmt(hasCondition(stmt().bind("a1")), 169 hasThen(stmt().bind("a2")))); 170 ASSERT_TRUE(StmtMatch); 171 auto Stencil = cat("if(!", node("a1"), ") ", node("UNBOUND"), ";"); 172 auto ResultOrErr = Stencil.eval(StmtMatch->Result); 173 EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError())) 174 << "Expected unbound node, got " << *ResultOrErr; 175 } 176 177 // Tests that a stencil with a single parameter (`Id`) evaluates to the expected 178 // string, when `Id` is bound to the expression-statement in `Snippet`. 179 void testExpr(StringRef Id, StringRef Snippet, const Stencil &Stencil, 180 StringRef Expected) { 181 auto StmtMatch = matchStmt(Snippet, expr().bind(Id)); 182 ASSERT_TRUE(StmtMatch); 183 EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), HasValue(Expected)); 184 } 185 186 TEST_F(StencilTest, SelectionOp) { 187 StringRef Id = "id"; 188 testExpr(Id, "3;", cat(node(Id)), "3"); 189 } 190 191 TEST_F(StencilTest, IfBoundOpBound) { 192 StringRef Id = "id"; 193 testExpr(Id, "3;", cat(ifBound(Id, text("5"), text("7"))), "5"); 194 } 195 196 TEST_F(StencilTest, IfBoundOpUnbound) { 197 StringRef Id = "id"; 198 testExpr(Id, "3;", cat(ifBound("other", text("5"), text("7"))), "7"); 199 } 200 201 TEST_F(StencilTest, AccessOpValue) { 202 StringRef Snippet = R"cc( 203 S x; 204 x; 205 )cc"; 206 StringRef Id = "id"; 207 testExpr(Id, Snippet, cat(access(Id, "field")), "x.field"); 208 } 209 210 TEST_F(StencilTest, AccessOpValueExplicitText) { 211 StringRef Snippet = R"cc( 212 S x; 213 x; 214 )cc"; 215 StringRef Id = "id"; 216 testExpr(Id, Snippet, cat(access(Id, text("field"))), "x.field"); 217 } 218 219 TEST_F(StencilTest, AccessOpValueAddress) { 220 StringRef Snippet = R"cc( 221 S x; 222 &x; 223 )cc"; 224 StringRef Id = "id"; 225 testExpr(Id, Snippet, cat(access(Id, "field")), "x.field"); 226 } 227 228 TEST_F(StencilTest, AccessOpPointer) { 229 StringRef Snippet = R"cc( 230 S *x; 231 x; 232 )cc"; 233 StringRef Id = "id"; 234 testExpr(Id, Snippet, cat(access(Id, "field")), "x->field"); 235 } 236 237 TEST_F(StencilTest, AccessOpPointerDereference) { 238 StringRef Snippet = R"cc( 239 S *x; 240 *x; 241 )cc"; 242 StringRef Id = "id"; 243 testExpr(Id, Snippet, cat(access(Id, "field")), "x->field"); 244 } 245 246 TEST_F(StencilTest, AccessOpExplicitThis) { 247 using clang::ast_matchers::hasObjectExpression; 248 using clang::ast_matchers::memberExpr; 249 250 // Set up the code so we can bind to a use of this. 251 StringRef Snippet = R"cc( 252 class C { 253 public: 254 int x; 255 int foo() { return this->x; } 256 }; 257 )cc"; 258 auto StmtMatch = 259 matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr( 260 hasObjectExpression(expr().bind("obj"))))))); 261 ASSERT_TRUE(StmtMatch); 262 const Stencil Stencil = cat(access("obj", "field")); 263 EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), 264 HasValue("this->field")); 265 } 266 267 TEST_F(StencilTest, AccessOpImplicitThis) { 268 using clang::ast_matchers::hasObjectExpression; 269 using clang::ast_matchers::memberExpr; 270 271 // Set up the code so we can bind to a use of (implicit) this. 272 StringRef Snippet = R"cc( 273 class C { 274 public: 275 int x; 276 int foo() { return x; } 277 }; 278 )cc"; 279 auto StmtMatch = 280 matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr( 281 hasObjectExpression(expr().bind("obj"))))))); 282 ASSERT_TRUE(StmtMatch); 283 const Stencil Stencil = cat(access("obj", "field")); 284 EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), HasValue("field")); 285 } 286 287 TEST_F(StencilTest, RunOp) { 288 StringRef Id = "id"; 289 auto SimpleFn = [Id](const MatchResult &R) { 290 return std::string(R.Nodes.getNodeAs<Stmt>(Id) != nullptr ? "Bound" 291 : "Unbound"); 292 }; 293 testExpr(Id, "3;", cat(run(SimpleFn)), "Bound"); 294 } 295 296 TEST(StencilEqualityTest, Equality) { 297 auto Lhs = cat("foo", dPrint("dprint_id")); 298 auto Rhs = cat("foo", dPrint("dprint_id")); 299 EXPECT_EQ(Lhs, Rhs); 300 } 301 302 TEST(StencilEqualityTest, InEqualityDifferentOrdering) { 303 auto Lhs = cat("foo", dPrint("node")); 304 auto Rhs = cat(dPrint("node"), "foo"); 305 EXPECT_NE(Lhs, Rhs); 306 } 307 308 TEST(StencilEqualityTest, InEqualityDifferentSizes) { 309 auto Lhs = cat("foo", dPrint("node"), "bar", "baz"); 310 auto Rhs = cat("foo", dPrint("node"), "bar"); 311 EXPECT_NE(Lhs, Rhs); 312 } 313 314 // node is opaque and therefore cannot be examined for equality. 315 TEST(StencilEqualityTest, InEqualitySelection) { 316 auto S1 = cat(node("node")); 317 auto S2 = cat(node("node")); 318 EXPECT_NE(S1, S2); 319 } 320 321 // `run` is opaque. 322 TEST(StencilEqualityTest, InEqualityRun) { 323 auto F = [](const MatchResult &R) { return "foo"; }; 324 auto S1 = cat(run(F)); 325 auto S2 = cat(run(F)); 326 EXPECT_NE(S1, S2); 327 } 328 } // namespace 329