// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_TEST_GMOCK_EXPECTED_SUPPORT_H_ #define BASE_TEST_GMOCK_EXPECTED_SUPPORT_H_ #include #include #include #include #include "base/strings/strcat.h" #include "base/strings/to_string.h" #include "base/types/expected.h" #include "base/types/expected_internal.h" #include "base/types/expected_macros.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace base::test { namespace internal { // `HasVoidValueType` is true iff `T` satisfies // `base::internal::IsExpected` and `T`'s `value_type` is `void`. template constexpr bool HasVoidValueType = false; template constexpr bool HasVoidValueType>> = std::is_void_v::value_type>; // Implementation for matcher `HasValue`. class HasValueMatcher { public: HasValueMatcher() = default; template operator ::testing::Matcher() const { // NOLINT return ::testing::Matcher(new Impl()); } private: // Reject instantiation with types that do not satisfy // `base::internal::IsExpected`. template class Impl { static_assert(base::internal::IsExpected, "Must be used with base::expected"); }; template class Impl>> : public ::testing::MatcherInterface { public: Impl() = default; void DescribeTo(std::ostream* os) const override { *os << "is an 'expected' type with a value"; } void DescribeNegationTo(std::ostream* os) const override { *os << "is an 'expected' type with an error"; } bool MatchAndExplain( T actual_value, ::testing::MatchResultListener* listener) const override { if (!actual_value.has_value()) { *listener << "which has the error " << ToString(actual_value.error()); } return actual_value.has_value(); } }; }; // Implementation for matcher `ValueIs`. template class ValueIsMatcher { public: explicit ValueIsMatcher(T matcher) : matcher_(std::move(matcher)) {} template operator ::testing::Matcher() const { // NOLINT return ::testing::Matcher(new Impl(matcher_)); } private: // Reject instantiation with types that do not satisfy // `base::internal::IsExpected && !HasVoidValueType`. template class Impl { static_assert(base::internal::IsExpected, "Must be used with base::expected"); static_assert(!HasVoidValueType, "expected object must have non-void value type"); }; template class Impl< U, std::enable_if_t && !HasVoidValueType>> : public ::testing::MatcherInterface { public: explicit Impl(const T& matcher) : matcher_(::testing::SafeMatcherCast(matcher)) {} void DescribeTo(std::ostream* os) const override { *os << "is an 'expected' type with a value which "; matcher_.DescribeTo(os); } void DescribeNegationTo(std::ostream* os) const override { *os << "is an 'expected' type with an error or a value which "; matcher_.DescribeNegationTo(os); } bool MatchAndExplain( U actual_value, ::testing::MatchResultListener* listener) const override { if (!actual_value.has_value()) { *listener << "which has the error " << ToString(actual_value.error()); return false; } ::testing::StringMatchResultListener inner_listener; const bool match = matcher_.MatchAndExplain(actual_value.value(), &inner_listener); const std::string explanation = inner_listener.str(); if (!explanation.empty()) { *listener << "which has the value " << ToString(actual_value.value()) << ", " << explanation; } return match; } private: using V = typename std::remove_cvref_t::value_type; const ::testing::Matcher matcher_; }; const T matcher_; }; // Implementation for matcher `ErrorIs`. template class ErrorIsMatcher { public: explicit ErrorIsMatcher(T matcher) : matcher_(std::move(matcher)) {} template operator ::testing::Matcher() const { // NOLINT return ::testing::Matcher(new Impl(matcher_)); } private: // Reject instantiation with types that do not satisfy // `base::internal::IsExpected`. template class Impl { static_assert(base::internal::IsExpected, "Must be used with base::expected"); }; template class Impl>> : public ::testing::MatcherInterface { public: explicit Impl(const T& matcher) : matcher_(::testing::SafeMatcherCast(matcher)) {} void DescribeTo(std::ostream* os) const override { *os << "is an 'expected' type with an error which "; matcher_.DescribeTo(os); } void DescribeNegationTo(std::ostream* os) const override { *os << "is an 'expected' type with a value or an error which "; matcher_.DescribeNegationTo(os); } bool MatchAndExplain( U actual_value, ::testing::MatchResultListener* listener) const override { if (actual_value.has_value()) { if constexpr (HasVoidValueType) { *listener << "which has a value"; } else { *listener << "which has the value " << ToString(actual_value.value()); } return false; } ::testing::StringMatchResultListener inner_listener; const bool match = matcher_.MatchAndExplain(actual_value.error(), &inner_listener); const std::string explanation = inner_listener.str(); if (!explanation.empty()) { *listener << "which has the error " << ToString(actual_value.error()) << ", " << explanation; } return match; } private: using E = typename std::remove_cvref_t::error_type; const ::testing::Matcher matcher_; }; private: const T matcher_; }; } // namespace internal // Returns a gMock matcher that matches an `expected` which has a value. inline internal::HasValueMatcher HasValue() { return internal::HasValueMatcher(); } // Returns a gMock matcher that matches an `expected` which has a non-void // value which matches the inner matcher. template inline internal::ValueIsMatcher> ValueIs(T&& matcher) { return internal::ValueIsMatcher>( std::forward(matcher)); } // Returns a gMock matcher that matches an `expected` which has an error // which matches the inner matcher. template inline internal::ErrorIsMatcher> ErrorIs(T&& matcher) { return internal::ErrorIsMatcher>( std::forward(matcher)); } } // namespace base::test // Executes an expression that returns an `expected` or some subclass // thereof, and assigns the contained `T` to `lhs` if the result is a value. If // the result is an error, generates a test failure and returns from the current // function, which must have a `void` return type. For more usage examples and // caveats, see the documentation for `ASSIGN_OR_RETURN`. // // Example: Declaring and initializing a new value: // ASSERT_OK_AND_ASSIGN(ValueType value, MaybeGetValue(arg)); // // Example: Assigning to an existing value: // ValueType value; // ASSERT_OK_AND_ASSIGN(value, MaybeGetValue(arg)); #define ASSERT_OK_AND_ASSIGN(lhs, rexpr) \ ASSIGN_OR_RETURN(lhs, rexpr, [](const auto& e) { \ return GTEST_MESSAGE_( \ base::StrCat({#rexpr, " returned error: ", base::ToString(e)}) \ .c_str(), \ ::testing::TestPartResult::kFatalFailure); \ }) namespace base { template void PrintTo(const expected& expected, ::std::ostream* os) { *os << expected.ToString(); } template void PrintTo(const ok& a, ::std::ostream* os) { *os << a.ToString(); } template void PrintTo(const unexpected& a, ::std::ostream* os) { *os << a.ToString(); } } // namespace base #endif // BASE_TEST_GMOCK_EXPECTED_SUPPORT_H_