[clang] 23444ed - [AST matchers] Add basic matchers for googletest EXPECT/ASSERT calls.

Yitzhak Mandelbaum via cfe-commits cfe-commits at lists.llvm.org
Fri Feb 21 09:06:26 PST 2020


Author: Yitzhak Mandelbaum
Date: 2020-02-21T12:05:15-05:00
New Revision: 23444edf30ba00ccefa3a582ac7ddc29774e9da5

URL: https://github.com/llvm/llvm-project/commit/23444edf30ba00ccefa3a582ac7ddc29774e9da5
DIFF: https://github.com/llvm/llvm-project/commit/23444edf30ba00ccefa3a582ac7ddc29774e9da5.diff

LOG: [AST matchers] Add basic matchers for googletest EXPECT/ASSERT calls.

Summary:
This revision adds matchers that match calls to the gtest EXPECT and ASSERT
macros almost like function calls. The matchers are placed in separate files
(GtestMatchers...), because they are specific to the gtest library.

Reviewers: gribozavr2

Subscribers: mgorny, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D74840

Added: 
    clang/include/clang/ASTMatchers/GtestMatchers.h
    clang/lib/ASTMatchers/GtestMatchers.cpp
    clang/unittests/ASTMatchers/GtestMatchersTest.cpp

Modified: 
    clang/lib/ASTMatchers/CMakeLists.txt
    clang/unittests/ASTMatchers/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/ASTMatchers/GtestMatchers.h b/clang/include/clang/ASTMatchers/GtestMatchers.h
new file mode 100644
index 000000000000..4f8addcf744a
--- /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<Stmt> gtestAssert(GtestCmp Cmp, StatementMatcher Left,
+                                            StatementMatcher Right);
+
+/// Matcher for gtest's EXPECT_... macros.
+internal::BindableMatcher<Stmt> 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 cd88d1db9ce4..8f700ca3226b 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 000000000000..317bddd035f8
--- /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 <deque>
+#include <memory>
+#include <set>
+
+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<Stmt> gtestAssert(GtestCmp Cmp, StatementMatcher Left,
+                                            StatementMatcher Right) {
+  return callExpr(callee(getComparisonDecl(Cmp)),
+                  isExpandedFromMacro(getAssertMacro(Cmp)),
+                  hasArgument(2, Left), hasArgument(3, Right));
+}
+
+internal::BindableMatcher<Stmt> 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 09c4290fa1d2..aa5438c947f4 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 000000000000..e5abb24cb4ab
--- /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 <typename T1, typename T2>
+    static int Compare(const char* lhs_expression, const char* rhs_expression,
+                       const T1& lhs, const T2& rhs) {
+      return 0;
+    }
+  };
+  template <typename T1, typename T2>
+  int CmpHelperNE(const char* expr1, const char* expr2, const T1& val1,
+                  const T2& val2) {
+    return 0;
+  }
+  template <typename T1, typename T2>
+  int CmpHelperGE(const char* expr1, const char* expr2, const T1& val1,
+                  const T2& val2) {
+    return 0;
+  }
+  template <typename T1, typename T2>
+  int CmpHelperGT(const char* expr1, const char* expr2, const T1& val1,
+                  const T2& val2) {
+    return 0;
+  }
+  template <typename T1, typename T2>
+  int CmpHelperLE(const char* expr1, const char* expr2, const T1& val1,
+                  const T2& val2) {
+    return 0;
+  }
+  template <typename T1, typename T2>
+  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


        


More information about the cfe-commits mailing list