[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