From 352788321faa2f2aa7a098a5a6e53053059b934b Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Tue, 23 Jul 2024 19:43:05 -0700 Subject: [PATCH] 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 --- googlemock/include/gmock/gmock-actions.h | 62 +++++++++++++++++++----- 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 11da8990..aa470799 100644 --- a/googlemock/include/gmock/gmock-actions.h +++ b/googlemock/include/gmock/gmock-actions.h @@ -1493,6 +1493,7 @@ class DoAllAction { // 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 >::value, @@ -1501,6 +1502,21 @@ class DoAllAction { 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>>, + std::is_convertible>>::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + return Action(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 : Base({}, std::forward(other_actions)...), initial_action_(std::forward(initial_action)) {} - template ...)>>, - std::is_convertible>>::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>>::value, + int>::type = 0> operator OnceAction() && { // 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 }; } + // 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...)>>, + std::is_convertible>>::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + return DoAll( + Action...)>(std::move(initial_action_)), + std::move(static_cast(*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...)>>, std::is_convertible>>::value, diff --git a/googlemock/test/gmock-actions_test.cc b/googlemock/test/gmock-actions_test.cc index 59b94583..82c22c31 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() { // NOLINT + return Return(17); + } + + operator Action() { // NOLINT + return Return(19); + } + }; + + { + OnceAction action = DoAll(CustomFinal{}); + EXPECT_EQ(17, std::move(action).Call()); + } + + { + OnceAction 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() { // NOLINT + return [] {}; + } + + operator Action() { // NOLINT + return [] {}; + } + }; + + { + OnceAction action = DoAll(CustomInitial{}, CustomFinal{}); + EXPECT_EQ(17, std::move(action).Call()); + } + + { + OnceAction 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 a = WithArgs<1>(Invoke(Unary)); // NOLINT