瀏覽代碼

Rework the `IsSuccess` matcher to be fully polymorphic (#5981)

Previously, this matcher mostly worked, but the `DescribeTo` functions
wouldn't compile when another polymorphic matcher was nested to match
the value.

The updated code uses the same polymorphic matcher design as used by
`Not` and others in Google Test itself.

I've added a test that uses `VariantWith` to nest matchers more deeply
with `IsSuccess`. This test doesn't compile prior to this change.
Chandler Carruth 8 月之前
父節點
當前提交
74016d47f9
共有 2 個文件被更改,包括 53 次插入16 次删除
  1. 12 0
      common/error_test.cpp
  2. 41 16
      common/error_test_helpers.h

+ 12 - 0
common/error_test.cpp

@@ -19,6 +19,7 @@ using ::Carbon::Testing::IsError;
 using ::Carbon::Testing::IsSuccess;
 using ::testing::_;
 using ::testing::Eq;
+using ::testing::VariantWith;
 
 TEST(ErrorTest, Error) {
   Error err("test");
@@ -158,6 +159,17 @@ TYPED_TEST(ErrorOrTest, UnprintableValue) {
   EXPECT_THAT(error, IsError(this->ErrorStr()));
 }
 
+// Note that this is more of a test of `IsSuccess` than `ErrorOr` itself.
+TYPED_TEST(ErrorOrTest, NestedMatching) {
+  using TestErrorOr = ErrorOr<std::variant<int, float>, TypeParam>;
+
+  TestErrorOr i(42);
+  EXPECT_THAT(i, IsSuccess(VariantWith<int>(Eq(42))));
+
+  TestErrorOr f(0.42F);
+  EXPECT_THAT(f, IsSuccess(VariantWith<float>(Eq(0.42F))));
+}
+
 TYPED_TEST(ErrorOrTest, ReturnIfErrorNoError) {
   using TestErrorOr = ErrorOr<Success, TypeParam>;
   auto result = []() -> TestErrorOr {

+ 41 - 16
common/error_test_helpers.h

@@ -48,43 +48,68 @@ class IsError {
   ::testing::Matcher<std::string> matcher_;
 };
 
-// Matches the value for a non-error state of `ErrorOr<T>`. For example:
-//   EXPECT_THAT(my_result, IsSuccess(Eq(3)));
-template <typename InnerMatcher>
-class IsSuccessMatcher {
+// Implementation of a success matcher for a specific `T` and `ErrorT` in an
+// `ErrorOr`. Supports a nested matcher for the `T` value.
+template <typename T, typename ErrorT>
+class IsSuccessMatcherImpl
+    : public ::testing::MatcherInterface<const ErrorOr<T, ErrorT>&> {
  public:
-  // NOLINTNEXTLINE(readability-identifier-naming)
-  using is_gtest_matcher = void;
-
-  explicit IsSuccessMatcher(InnerMatcher matcher)
-      : matcher_(std::move(matcher)) {}
+  explicit IsSuccessMatcherImpl(const ::testing::Matcher<T>& matcher)
+      : matcher_(matcher) {}
 
-  template <typename T, typename ErrorT>
   auto MatchAndExplain(const ErrorOr<T, ErrorT>& result,
-                       ::testing::MatchResultListener* listener) const -> bool {
+                       ::testing::MatchResultListener* listener) const
+      -> bool override {
     if (result.ok()) {
-      return ::testing::Matcher<T>(matcher_).MatchAndExplain(*result, listener);
+      return matcher_.MatchAndExplain(*result, listener);
     } else {
       *listener << "is an error with `" << result.error() << "`";
       return false;
     }
   }
 
-  auto DescribeTo(std::ostream* os) const -> void {
+  auto DescribeTo(std::ostream* os) const -> void override {
     *os << "is a success and matches ";
     matcher_.DescribeTo(os);
   }
 
-  auto DescribeNegationTo(std::ostream* os) const -> void {
+  auto DescribeNegationTo(std::ostream* os) const -> void override {
     *os << "is an error or does not match ";
-    matcher_.DescribeTo(os);
+    matcher_.DescribeNegationTo(os);
+  }
+
+ private:
+  ::testing::Matcher<T> matcher_;
+};
+
+// Polymorphic match implementation for GoogleTest.
+//
+// To support matching arbitrary types that `InnerMatcher` can also match, this
+// itself must match arbitrary types. This is accomplished by not being a
+// matcher itself, but by being convertible into matchers for any particular
+// `ErrorOr`.
+template <typename InnerMatcher>
+class IsSuccessMatcher {
+ public:
+  explicit IsSuccessMatcher(InnerMatcher matcher)
+      : matcher_(std::move(matcher)) {}
+
+  template <typename T, typename ErrorT>
+  // NOLINTNEXTLINE(google-explicit-constructor): Required for matcher APIs.
+  operator ::testing::Matcher<const ErrorOr<T, ErrorT>&>() const {
+    return ::testing::Matcher<const ErrorOr<T, ErrorT>&>(
+        new IsSuccessMatcherImpl<T, ErrorT>(
+            ::testing::SafeMatcherCast<T>(matcher_)));
   }
 
  private:
   InnerMatcher matcher_;
 };
 
-// Wraps `IsSuccessMatcher` for the inner matcher deduction.
+// Returns a matcher the value for a non-error state of `ErrorOr<T>`.
+//
+// For example:
+//   EXPECT_THAT(my_result, IsSuccess(Eq(3)));
 template <typename InnerMatcher>
 auto IsSuccess(InnerMatcher matcher) -> IsSuccessMatcher<InnerMatcher> {
   return IsSuccessMatcher<InnerMatcher>(matcher);