[AST matchers] Add basic matchers for googletest EXPECT/ASSERT calls.
authorYitzhak Mandelbaum <yitzhakm@google.com>
Wed, 19 Feb 2020 16:04:51 +0000 (11:04 -0500)
committerYitzhak Mandelbaum <yitzhakm@google.com>
Fri, 21 Feb 2020 17:05:15 +0000 (12:05 -0500)
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

clang/include/clang/ASTMatchers/GtestMatchers.h [new file with mode: 0644]
clang/lib/ASTMatchers/CMakeLists.txt
clang/lib/ASTMatchers/GtestMatchers.cpp [new file with mode: 0644]
clang/unittests/ASTMatchers/CMakeLists.txt
clang/unittests/ASTMatchers/GtestMatchersTest.cpp [new file with mode: 0644]

diff --git a/clang/include/clang/ASTMatchers/GtestMatchers.h b/clang/include/clang/ASTMatchers/GtestMatchers.h
new file mode 100644 (file)
index 0000000..4f8addc
--- /dev/null
@@ -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
+
index cd88d1d..8f700ca 100644 (file)
@@ -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 (file)
index 0000000..317bddd
--- /dev/null
@@ -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
index 09c4290..aa5438c 100644 (file)
@@ -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 (file)
index 0000000..e5abb24
--- /dev/null
@@ -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