From a1e255a582377e1006bb88a408ac3f933ba7c916 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 1 Oct 2024 09:32:16 -0700 Subject: [PATCH] Optional(): Add support for std::optional<>-like types lacking bool conversion. PiperOrigin-RevId: 681053268 Change-Id: If80ba667fd4c91340e1405a9691f5ca0350fa9eb --- googlemock/include/gmock/gmock-matchers.h | 33 ++++++--- googlemock/test/gmock-matchers-misc_test.cc | 74 ++++++++++++++++----- 2 files changed, 82 insertions(+), 25 deletions(-) diff --git a/googlemock/include/gmock/gmock-matchers.h b/googlemock/include/gmock/gmock-matchers.h index 26401d53..70e7a764 100644 --- a/googlemock/include/gmock/gmock-matchers.h +++ b/googlemock/include/gmock/gmock-matchers.h @@ -561,6 +561,11 @@ Matcher A(); // and MUST NOT BE USED IN USER CODE!!! namespace internal { +// Used per go/ranked-overloads for dispatching. +struct Rank0 {}; +struct Rank1 : Rank0 {}; +using HighestRank = Rank1; + // If the explanation is not empty, prints it to the ostream. inline void PrintIfNotEmpty(const std::string& explanation, ::std::ostream* os) { @@ -2955,10 +2960,6 @@ class EachMatcher { const M inner_matcher_; }; -// Use go/ranked-overloads for dispatching. -struct Rank0 {}; -struct Rank1 : Rank0 {}; - namespace pair_getters { using std::get; template @@ -3912,6 +3913,21 @@ GTEST_API_ std::string FormatMatcherDescription( bool negation, const char* matcher_name, const std::vector& param_names, const Strings& param_values); +// Overloads to support `OptionalMatcher` being used with a type that either +// supports implicit conversion to bool or a `has_value()` method. +template +auto IsOptionalEngaged(const Optional& optional, + Rank1) -> decltype(!!optional) { + // The use of double-negation here is to preserve historical behavior where + // the matcher used `operator!` rather than directly using `operator bool`. + return !static_cast(!optional); +} +template +auto IsOptionalEngaged(const Optional& optional, + Rank0) -> decltype(!optional.has_value()) { + return optional.has_value(); +} + // Implements a matcher that checks the value of a optional<> type variable. template class OptionalMatcher { @@ -3944,7 +3960,7 @@ class OptionalMatcher { bool MatchAndExplain(Optional optional, MatchResultListener* listener) const override { - if (!optional) { + if (!IsOptionalEngaged(optional, HighestRank())) { *listener << "which is not engaged"; return false; } @@ -5266,9 +5282,10 @@ inline InnerMatcher AllArgs(const InnerMatcher& matcher) { } // Returns a matcher that matches the value of an optional<> type variable. -// The matcher implementation only uses '!arg' and requires that the optional<> -// type has a 'value_type' member type and that '*arg' is of type 'value_type' -// and is printable using 'PrintToString'. It is compatible with +// The matcher implementation only uses '!arg' (or 'arg.has_value()' if '!arg` +// isn't a valid expression) and requires that the optional<> type has a +// 'value_type' member type and that '*arg' is of type 'value_type' and is +// printable using 'PrintToString'. It is compatible with // std::optional/std::experimental::optional. // Note that to compare an optional type variable against nullopt you should // use Eq(nullopt) and not Eq(Optional(nullopt)). The latter implies that the diff --git a/googlemock/test/gmock-matchers-misc_test.cc b/googlemock/test/gmock-matchers-misc_test.cc index 9ac24586..ac976dd3 100644 --- a/googlemock/test/gmock-matchers-misc_test.cc +++ b/googlemock/test/gmock-matchers-misc_test.cc @@ -674,6 +674,8 @@ TEST_P(MatcherTupleTestP, ExplainsMatchFailure) { // explanation. } +#if GTEST_HAS_TYPED_TEST + // Sample optional type implementation with minimal requirements for use with // Optional matcher. template @@ -691,38 +693,76 @@ class SampleOptional { bool has_value_; }; -TEST(OptionalTest, DescribesSelf) { - const Matcher> m = Optional(Eq(1)); +// Sample optional type implementation with alternative minimal requirements for +// use with Optional matcher. In particular, while it doesn't have a bool +// conversion operator, it does have a has_value() method. +template +class SampleOptionalWithoutBoolConversion { + public: + using value_type = T; + explicit SampleOptionalWithoutBoolConversion(T value) + : value_(std::move(value)), has_value_(true) {} + SampleOptionalWithoutBoolConversion() : value_(), has_value_(false) {} + bool has_value() const { return has_value_; } + const T& operator*() const { return value_; } + + private: + T value_; + bool has_value_; +}; + +template +class OptionalTest : public testing::Test {}; + +using OptionalTestTypes = + testing::Types, + SampleOptionalWithoutBoolConversion>; + +TYPED_TEST_SUITE(OptionalTest, OptionalTestTypes); + +TYPED_TEST(OptionalTest, DescribesSelf) { + const Matcher m = Optional(Eq(1)); EXPECT_EQ("value is equal to 1", Describe(m)); } -TEST(OptionalTest, ExplainsSelf) { - const Matcher> m = Optional(Eq(1)); - EXPECT_EQ("whose value 1 matches", Explain(m, SampleOptional(1))); - EXPECT_EQ("whose value 2 doesn't match", Explain(m, SampleOptional(2))); +TYPED_TEST(OptionalTest, ExplainsSelf) { + const Matcher m = Optional(Eq(1)); + EXPECT_EQ("whose value 1 matches", Explain(m, TypeParam(1))); + EXPECT_EQ("whose value 2 doesn't match", Explain(m, TypeParam(2))); } -TEST(OptionalTest, MatchesNonEmptyOptional) { - const Matcher> m1 = Optional(1); - const Matcher> m2 = Optional(Eq(2)); - const Matcher> m3 = Optional(Lt(3)); - SampleOptional opt(1); +TYPED_TEST(OptionalTest, MatchesNonEmptyOptional) { + const Matcher m1 = Optional(1); + const Matcher m2 = Optional(Eq(2)); + const Matcher m3 = Optional(Lt(3)); + TypeParam opt(1); EXPECT_TRUE(m1.Matches(opt)); EXPECT_FALSE(m2.Matches(opt)); EXPECT_TRUE(m3.Matches(opt)); } -TEST(OptionalTest, DoesNotMatchNullopt) { - const Matcher> m = Optional(1); - SampleOptional empty; +TYPED_TEST(OptionalTest, DoesNotMatchNullopt) { + const Matcher m = Optional(1); + TypeParam empty; EXPECT_FALSE(m.Matches(empty)); } -TEST(OptionalTest, WorksWithMoveOnly) { - Matcher>> m = Optional(Eq(nullptr)); - EXPECT_TRUE(m.Matches(SampleOptional>(nullptr))); +template +class MoveOnlyOptionalTest : public testing::Test {}; + +using MoveOnlyOptionalTestTypes = + testing::Types>, + SampleOptionalWithoutBoolConversion>>; + +TYPED_TEST_SUITE(MoveOnlyOptionalTest, MoveOnlyOptionalTestTypes); + +TYPED_TEST(MoveOnlyOptionalTest, WorksWithMoveOnly) { + Matcher m = Optional(Eq(nullptr)); + EXPECT_TRUE(m.Matches(TypeParam(nullptr))); } +#endif // GTEST_HAS_TYPED_TEST + class SampleVariantIntString { public: SampleVariantIntString(int i) : i_(i), has_int_(true) {}