diff --git a/clang/include/clang/ASTMatchers/GtestMatchers.h b/clang/include/clang/ASTMatchers/GtestMatchers.h new file mode 100644 index 0000000000000..4f8addcf744ae --- /dev/null +++ b/clang/include/clang/ASTMatchers/GtestMatchers.h @@ -0,0 +1,45 @@ +//===- GtestMatchers.h - 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 matchers specific to structures in the Googletest +// (gtest) framework. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ASTMATCHERS_GTESTMATCHERS_H +#define LLVM_CLANG_ASTMATCHERS_GTESTMATCHERS_H + +#include "clang/AST/Stmt.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +namespace clang { +namespace ast_matchers { + +/// Gtest's comparison operations. +enum class GtestCmp { + Eq, + Ne, + Ge, + Gt, + Le, + Lt, +}; + +/// Matcher for gtest's ASSERT_... macros. +internal::BindableMatcher gtestAssert(GtestCmp Cmp, StatementMatcher Left, + StatementMatcher Right); + +/// Matcher for gtest's EXPECT_... macros. +internal::BindableMatcher gtestExpect(GtestCmp Cmp, StatementMatcher Left, + StatementMatcher Right); + +} // namespace ast_matchers +} // namespace clang + +#endif // LLVM_CLANG_ASTMATCHERS_GTESTMATCHERS_H + diff --git a/clang/lib/ASTMatchers/CMakeLists.txt b/clang/lib/ASTMatchers/CMakeLists.txt index cd88d1db9ce43..8f700ca3226be 100644 --- a/clang/lib/ASTMatchers/CMakeLists.txt +++ b/clang/lib/ASTMatchers/CMakeLists.txt @@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS support) add_clang_library(clangASTMatchers ASTMatchFinder.cpp ASTMatchersInternal.cpp + GtestMatchers.cpp LINK_LIBS clangAST diff --git a/clang/lib/ASTMatchers/GtestMatchers.cpp b/clang/lib/ASTMatchers/GtestMatchers.cpp new file mode 100644 index 0000000000000..317bddd035f84 --- /dev/null +++ b/clang/lib/ASTMatchers/GtestMatchers.cpp @@ -0,0 +1,101 @@ +//===- 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 +// +//===----------------------------------------------------------------------===// + +#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 "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Timer.h" +#include +#include +#include + +namespace clang { +namespace ast_matchers { + +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")); + } +} + +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 getExpectMacro(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"; + } +} + +// 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. +internal::BindableMatcher gtestAssert(GtestCmp Cmp, StatementMatcher Left, + StatementMatcher Right) { + return callExpr(callee(getComparisonDecl(Cmp)), + isExpandedFromMacro(getAssertMacro(Cmp)), + hasArgument(2, Left), hasArgument(3, Right)); +} + +internal::BindableMatcher gtestExpect(GtestCmp Cmp, StatementMatcher Left, + StatementMatcher Right) { + return callExpr(callee(getComparisonDecl(Cmp)), + isExpandedFromMacro(getExpectMacro(Cmp)), + hasArgument(2, Left), hasArgument(3, Right)); +} + +} // end namespace ast_matchers +} // end namespace clang diff --git a/clang/unittests/ASTMatchers/CMakeLists.txt b/clang/unittests/ASTMatchers/CMakeLists.txt index 09c4290fa1d21..aa5438c947f40 100644 --- a/clang/unittests/ASTMatchers/CMakeLists.txt +++ b/clang/unittests/ASTMatchers/CMakeLists.txt @@ -16,6 +16,7 @@ add_clang_unittest(ASTMatchersTests ASTMatchersNodeTest.cpp ASTMatchersNarrowingTest.cpp ASTMatchersTraversalTest.cpp + GtestMatchersTest.cpp ) clang_target_link_libraries(ASTMatchersTests diff --git a/clang/unittests/ASTMatchers/GtestMatchersTest.cpp b/clang/unittests/ASTMatchers/GtestMatchersTest.cpp new file mode 100644 index 0000000000000..e5abb24cb4aba --- /dev/null +++ b/clang/unittests/ASTMatchers/GtestMatchersTest.cpp @@ -0,0 +1,191 @@ +//===- unittests/ASTMatchers/GTestMatchersTest.cpp - GTest matcher unit tests // +// +// 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 "ASTMatchersTest.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/GtestMatchers.h" + +namespace clang { +namespace ast_matchers { + +constexpr llvm::StringLiteral GtestMockDecls = R"cc( + static int testerr; + +#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + switch (0) \ + case 0: \ + default: // NOLINT + +#define GTEST_NONFATAL_FAILURE_(code) testerr = code + +#define GTEST_FATAL_FAILURE_(code) testerr = code + +#define GTEST_ASSERT_(expression, on_failure) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const int gtest_ar = (expression)) \ + ; \ + else \ + on_failure(gtest_ar) + + // Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2. + // Don't use this in your code. +#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure) \ + GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), on_failure) + +#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_) +#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_) + +#define EXPECT_EQ(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2) +#define EXPECT_NE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) +#define EXPECT_GE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define EXPECT_GT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) +#define EXPECT_LE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define EXPECT_LT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) + +#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) + + namespace testing { + namespace internal { + class EqHelper { + public: + // This templatized version is for the general case. + template + static int Compare(const char* lhs_expression, const char* rhs_expression, + const T1& lhs, const T2& rhs) { + return 0; + } + }; + template + int CmpHelperNE(const char* expr1, const char* expr2, const T1& val1, + const T2& val2) { + return 0; + } + template + int CmpHelperGE(const char* expr1, const char* expr2, const T1& val1, + const T2& val2) { + return 0; + } + template + int CmpHelperGT(const char* expr1, const char* expr2, const T1& val1, + const T2& val2) { + return 0; + } + template + int CmpHelperLE(const char* expr1, const char* expr2, const T1& val1, + const T2& val2) { + return 0; + } + template + int CmpHelperLT(const char* expr1, const char* expr2, const T1& val1, + const T2& val2) { + return 0; + } + } // namespace internal + } // namespace testing +)cc"; + +static std::string wrapGtest(llvm::StringRef Input) { + return (GtestMockDecls + Input).str(); +} + +TEST(GtestAssertTest, ShouldMatchAssert) { + std::string Input = R"cc( + void Test() { ASSERT_EQ(1010, 4321); } + )cc"; + EXPECT_TRUE(matches(wrapGtest(Input), + gtestAssert(GtestCmp::Eq, integerLiteral(equals(1010)), + integerLiteral(equals(4321))))); +} + +TEST(GtestAssertTest, ShouldNotMatchExpect) { + std::string Input = R"cc( + void Test() { EXPECT_EQ(2, 3); } + )cc"; + EXPECT_TRUE( + notMatches(wrapGtest(Input), gtestAssert(GtestCmp::Eq, expr(), expr()))); +} + +TEST(GtestAssertTest, ShouldMatchNestedAssert) { + std::string Input = R"cc( + #define WRAPPER(a, b) ASSERT_EQ(a, b) + void Test() { WRAPPER(2, 3); } + )cc"; + EXPECT_TRUE( + matches(wrapGtest(Input), gtestAssert(GtestCmp::Eq, expr(), expr()))); +} + +TEST(GtestExpectTest, ShouldMatchExpect) { + std::string Input = R"cc( + void Test() { EXPECT_EQ(1010, 4321); } + )cc"; + EXPECT_TRUE(matches(wrapGtest(Input), + gtestExpect(GtestCmp::Eq, integerLiteral(equals(1010)), + integerLiteral(equals(4321))))); +} + +TEST(GtestExpectTest, ShouldNotMatchAssert) { + std::string Input = R"cc( + void Test() { ASSERT_EQ(2, 3); } + )cc"; + EXPECT_TRUE( + notMatches(wrapGtest(Input), gtestExpect(GtestCmp::Eq, expr(), expr()))); +} + +TEST(GtestExpectTest, NeShouldMatchExpectNe) { + std::string Input = R"cc( + void Test() { EXPECT_NE(2, 3); } + )cc"; + EXPECT_TRUE( + matches(wrapGtest(Input), gtestExpect(GtestCmp::Ne, expr(), expr()))); +} + +TEST(GtestExpectTest, LeShouldMatchExpectLe) { + std::string Input = R"cc( + void Test() { EXPECT_LE(2, 3); } + )cc"; + EXPECT_TRUE( + matches(wrapGtest(Input), gtestExpect(GtestCmp::Le, expr(), expr()))); +} + +TEST(GtestExpectTest, LtShouldMatchExpectLt) { + std::string Input = R"cc( + void Test() { EXPECT_LT(2, 3); } + )cc"; + EXPECT_TRUE( + matches(wrapGtest(Input), gtestExpect(GtestCmp::Lt, expr(), expr()))); +} + +TEST(GtestExpectTest, GeShouldMatchExpectGe) { + std::string Input = R"cc( + void Test() { EXPECT_GE(2, 3); } + )cc"; + EXPECT_TRUE( + matches(wrapGtest(Input), gtestExpect(GtestCmp::Ge, expr(), expr()))); +} + +TEST(GtestExpectTest, GtShouldMatchExpectGt) { + std::string Input = R"cc( + void Test() { EXPECT_GT(2, 3); } + )cc"; + EXPECT_TRUE( + matches(wrapGtest(Input), gtestExpect(GtestCmp::Gt, expr(), expr()))); +} + +} // end namespace ast_matchers +} // end namespace clang