diff --git a/common/error_test.cpp b/common/error_test.cpp index 15880c471061..428f339078bc 100644 --- a/common/error_test.cpp +++ b/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, TypeParam>; + + TestErrorOr i(42); + EXPECT_THAT(i, IsSuccess(VariantWith(Eq(42)))); + + TestErrorOr f(0.42F); + EXPECT_THAT(f, IsSuccess(VariantWith(Eq(0.42F)))); +} + TYPED_TEST(ErrorOrTest, ReturnIfErrorNoError) { using TestErrorOr = ErrorOr; auto result = []() -> TestErrorOr { diff --git a/common/error_test_helpers.h b/common/error_test_helpers.h index 7cae1b5b9aa6..e34377dbadf6 100644 --- a/common/error_test_helpers.h +++ b/common/error_test_helpers.h @@ -48,43 +48,68 @@ class IsError { ::testing::Matcher matcher_; }; -// Matches the value for a non-error state of `ErrorOr`. For example: -// EXPECT_THAT(my_result, IsSuccess(Eq(3))); -template -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 +class IsSuccessMatcherImpl + : public ::testing::MatcherInterface&> { public: - // NOLINTNEXTLINE(readability-identifier-naming) - using is_gtest_matcher = void; + explicit IsSuccessMatcherImpl(const ::testing::Matcher& matcher) + : matcher_(matcher) {} - explicit IsSuccessMatcher(InnerMatcher matcher) - : matcher_(std::move(matcher)) {} - - template auto MatchAndExplain(const ErrorOr& result, - ::testing::MatchResultListener* listener) const -> bool { + ::testing::MatchResultListener* listener) const + -> bool override { if (result.ok()) { - return ::testing::Matcher(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 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 +class IsSuccessMatcher { + public: + explicit IsSuccessMatcher(InnerMatcher matcher) + : matcher_(std::move(matcher)) {} + + template + // NOLINTNEXTLINE(google-explicit-constructor): Required for matcher APIs. + operator ::testing::Matcher&>() const { + return ::testing::Matcher&>( + new IsSuccessMatcherImpl( + ::testing::SafeMatcherCast(matcher_))); } private: InnerMatcher matcher_; }; -// Wraps `IsSuccessMatcher` for the inner matcher deduction. +// Returns a matcher the value for a non-error state of `ErrorOr`. +// +// For example: +// EXPECT_THAT(my_result, IsSuccess(Eq(3))); template auto IsSuccess(InnerMatcher matcher) -> IsSuccessMatcher { return IsSuccessMatcher(matcher);