//===- GtestMatchers.cpp - AST Matchers for Gtest ---------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// // // 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/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/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")); } llvm_unreachable("Unhandled GtestCmp enum"); } 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 MacroType enum"); } static llvm::StringRef getComparisonTypeName(GtestCmp Cmp) { switch (Cmp) { 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"; } llvm_unreachable("Unhandled GtestCmp enum"); } 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 MacroType enum"); } // 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. // // 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)))))); } llvm_unreachable("Unhandled MockArgs enum"); } 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 gtestComparisonInternal(MacroType::Assert, Cmp, Left, Right); } internal::BindableMatcher gtestExpect(GtestCmp Cmp, StatementMatcher Left, StatementMatcher 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 } // end namespace clang