diff --git a/clang/include/clang/ASTMatchers/GtestMatchers.h b/clang/include/clang/ASTMatchers/GtestMatchers.h --- a/clang/include/clang/ASTMatchers/GtestMatchers.h +++ b/clang/include/clang/ASTMatchers/GtestMatchers.h @@ -16,6 +16,7 @@ #include "clang/AST/Stmt.h" #include "clang/ASTMatchers/ASTMatchers.h" +#include "llvm/ADT/StringRef.h" namespace clang { namespace ast_matchers { @@ -30,14 +31,55 @@ 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 gtestAssert(GtestCmp Cmp, StatementMatcher Left, StatementMatcher Right); -/// Matcher for gtest's EXPECT_... macros. +/// Matcher for gtest's ASSERT_THAT macro. +internal::BindableMatcher 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 gtestExpect(GtestCmp Cmp, StatementMatcher Left, StatementMatcher Right); +/// Matcher for gtest's EXPECT_THAT macro. +internal::BindableMatcher 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 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 gtestExpectCall(StatementMatcher MockCall, + MockArgs Args); + +/// Like the first `gtestExpectCall` overload but for `ON_CALL`. +internal::BindableMatcher gtestOnCall(StatementMatcher MockObject, + llvm::StringRef MockMethodName, + MockArgs Args); + +/// Like the second `gtestExpectCall` overload but for `ON_CALL`. +internal::BindableMatcher gtestOnCall(StatementMatcher MockCall, + MockArgs Args); + } // namespace ast_matchers } // namespace clang diff --git a/clang/lib/ASTMatchers/GtestMatchers.cpp b/clang/lib/ASTMatchers/GtestMatchers.cpp --- a/clang/lib/ASTMatchers/GtestMatchers.cpp +++ b/clang/lib/ASTMatchers/GtestMatchers.cpp @@ -5,76 +5,105 @@ // 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 -#include -#include +#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 @@ -86,18 +115,114 @@ // // We use this approach to implement the derived matchers gtestAssert and // gtestExpect. +static internal::BindableMatcher +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 +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 +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 +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 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 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 gtestAssertThat(StatementMatcher Actual, + StatementMatcher Matcher) { + return gtestThatInternal(MacroType::Assert, Actual, Matcher); +} + +internal::BindableMatcher gtestExpectThat(StatementMatcher Actual, + StatementMatcher Matcher) { + return gtestThatInternal(MacroType::Expect, Actual, Matcher); +} + +internal::BindableMatcher gtestOnCall(StatementMatcher MockObject, + llvm::StringRef MockMethodName, + MockArgs Args) { + return gtestCallInternal(MacroType::On, MockObject, MockMethodName, Args); +} + +internal::BindableMatcher gtestOnCall(StatementMatcher MockCall, + MockArgs Args) { + return gtestCallInternal(MacroType::On, MockCall, Args); +} + +internal::BindableMatcher gtestExpectCall(StatementMatcher MockObject, + llvm::StringRef MockMethodName, + MockArgs Args) { + return gtestCallInternal(MacroType::Expect, MockObject, MockMethodName, Args); +} + +internal::BindableMatcher gtestExpectCall(StatementMatcher MockCall, + MockArgs Args) { + return gtestCallInternal(MacroType::Expect, MockCall, Args); } } // end namespace ast_matchers diff --git a/clang/unittests/ASTMatchers/GtestMatchersTest.cpp b/clang/unittests/ASTMatchers/GtestMatchersTest.cpp --- a/clang/unittests/ASTMatchers/GtestMatchersTest.cpp +++ b/clang/unittests/ASTMatchers/GtestMatchersTest.cpp @@ -42,6 +42,14 @@ #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) \ @@ -55,11 +63,29 @@ #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 { @@ -96,8 +122,77 @@ 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 + 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 + int operator()(const char* value_text, const T& x) const { + return 0; + } + + private: + const M matcher_; + }; + + template + inline PredicateFormatterFromMatcher MakePredicateFormatterFromMatcher( + M matcher) { + return PredicateFormatterFromMatcher(matcher); + } + + bool GetWithoutMatchers() { return false; } + + template + class MockSpec { + public: + MockSpec() {} + + 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 operator()(bool, void*) { + return *this; + } + }; // class MockSpec + } // namespace internal + + template + int StrEq(T val) { + return 0; + } + template + int Eq(T val) { + return 0; + } + } // namespace testing + + class Mock { + public: + Mock() {} + testing::internal::MockSpec gmock_TwoArgsMethod(int, int) { + return testing::internal::MockSpec(); + } + testing::internal::MockSpec gmock_TwoArgsMethod(bool, void*) { + return testing::internal::MockSpec(); + } + }; // class Mock )cc"; static std::string wrapGtest(llvm::StringRef Input) { @@ -187,5 +282,137 @@ 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