diff options
author | Aaron Jacobs <jacobsa@google.com> | 2024-07-24 02:43:05 (GMT) |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2024-07-24 02:43:42 (GMT) |
commit | 352788321faa2f2aa7a098a5a6e53053059b934b (patch) | |
tree | 49cf3d4b0f578a067cdfd90dbaa13a997300f4de | |
parent | 57e107a10ea4ff5d8d31df9e4833f80b414b0dd2 (diff) | |
download | googletest-352788321faa2f2aa7a098a5a6e53053059b934b.zip googletest-352788321faa2f2aa7a098a5a6e53053059b934b.tar.gz googletest-352788321faa2f2aa7a098a5a6e53053059b934b.tar.bz2 |
gmock-actions: make DoAll convert to OnceAction via custom conversions.
Currently it will refuse to become a `OnceAction` if its component sub-actions
have an `Action` conversion operator but don't know about `OnceAction` in
particular because although `Action` is convertible to `OnceAction`, the
compiler won't follow the chain of conversions.
Instead, teach it explicitly that it can always be a `OnceAction` when it can be
an `Action`.
PiperOrigin-RevId: 655393035
Change-Id: Ib205b518ceef5f256627f4b02cd93ec9bd98343b
-rw-r--r-- | googlemock/include/gmock/gmock-actions.h | 62 | ||||
-rw-r--r-- | googlemock/test/gmock-actions_test.cc | 48 |
2 files changed, 98 insertions, 12 deletions
diff --git a/googlemock/include/gmock/gmock-actions.h b/googlemock/include/gmock/gmock-actions.h index 11da899..aa47079 100644 --- a/googlemock/include/gmock/gmock-actions.h +++ b/googlemock/include/gmock/gmock-actions.h @@ -1493,6 +1493,7 @@ class DoAllAction<FinalAction> { // providing a call operator because even with a particular set of arguments // they don't have a fixed return type. + // We support conversion to OnceAction whenever the sub-action does. template <typename R, typename... Args, typename std::enable_if< std::is_convertible<FinalAction, OnceAction<R(Args...)>>::value, @@ -1501,6 +1502,21 @@ class DoAllAction<FinalAction> { return std::move(final_action_); } + // We also support conversion to OnceAction whenever the sub-action supports + // conversion to Action (since any Action can also be a OnceAction). + template < + typename R, typename... Args, + typename std::enable_if< + conjunction< + negation< + std::is_convertible<FinalAction, OnceAction<R(Args...)>>>, + std::is_convertible<FinalAction, Action<R(Args...)>>>::value, + int>::type = 0> + operator OnceAction<R(Args...)>() && { // NOLINT + return Action<R(Args...)>(std::move(final_action_)); + } + + // We support conversion to Action whenever the sub-action does. template < typename R, typename... Args, typename std::enable_if< @@ -1580,16 +1596,16 @@ class DoAllAction<InitialAction, OtherActions...> : Base({}, std::forward<U>(other_actions)...), initial_action_(std::forward<T>(initial_action)) {} - template <typename R, typename... Args, - typename std::enable_if< - conjunction< - // Both the initial action and the rest must support - // conversion to OnceAction. - std::is_convertible< - InitialAction, - OnceAction<void(InitialActionArgType<Args>...)>>, - std::is_convertible<Base, OnceAction<R(Args...)>>>::value, - int>::type = 0> + // We support conversion to OnceAction whenever both the initial action and + // the rest support conversion to OnceAction. + template < + typename R, typename... Args, + typename std::enable_if< + conjunction<std::is_convertible< + InitialAction, + OnceAction<void(InitialActionArgType<Args>...)>>, + std::is_convertible<Base, OnceAction<R(Args...)>>>::value, + int>::type = 0> operator OnceAction<R(Args...)>() && { // NOLINT // Return an action that first calls the initial action with arguments // filtered through InitialActionArgType, then forwards arguments directly @@ -1612,12 +1628,34 @@ class DoAllAction<InitialAction, OtherActions...> }; } + // We also support conversion to OnceAction whenever the initial action + // supports conversion to Action (since any Action can also be a OnceAction). + // + // The remaining sub-actions must also be compatible, but we don't need to + // special case them because the base class deals with them. + template < + typename R, typename... Args, + typename std::enable_if< + conjunction< + negation<std::is_convertible< + InitialAction, + OnceAction<void(InitialActionArgType<Args>...)>>>, + std::is_convertible<InitialAction, + Action<void(InitialActionArgType<Args>...)>>, + std::is_convertible<Base, OnceAction<R(Args...)>>>::value, + int>::type = 0> + operator OnceAction<R(Args...)>() && { // NOLINT + return DoAll( + Action<void(InitialActionArgType<Args>...)>(std::move(initial_action_)), + std::move(static_cast<Base&>(*this))); + } + + // We support conversion to Action whenever both the initial action and the + // rest support conversion to Action. template < typename R, typename... Args, typename std::enable_if< conjunction< - // Both the initial action and the rest must support conversion to - // Action. std::is_convertible<const InitialAction&, Action<void(InitialActionArgType<Args>...)>>, std::is_convertible<const Base&, Action<R(Args...)>>>::value, diff --git a/googlemock/test/gmock-actions_test.cc b/googlemock/test/gmock-actions_test.cc index 59b9458..82c22c3 100644 --- a/googlemock/test/gmock-actions_test.cc +++ b/googlemock/test/gmock-actions_test.cc @@ -1477,6 +1477,54 @@ TEST(DoAll, SupportsTypeErasedActions) { } } +// A DoAll action should be convertible to a OnceAction, even when its component +// sub-actions are user-provided types that define only an Action conversion +// operator. If they supposed being called more than once then they also support +// being called at most once. +TEST(DoAll, ConvertibleToOnceActionWithUserProvidedActionConversion) { + // Simplest case: only one sub-action. + struct CustomFinal final { + operator Action<int()>() { // NOLINT + return Return(17); + } + + operator Action<int(int, char)>() { // NOLINT + return Return(19); + } + }; + + { + OnceAction<int()> action = DoAll(CustomFinal{}); + EXPECT_EQ(17, std::move(action).Call()); + } + + { + OnceAction<int(int, char)> action = DoAll(CustomFinal{}); + EXPECT_EQ(19, std::move(action).Call(0, 0)); + } + + // It should also work with multiple sub-actions. + struct CustomInitial final { + operator Action<void()>() { // NOLINT + return [] {}; + } + + operator Action<void(int, char)>() { // NOLINT + return [] {}; + } + }; + + { + OnceAction<int()> action = DoAll(CustomInitial{}, CustomFinal{}); + EXPECT_EQ(17, std::move(action).Call()); + } + + { + OnceAction<int(int, char)> action = DoAll(CustomInitial{}, CustomFinal{}); + EXPECT_EQ(19, std::move(action).Call(0, 0)); + } +} + // Tests using WithArgs and with an action that takes 1 argument. TEST(WithArgsTest, OneArg) { Action<bool(double x, int n)> a = WithArgs<1>(Invoke(Unary)); // NOLINT |