This patch adds support for matching gtest's ASSERT_THAT, EXPECT_THAT, ON_CALL and EXPECT_CALL macros.
Reviewed By: ymandel, hokein
Differential Revision: https://reviews.llvm.org/D103195
#include "clang/AST/Stmt.h"
#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/StringRef.h"
namespace clang {
namespace ast_matchers {
Lt,
};
-/// Matcher for gtest's ASSERT_... macros.
+/// This enum indicates whether the mock method in the matched ON_CALL or
+/// EXPECT_CALL macro has arguments. For example, `None` can be used to match
+/// `ON_CALL(mock, TwoParamMethod)` whereas `Some` can be used to match
+/// `ON_CALL(mock, TwoParamMethod(m1, m2))`.
+enum class MockArgs {
+ None,
+ Some,
+};
+
+/// Matcher for gtest's ASSERT comparison macros including ASSERT_EQ, ASSERT_NE,
+/// ASSERT_GE, ASSERT_GT, ASSERT_LE and ASSERT_LT.
internal::BindableMatcher<Stmt> gtestAssert(GtestCmp Cmp, StatementMatcher Left,
StatementMatcher Right);
-/// Matcher for gtest's EXPECT_... macros.
+/// Matcher for gtest's ASSERT_THAT macro.
+internal::BindableMatcher<Stmt> gtestAssertThat(StatementMatcher Actual,
+ StatementMatcher Matcher);
+
+/// Matcher for gtest's EXPECT comparison macros including EXPECT_EQ, EXPECT_NE,
+/// EXPECT_GE, EXPECT_GT, EXPECT_LE and EXPECT_LT.
internal::BindableMatcher<Stmt> gtestExpect(GtestCmp Cmp, StatementMatcher Left,
StatementMatcher Right);
+/// Matcher for gtest's EXPECT_THAT macro.
+internal::BindableMatcher<Stmt> gtestExpectThat(StatementMatcher Actual,
+ StatementMatcher Matcher);
+
+/// Matcher for gtest's EXPECT_CALL macro. `MockObject` matches the mock
+/// object and `MockMethodName` is the name of the method invoked on the mock
+/// object.
+internal::BindableMatcher<Stmt> gtestExpectCall(StatementMatcher MockObject,
+ llvm::StringRef MockMethodName,
+ MockArgs Args);
+
+/// Matcher for gtest's EXPECT_CALL macro. `MockCall` matches the whole mock
+/// member method call. This API is more flexible but requires more knowledge of
+/// the AST structure of EXPECT_CALL macros.
+internal::BindableMatcher<Stmt> gtestExpectCall(StatementMatcher MockCall,
+ MockArgs Args);
+
+/// Like the first `gtestExpectCall` overload but for `ON_CALL`.
+internal::BindableMatcher<Stmt> gtestOnCall(StatementMatcher MockObject,
+ llvm::StringRef MockMethodName,
+ MockArgs Args);
+
+/// Like the second `gtestExpectCall` overload but for `ON_CALL`.
+internal::BindableMatcher<Stmt> gtestOnCall(StatementMatcher MockCall,
+ MockArgs Args);
+
} // namespace ast_matchers
} // namespace clang
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
+//
+// This file implements several matchers for popular gtest macros. In general,
+// AST matchers cannot match calls to macros. However, we can simulate such
+// matches if the macro definition has identifiable elements that themselves can
+// be matched. In that case, we can match on those elements and then check that
+// the match occurs within an expansion of the desired macro. The more uncommon
+// the identified elements, the more efficient this process will be.
+//
+//===----------------------------------------------------------------------===//
#include "clang/ASTMatchers/GtestMatchers.h"
-#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringMap.h"
-#include "llvm/Support/Timer.h"
-#include <deque>
-#include <memory>
-#include <set>
+#include "llvm/ADT/StringRef.h"
namespace clang {
namespace ast_matchers {
+namespace {
+
+enum class MacroType {
+ Expect,
+ Assert,
+ On,
+};
+
+} // namespace
static DeclarationMatcher getComparisonDecl(GtestCmp Cmp) {
switch (Cmp) {
- case GtestCmp::Eq:
- return cxxMethodDecl(hasName("Compare"),
- ofClass(cxxRecordDecl(isSameOrDerivedFrom(
- hasName("::testing::internal::EqHelper")))));
- case GtestCmp::Ne:
- return functionDecl(hasName("::testing::internal::CmpHelperNE"));
- case GtestCmp::Ge:
- return functionDecl(hasName("::testing::internal::CmpHelperGE"));
- case GtestCmp::Gt:
- return functionDecl(hasName("::testing::internal::CmpHelperGT"));
- case GtestCmp::Le:
- return functionDecl(hasName("::testing::internal::CmpHelperLE"));
- case GtestCmp::Lt:
- return functionDecl(hasName("::testing::internal::CmpHelperLT"));
+ case GtestCmp::Eq:
+ return cxxMethodDecl(hasName("Compare"),
+ ofClass(cxxRecordDecl(isSameOrDerivedFrom(
+ hasName("::testing::internal::EqHelper")))));
+ case GtestCmp::Ne:
+ return functionDecl(hasName("::testing::internal::CmpHelperNE"));
+ case GtestCmp::Ge:
+ return functionDecl(hasName("::testing::internal::CmpHelperGE"));
+ case GtestCmp::Gt:
+ return functionDecl(hasName("::testing::internal::CmpHelperGT"));
+ case GtestCmp::Le:
+ return functionDecl(hasName("::testing::internal::CmpHelperLE"));
+ case GtestCmp::Lt:
+ return functionDecl(hasName("::testing::internal::CmpHelperLT"));
}
- llvm_unreachable("Unhandled GtestCmp enum");
}
-static llvm::StringRef getAssertMacro(GtestCmp Cmp) {
- switch (Cmp) {
- case GtestCmp::Eq:
- return "ASSERT_EQ";
- case GtestCmp::Ne:
- return "ASSERT_NE";
- case GtestCmp::Ge:
- return "ASSERT_GE";
- case GtestCmp::Gt:
- return "ASSERT_GT";
- case GtestCmp::Le:
- return "ASSERT_LE";
- case GtestCmp::Lt:
- return "ASSERT_LT";
+static llvm::StringRef getMacroTypeName(MacroType Macro) {
+ switch (Macro) {
+ case MacroType::Expect:
+ return "EXPECT";
+ case MacroType::Assert:
+ return "ASSERT";
+ case MacroType::On:
+ return "ON";
}
- llvm_unreachable("Unhandled GtestCmp enum");
}
-static llvm::StringRef getExpectMacro(GtestCmp Cmp) {
+static llvm::StringRef getComparisonTypeName(GtestCmp Cmp) {
switch (Cmp) {
- case GtestCmp::Eq:
- return "EXPECT_EQ";
- case GtestCmp::Ne:
- return "EXPECT_NE";
- case GtestCmp::Ge:
- return "EXPECT_GE";
- case GtestCmp::Gt:
- return "EXPECT_GT";
- case GtestCmp::Le:
- return "EXPECT_LE";
- case GtestCmp::Lt:
- return "EXPECT_LT";
+ case GtestCmp::Eq:
+ return "EQ";
+ case GtestCmp::Ne:
+ return "NE";
+ case GtestCmp::Ge:
+ return "GE";
+ case GtestCmp::Gt:
+ return "GT";
+ case GtestCmp::Le:
+ return "LE";
+ case GtestCmp::Lt:
+ return "LT";
+ }
+}
+
+static std::string getMacroName(MacroType Macro, GtestCmp Cmp) {
+ return (getMacroTypeName(Macro) + "_" + getComparisonTypeName(Cmp)).str();
+}
+
+static std::string getMacroName(MacroType Macro, llvm::StringRef Operation) {
+ return (getMacroTypeName(Macro) + "_" + Operation).str();
+}
+
+// Under the hood, ON_CALL is expanded to a call to `InternalDefaultActionSetAt`
+// to set a default action spec to the underlying function mocker, while
+// EXPECT_CALL is expanded to a call to `InternalExpectedAt` to set a new
+// expectation spec.
+static llvm::StringRef getSpecSetterName(MacroType Macro) {
+ switch (Macro) {
+ case MacroType::On:
+ return "InternalDefaultActionSetAt";
+ case MacroType::Expect:
+ return "InternalExpectedAt";
+ default:
+ llvm_unreachable("Unhandled MacroType enum");
}
- llvm_unreachable("Unhandled GtestCmp enum");
}
// In general, AST matchers cannot match calls to macros. However, we can
//
// We use this approach to implement the derived matchers gtestAssert and
// gtestExpect.
+static internal::BindableMatcher<Stmt>
+gtestComparisonInternal(MacroType Macro, GtestCmp Cmp, StatementMatcher Left,
+ StatementMatcher Right) {
+ return callExpr(isExpandedFromMacro(getMacroName(Macro, Cmp)),
+ callee(getComparisonDecl(Cmp)), hasArgument(2, Left),
+ hasArgument(3, Right));
+}
+
+static internal::BindableMatcher<Stmt>
+gtestThatInternal(MacroType Macro, StatementMatcher Actual,
+ StatementMatcher Matcher) {
+ return cxxOperatorCallExpr(
+ isExpandedFromMacro(getMacroName(Macro, "THAT")),
+ hasOverloadedOperatorName("()"), hasArgument(2, Actual),
+ hasArgument(
+ 0, expr(hasType(classTemplateSpecializationDecl(hasName(
+ "::testing::internal::PredicateFormatterFromMatcher"))),
+ ignoringImplicit(
+ callExpr(callee(functionDecl(hasName(
+ "::testing::internal::"
+ "MakePredicateFormatterFromMatcher"))),
+ hasArgument(0, ignoringImplicit(Matcher)))))));
+}
+
+static internal::BindableMatcher<Stmt>
+gtestCallInternal(MacroType Macro, StatementMatcher MockCall, MockArgs Args) {
+ // A ON_CALL or EXPECT_CALL macro expands to different AST structures
+ // depending on whether the mock method has arguments or not.
+ switch (Args) {
+ // For example,
+ // `ON_CALL(mock, TwoParamMethod)` is expanded to
+ // `mock.gmock_TwoArgsMethod(WithoutMatchers(),
+ // nullptr).InternalDefaultActionSetAt(...)`.
+ // EXPECT_CALL is the same except
+ // that it calls `InternalExpectedAt` instead of `InternalDefaultActionSetAt`
+ // in the end.
+ case MockArgs::None:
+ return cxxMemberCallExpr(
+ isExpandedFromMacro(getMacroName(Macro, "CALL")),
+ callee(functionDecl(hasName(getSpecSetterName(Macro)))),
+ onImplicitObjectArgument(ignoringImplicit(MockCall)));
+ // For example,
+ // `ON_CALL(mock, TwoParamMethod(m1, m2))` is expanded to
+ // `mock.gmock_TwoParamMethod(m1,m2)(WithoutMatchers(),
+ // nullptr).InternalDefaultActionSetAt(...)`.
+ // EXPECT_CALL is the same except that it calls `InternalExpectedAt` instead
+ // of `InternalDefaultActionSetAt` in the end.
+ case MockArgs::Some:
+ return cxxMemberCallExpr(
+ isExpandedFromMacro(getMacroName(Macro, "CALL")),
+ callee(functionDecl(hasName(getSpecSetterName(Macro)))),
+ onImplicitObjectArgument(ignoringImplicit(cxxOperatorCallExpr(
+ hasOverloadedOperatorName("()"), argumentCountIs(3),
+ hasArgument(0, ignoringImplicit(MockCall))))));
+ }
+}
+
+static internal::BindableMatcher<Stmt>
+gtestCallInternal(MacroType Macro, StatementMatcher MockObject,
+ llvm::StringRef MockMethodName, MockArgs Args) {
+ return gtestCallInternal(
+ Macro,
+ cxxMemberCallExpr(
+ onImplicitObjectArgument(MockObject),
+ callee(functionDecl(hasName(("gmock_" + MockMethodName).str())))),
+ Args);
+}
+
internal::BindableMatcher<Stmt> gtestAssert(GtestCmp Cmp, StatementMatcher Left,
StatementMatcher Right) {
- return callExpr(callee(getComparisonDecl(Cmp)),
- isExpandedFromMacro(getAssertMacro(Cmp).str()),
- hasArgument(2, Left), hasArgument(3, Right));
+ return gtestComparisonInternal(MacroType::Assert, Cmp, Left, Right);
}
internal::BindableMatcher<Stmt> gtestExpect(GtestCmp Cmp, StatementMatcher Left,
StatementMatcher Right) {
- return callExpr(callee(getComparisonDecl(Cmp)),
- isExpandedFromMacro(getExpectMacro(Cmp).str()),
- hasArgument(2, Left), hasArgument(3, Right));
+ return gtestComparisonInternal(MacroType::Expect, Cmp, Left, Right);
+}
+
+internal::BindableMatcher<Stmt> gtestAssertThat(StatementMatcher Actual,
+ StatementMatcher Matcher) {
+ return gtestThatInternal(MacroType::Assert, Actual, Matcher);
+}
+
+internal::BindableMatcher<Stmt> gtestExpectThat(StatementMatcher Actual,
+ StatementMatcher Matcher) {
+ return gtestThatInternal(MacroType::Expect, Actual, Matcher);
+}
+
+internal::BindableMatcher<Stmt> gtestOnCall(StatementMatcher MockObject,
+ llvm::StringRef MockMethodName,
+ MockArgs Args) {
+ return gtestCallInternal(MacroType::On, MockObject, MockMethodName, Args);
+}
+
+internal::BindableMatcher<Stmt> gtestOnCall(StatementMatcher MockCall,
+ MockArgs Args) {
+ return gtestCallInternal(MacroType::On, MockCall, Args);
+}
+
+internal::BindableMatcher<Stmt> gtestExpectCall(StatementMatcher MockObject,
+ llvm::StringRef MockMethodName,
+ MockArgs Args) {
+ return gtestCallInternal(MacroType::Expect, MockObject, MockMethodName, Args);
+}
+
+internal::BindableMatcher<Stmt> gtestExpectCall(StatementMatcher MockCall,
+ MockArgs Args) {
+ return gtestCallInternal(MacroType::Expect, MockCall, Args);
}
} // end namespace ast_matchers
#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \
GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)
+#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure) \
+ GTEST_ASSERT_(pred_format(#v1, v1), on_failure)
+
+#define EXPECT_PRED_FORMAT1(pred_format, v1) \
+ GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT1(pred_format, v1) \
+ GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_)
+
#define EXPECT_EQ(val1, val2) \
EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)
#define EXPECT_NE(val1, val2) \
#define EXPECT_LT(val1, val2) \
EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2)
+#define ASSERT_THAT(value, matcher) \
+ ASSERT_PRED_FORMAT1( \
+ ::testing::internal::MakePredicateFormatterFromMatcher(matcher), value)
+#define EXPECT_THAT(value, matcher) \
+ EXPECT_PRED_FORMAT1( \
+ ::testing::internal::MakePredicateFormatterFromMatcher(matcher), value)
+
#define ASSERT_EQ(val1, val2) \
ASSERT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)
#define ASSERT_NE(val1, val2) \
ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2)
+#define GMOCK_ON_CALL_IMPL_(mock_expr, Setter, call) \
+ ((mock_expr).gmock_##call)(::testing::internal::GetWithoutMatchers(), \
+ nullptr) \
+ .Setter(nullptr, 0, #mock_expr, #call)
+
+#define ON_CALL(obj, call) \
+ GMOCK_ON_CALL_IMPL_(obj, InternalDefaultActionSetAt, call)
+
+#define EXPECT_CALL(obj, call) \
+ GMOCK_ON_CALL_IMPL_(obj, InternalExpectedAt, call)
+
namespace testing {
namespace internal {
class EqHelper {
const T2& val2) {
return 0;
}
+
+ // For implementing ASSERT_THAT() and EXPECT_THAT(). The template
+ // argument M must be a type that can be converted to a matcher.
+ template <typename M>
+ class PredicateFormatterFromMatcher {
+ public:
+ explicit PredicateFormatterFromMatcher(M m) : matcher_(m) {}
+
+ // This template () operator allows a PredicateFormatterFromMatcher
+ // object to act as a predicate-formatter suitable for using with
+ // Google Test's EXPECT_PRED_FORMAT1() macro.
+ template <typename T>
+ int operator()(const char* value_text, const T& x) const {
+ return 0;
+ }
+
+ private:
+ const M matcher_;
+ };
+
+ template <typename M>
+ inline PredicateFormatterFromMatcher<M> MakePredicateFormatterFromMatcher(
+ M matcher) {
+ return PredicateFormatterFromMatcher<M>(matcher);
+ }
+
+ bool GetWithoutMatchers() { return false; }
+
+ template <typename F>
+ class MockSpec {
+ public:
+ MockSpec<F>() {}
+
+ bool InternalDefaultActionSetAt(
+ const char* file, int line, const char* obj, const char* call) {
+ return false;
+ }
+
+ bool InternalExpectedAt(
+ const char* file, int line, const char* obj, const char* call) {
+ return false;
+ }
+
+ MockSpec<F> operator()(bool, void*) {
+ return *this;
+ }
+ }; // class MockSpec
+
} // namespace internal
+
+ template <typename T>
+ int StrEq(T val) {
+ return 0;
+ }
+ template <typename T>
+ int Eq(T val) {
+ return 0;
+ }
+
} // namespace testing
+
+ class Mock {
+ public:
+ Mock() {}
+ testing::internal::MockSpec<int> gmock_TwoArgsMethod(int, int) {
+ return testing::internal::MockSpec<int>();
+ }
+ testing::internal::MockSpec<int> gmock_TwoArgsMethod(bool, void*) {
+ return testing::internal::MockSpec<int>();
+ }
+ }; // class Mock
)cc";
static std::string wrapGtest(llvm::StringRef Input) {
matches(wrapGtest(Input), gtestExpect(GtestCmp::Gt, expr(), expr())));
}
+TEST(GtestExpectTest, ThatShouldMatchAssertThat) {
+ std::string Input = R"cc(
+ using ::testing::Eq;
+ void Test() { ASSERT_THAT(2, Eq(2)); }
+ )cc";
+ EXPECT_TRUE(matches(
+ wrapGtest(Input),
+ gtestAssertThat(
+ expr(), callExpr(callee(functionDecl(hasName("::testing::Eq")))))));
+}
+
+TEST(GtestExpectTest, ThatShouldMatchExpectThat) {
+ std::string Input = R"cc(
+ using ::testing::Eq;
+ void Test() { EXPECT_THAT(2, Eq(2)); }
+ )cc";
+ EXPECT_TRUE(matches(
+ wrapGtest(Input),
+ gtestExpectThat(
+ expr(), callExpr(callee(functionDecl(hasName("::testing::Eq")))))));
+}
+
+TEST(GtestOnCallTest, CallShouldMatchOnCallWithoutParams1) {
+ std::string Input = R"cc(
+ void Test() {
+ Mock mock;
+ ON_CALL(mock, TwoArgsMethod);
+ }
+ )cc";
+ EXPECT_TRUE(matches(wrapGtest(Input),
+ gtestOnCall(expr(hasType(cxxRecordDecl(hasName("Mock")))),
+ "TwoArgsMethod", MockArgs::None)));
+}
+
+TEST(GtestOnCallTest, CallShouldMatchOnCallWithoutParams2) {
+ std::string Input = R"cc(
+ void Test() {
+ Mock mock;
+ ON_CALL(mock, TwoArgsMethod);
+ }
+ )cc";
+ EXPECT_TRUE(matches(
+ wrapGtest(Input),
+ gtestOnCall(cxxMemberCallExpr(
+ callee(functionDecl(hasName("gmock_TwoArgsMethod"))))
+ .bind("mock_call"),
+ MockArgs::None)));
+}
+
+TEST(GtestOnCallTest, CallShouldMatchOnCallWithParams1) {
+ std::string Input = R"cc(
+ void Test() {
+ Mock mock;
+ ON_CALL(mock, TwoArgsMethod(1, 2));
+ }
+ )cc";
+ EXPECT_TRUE(matches(wrapGtest(Input),
+ gtestOnCall(expr(hasType(cxxRecordDecl(hasName("Mock")))),
+ "TwoArgsMethod", MockArgs::Some)));
+}
+
+TEST(GtestOnCallTest, CallShouldMatchOnCallWithParams2) {
+ std::string Input = R"cc(
+ void Test() {
+ Mock mock;
+ ON_CALL(mock, TwoArgsMethod(1, 2));
+ }
+ )cc";
+ EXPECT_TRUE(matches(
+ wrapGtest(Input),
+ gtestOnCall(cxxMemberCallExpr(
+ callee(functionDecl(hasName("gmock_TwoArgsMethod"))))
+ .bind("mock_call"),
+ MockArgs::Some)));
+}
+
+TEST(GtestExpectCallTest, CallShouldMatchExpectCallWithoutParams1) {
+ std::string Input = R"cc(
+ void Test() {
+ Mock mock;
+ EXPECT_CALL(mock, TwoArgsMethod);
+ }
+ )cc";
+ EXPECT_TRUE(
+ matches(wrapGtest(Input),
+ gtestExpectCall(expr(hasType(cxxRecordDecl(hasName("Mock")))),
+ "TwoArgsMethod", MockArgs::None)));
+}
+
+TEST(GtestExpectCallTest, CallShouldMatchExpectCallWithoutParams2) {
+ std::string Input = R"cc(
+ void Test() {
+ Mock mock;
+ EXPECT_CALL(mock, TwoArgsMethod);
+ }
+ )cc";
+ EXPECT_TRUE(matches(
+ wrapGtest(Input),
+ gtestExpectCall(cxxMemberCallExpr(
+ callee(functionDecl(hasName("gmock_TwoArgsMethod"))))
+ .bind("mock_call"),
+ MockArgs::None)));
+}
+
+TEST(GtestExpectCallTest, CallShouldMatchExpectCallWithParams1) {
+ std::string Input = R"cc(
+ void Test() {
+ Mock mock;
+ EXPECT_CALL(mock, TwoArgsMethod(1, 2));
+ }
+ )cc";
+ EXPECT_TRUE(
+ matches(wrapGtest(Input),
+ gtestExpectCall(expr(hasType(cxxRecordDecl(hasName("Mock")))),
+ "TwoArgsMethod", MockArgs::Some)));
+}
+
+TEST(GtestExpectCallTest, CallShouldMatchExpectCallWithParams2) {
+ std::string Input = R"cc(
+ void Test() {
+ Mock mock;
+ EXPECT_CALL(mock, TwoArgsMethod(1, 2));
+ }
+ )cc";
+ EXPECT_TRUE(matches(
+ wrapGtest(Input),
+ gtestExpectCall(cxxMemberCallExpr(
+ callee(functionDecl(hasName("gmock_TwoArgsMethod"))))
+ .bind("mock_call"),
+ MockArgs::Some)));
+}
+
} // end namespace ast_matchers
} // end namespace clang