Add support for move-only and &&-qualified actions in WillOnce.

This provides a type-safe way for an action to express that it wants to be
called only once, or to capture move-only objects. It is a generalization of
the type system-evading hack in ByMove, with the improvement that it works for
_any_ action (including user-defined ones), and correctly expresses that the
action can only be used with WillOnce. I'll make existing actions benefit in a
future commit.

PiperOrigin-RevId: 440496139
Change-Id: I4145d191cca5655995ef41360bb126c123cb41d3
This commit is contained in:
Abseil Team 2022-04-08 18:39:39 -07:00 committed by Copybara-Service
parent 5f467ec04d
commit a1cc8c5519
4 changed files with 584 additions and 35 deletions

View File

@ -248,7 +248,9 @@ EXPECT_CALL(my_mock, GetNumber())
.WillOnce(Return(3));
```
The `WillOnce` clause can be used any number of times on an expectation.
The `WillOnce` clause can be used any number of times on an expectation. Unlike
`WillRepeatedly`, the action fed to each `WillOnce` call will be called at most
once, so may be a move-only type and/or have an `&&`-qualified call operator.
#### WillRepeatedly {#EXPECT_CALL.WillRepeatedly}

View File

@ -262,9 +262,65 @@ GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(double, 0);
#undef GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_
// Simple two-arg form of std::disjunction.
template <typename P, typename Q>
using disjunction = typename ::std::conditional<P::value, P, Q>::type;
// Partial implementations of metaprogramming types from the standard library
// not available in C++11.
template <typename P>
struct negation
// NOLINTNEXTLINE
: std::integral_constant<bool, bool(!P::value)> {};
// Base case: with zero predicates the answer is always true.
template <typename...>
struct conjunction : std::true_type {};
// With a single predicate, the answer is that predicate.
template <typename P1>
struct conjunction<P1> : P1 {};
// With multiple predicates the answer is the first predicate if that is false,
// and we recurse otherwise.
template <typename P1, typename... Ps>
struct conjunction<P1, Ps...>
: std::conditional<bool(P1::value), conjunction<Ps...>, P1>::type {};
template <typename...>
struct disjunction : std::false_type {};
template <typename P1>
struct disjunction<P1> : P1 {};
template <typename P1, typename... Ps>
struct disjunction<P1, Ps...>
// NOLINTNEXTLINE
: std::conditional<!bool(P1::value), disjunction<Ps...>, P1>::type {};
template <typename...>
using void_t = void;
// Like std::invoke_result_t from C++17, but works only for objects with call
// operators (not e.g. member function pointers, which we don't need specific
// support for in OnceAction because std::function deals with them).
template <typename F, typename... Args>
using call_result_t = decltype(std::declval<F>()(std::declval<Args>()...));
template <typename Void, typename R, typename F, typename... Args>
struct is_callable_r_impl : std::false_type {};
// Specialize the struct for those template arguments where call_result_t is
// well-formed. When it's not, the generic template above is chosen, resulting
// in std::false_type.
template <typename R, typename F, typename... Args>
struct is_callable_r_impl<void_t<call_result_t<F, Args...>>, R, F, Args...>
: std::conditional<
std::is_same<R, void>::value, //
std::true_type, //
std::is_convertible<call_result_t<F, Args...>, R>>::type {};
// Like std::is_invocable_r from C++17, but works only for objects with call
// operators. See the note on call_result_t.
template <typename R, typename F, typename... Args>
using is_callable_r = is_callable_r_impl<void, R, F, Args...>;
} // namespace internal
@ -596,6 +652,213 @@ inline PolymorphicAction<Impl> MakePolymorphicAction(const Impl& impl) {
namespace internal {
template <typename F>
class TypedExpectation;
// Specialized for function types below.
template <typename F>
class OnceAction;
// An action that can only be used once.
//
// This is what is accepted by WillOnce, which doesn't require the underlying
// action to be copy-constructible (only move-constructible), and promises to
// invoke it as an rvalue reference. This allows the action to work with
// move-only types like std::move_only_function in a type-safe manner.
//
// For example:
//
// // Assume we have some API that needs to accept a unique pointer to some
// // non-copyable object Foo.
// void AcceptUniquePointer(std::unique_ptr<Foo> foo);
//
// // We can define an action that provides a Foo to that API. Because It
// // has to give away its unique pointer, it must not be called more than
// // once, so its call operator is &&-qualified.
// struct ProvideFoo {
// std::unique_ptr<Foo> foo;
//
// void operator()() && {
// AcceptUniquePointer(std::move(Foo));
// }
// };
//
// // This action can be used with WillOnce.
// EXPECT_CALL(mock, Call)
// .WillOnce(ProvideFoo{std::make_unique<Foo>(...)});
//
// // But a call to WillRepeatedly will fail to compile. This is correct,
// // since the action cannot correctly be used repeatedly.
// EXPECT_CALL(mock, Call)
// .WillRepeatedly(ProvideFoo{std::make_unique<Foo>(...)});
//
// A less-contrived example would be an action that returns an arbitrary type,
// whose &&-qualified call operator is capable of dealing with move-only types.
template <typename Result, typename... Args>
class OnceAction<Result(Args...)> final {
private:
// True iff we can use the given callable type (or lvalue reference) directly
// via ActionAdaptor.
template <typename Callable>
using IsDirectlyCompatible = internal::conjunction<
// It must be possible to capture the callable in ActionAdaptor.
std::is_constructible<typename std::decay<Callable>::type, Callable>,
// The callable must be compatible with our signature.
internal::is_callable_r<Result, typename std::decay<Callable>::type,
Args...>>;
// True iff we can use the given callable type via ActionAdaptor once we
// ignore incoming arguments.
template <typename Callable>
using IsCompatibleAfterIgnoringArguments = internal::conjunction<
// It must be possible to capture the callable in a lambda.
std::is_constructible<typename std::decay<Callable>::type, Callable>,
// The callable must be invocable with zero arguments, returning something
// convertible to Result.
internal::is_callable_r<Result, typename std::decay<Callable>::type>>;
public:
// Construct from a callable that is directly compatible with our mocked
// signature: it accepts our function type's arguments and returns something
// convertible to our result type.
template <typename Callable,
typename std::enable_if<
internal::conjunction<
// Teach clang on macOS that we're not talking about a
// copy/move constructor here. Otherwise it gets confused
// when checking the is_constructible requirement of our
// traits above.
internal::negation<std::is_same<
OnceAction, typename std::decay<Callable>::type>>,
IsDirectlyCompatible<Callable>> //
::value,
int>::type = 0>
OnceAction(Callable&& callable) // NOLINT
: action_(ActionAdaptor<typename std::decay<Callable>::type>(
{}, std::forward<Callable>(callable))) {}
// As above, but for a callable that ignores the mocked function's arguments.
template <typename Callable,
typename std::enable_if<
internal::conjunction<
// Teach clang on macOS that we're not talking about a
// copy/move constructor here. Otherwise it gets confused
// when checking the is_constructible requirement of our
// traits above.
internal::negation<std::is_same<
OnceAction, typename std::decay<Callable>::type>>,
// Exclude callables for which the overload above works.
// We'd rather provide the arguments if possible.
internal::negation<IsDirectlyCompatible<Callable>>,
IsCompatibleAfterIgnoringArguments<Callable>>::value,
int>::type = 0>
OnceAction(Callable&& callable) // NOLINT
// Call the constructor above with a callable
// that ignores the input arguments.
: OnceAction(IgnoreIncomingArguments<typename std::decay<Callable>::type>{
std::forward<Callable>(callable)}) {}
// A fallback constructor for anything that is convertible to Action, for use
// with legacy actions that uses older styles like implementing
// ActionInterface or a conversion operator to Action. Modern code should
// implement a call operator with appropriate restrictions.
template <typename T,
typename std::enable_if<
internal::conjunction<
// Teach clang on macOS that we're not talking about a
// copy/move constructor here. Otherwise it gets confused
// when checking the is_constructible requirement of our
// traits above.
internal::negation<
std::is_same<OnceAction, typename std::decay<T>::type>>,
// Exclude the overloads above, which we want to take
// precedence.
internal::negation<IsDirectlyCompatible<T>>,
internal::negation<IsCompatibleAfterIgnoringArguments<T>>,
// It must be possible to turn the object into an action of
// the appropriate type.
std::is_convertible<T, Action<Result(Args...)>> //
>::value,
int>::type = 0>
OnceAction(T&& action) : action_(std::forward<T>(action)) {} // NOLINT
// We are naturally copyable because we store only an Action, but semantically
// we should not be copyable.
OnceAction(const OnceAction&) = delete;
OnceAction& operator=(const OnceAction&) = delete;
OnceAction(OnceAction&&) = default;
private:
// Allow TypedExpectation::WillOnce to use our type-unsafe API below.
friend class TypedExpectation<Result(Args...)>;
// An adaptor that wraps a callable that is compatible with our signature and
// being invoked as an rvalue reference so that it can be used as an
// Action. This throws away type safety, but that's fine because this is only
// used by WillOnce, which we know calls at most once.
template <typename Callable>
class ActionAdaptor final {
public:
// A tag indicating that the (otherwise universal) constructor is accepting
// the callable itself, instead of e.g. stealing calls for the move
// constructor.
struct CallableTag final {};
template <typename F>
explicit ActionAdaptor(CallableTag, F&& callable)
: callable_(std::make_shared<Callable>(std::forward<F>(callable))) {}
// Rather than explicitly returning Result, we return whatever the wrapped
// callable returns. This allows for compatibility with existing uses like
// the following, when the mocked function returns void:
//
// EXPECT_CALL(mock_fn_, Call)
// .WillOnce([&] {
// [...]
// return 0;
// });
//
// This works with Action since such a callable can be turned into
// std::function<void()>. If we use an explicit return type of Result here
// then it *doesn't* work with OnceAction, because we'll get a "void
// function should not return a value" error.
//
// We need not worry about incompatible result types because the SFINAE on
// OnceAction already checks this for us. std::is_invocable_r_v itself makes
// the same allowance for void result types.
template <typename... ArgRefs>
internal::call_result_t<Callable, ArgRefs...> operator()(
ArgRefs&&... args) const {
return std::move(*callable_)(std::forward<ArgRefs>(args)...);
}
private:
// We must put the callable on the heap so that we are copyable, which
// Action needs.
std::shared_ptr<Callable> callable_;
};
// An adaptor that makes a callable that accepts zero arguments callable with
// our mocked arguments.
template <typename Callable>
struct IgnoreIncomingArguments {
internal::call_result_t<Callable> operator()(Args&&...) {
return std::move(callable)();
}
Callable callable;
};
// Return an Action that calls the underlying callable in a type-safe manner.
// The action's Perform method must be called at most once.
//
// This is the transition from a type-safe API to a type-unsafe one, since
// "must be called at most once" is no longer reflecting in the type system.
Action<Result(Args...)> ReleaseAction() && { return std::move(action_); }
Action<Result(Args...)> action_;
};
// Helper struct to specialize ReturnAction to execute a move instead of a copy
// on return. Useful for move-only types, but could be used on any type.
template <typename T>

View File

@ -992,14 +992,16 @@ class TypedExpectation : public ExpectationBase {
return After(s1, s2, s3, s4).After(s5);
}
// Implements the .WillOnce() clause.
TypedExpectation& WillOnce(const Action<F>& action) {
// Implements the .WillOnce() clause for copyable actions.
TypedExpectation& WillOnce(OnceAction<F> once_action) {
ExpectSpecProperty(last_clause_ <= kWillOnce,
".WillOnce() cannot appear after "
".WillRepeatedly() or .RetiresOnSaturation().");
last_clause_ = kWillOnce;
untyped_actions_.push_back(new Action<F>(action));
untyped_actions_.push_back(
new Action<F>(std::move(once_action).ReleaseAction()));
if (!cardinality_specified()) {
set_cardinality(Exactly(static_cast<int>(untyped_actions_.size())));
}

View File

@ -55,37 +55,170 @@
#include "gtest/gtest-spi.h"
#include "gtest/gtest.h"
namespace testing {
namespace {
using ::testing::_;
using ::testing::Action;
using ::testing::ActionInterface;
using ::testing::Assign;
using ::testing::ByMove;
using ::testing::ByRef;
using ::testing::DefaultValue;
using ::testing::DoAll;
using ::testing::DoDefault;
using ::testing::IgnoreResult;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::MakePolymorphicAction;
using ::testing::PolymorphicAction;
using ::testing::Return;
using ::testing::ReturnNew;
using ::testing::ReturnNull;
using ::testing::ReturnRef;
using ::testing::ReturnRefOfCopy;
using ::testing::ReturnRoundRobin;
using ::testing::SetArgPointee;
using ::testing::SetArgumentPointee;
using ::testing::Unused;
using ::testing::WithArgs;
using ::testing::internal::BuiltInDefaultValue;
#if !GTEST_OS_WINDOWS_MOBILE
using ::testing::SetErrnoAndReturn;
#endif
TEST(TypeTraits, Negation) {
// Direct use with std types.
static_assert(std::is_base_of<std::false_type,
internal::negation<std::true_type>>::value,
"");
static_assert(std::is_base_of<std::true_type,
internal::negation<std::false_type>>::value,
"");
// With other types that fit the requirement of a value member that is
// convertible to bool.
static_assert(std::is_base_of<
std::true_type,
internal::negation<std::integral_constant<int, 0>>>::value,
"");
static_assert(std::is_base_of<
std::false_type,
internal::negation<std::integral_constant<int, 1>>>::value,
"");
static_assert(std::is_base_of<
std::false_type,
internal::negation<std::integral_constant<int, -1>>>::value,
"");
}
// Weird false/true types that aren't actually bool constants (but should still
// be legal according to [meta.logical] because `bool(T::value)` is valid), are
// distinct from std::false_type and std::true_type, and are distinct from other
// instantiations of the same template.
//
// These let us check finicky details mandated by the standard like
// "std::conjunction should evaluate to a type that inherits from the first
// false-y input".
template <int>
struct MyFalse : std::integral_constant<int, 0> {};
template <int>
struct MyTrue : std::integral_constant<int, -1> {};
TEST(TypeTraits, Conjunction) {
// Base case: always true.
static_assert(std::is_base_of<std::true_type, internal::conjunction<>>::value,
"");
// One predicate: inherits from that predicate, regardless of value.
static_assert(
std::is_base_of<MyFalse<0>, internal::conjunction<MyFalse<0>>>::value,
"");
static_assert(
std::is_base_of<MyTrue<0>, internal::conjunction<MyTrue<0>>>::value, "");
// Multiple predicates, with at least one false: inherits from that one.
static_assert(
std::is_base_of<MyFalse<1>, internal::conjunction<MyTrue<0>, MyFalse<1>,
MyTrue<2>>>::value,
"");
static_assert(
std::is_base_of<MyFalse<1>, internal::conjunction<MyTrue<0>, MyFalse<1>,
MyFalse<2>>>::value,
"");
// Short circuiting: in the case above, additional predicates need not even
// define a value member.
struct Empty {};
static_assert(
std::is_base_of<MyFalse<1>, internal::conjunction<MyTrue<0>, MyFalse<1>,
Empty>>::value,
"");
// All predicates true: inherits from the last.
static_assert(
std::is_base_of<MyTrue<2>, internal::conjunction<MyTrue<0>, MyTrue<1>,
MyTrue<2>>>::value,
"");
}
TEST(TypeTraits, Disjunction) {
// Base case: always false.
static_assert(
std::is_base_of<std::false_type, internal::disjunction<>>::value, "");
// One predicate: inherits from that predicate, regardless of value.
static_assert(
std::is_base_of<MyFalse<0>, internal::disjunction<MyFalse<0>>>::value,
"");
static_assert(
std::is_base_of<MyTrue<0>, internal::disjunction<MyTrue<0>>>::value, "");
// Multiple predicates, with at least one true: inherits from that one.
static_assert(
std::is_base_of<MyTrue<1>, internal::disjunction<MyFalse<0>, MyTrue<1>,
MyFalse<2>>>::value,
"");
static_assert(
std::is_base_of<MyTrue<1>, internal::disjunction<MyFalse<0>, MyTrue<1>,
MyTrue<2>>>::value,
"");
// Short circuiting: in the case above, additional predicates need not even
// define a value member.
struct Empty {};
static_assert(
std::is_base_of<MyTrue<1>, internal::disjunction<MyFalse<0>, MyTrue<1>,
Empty>>::value,
"");
// All predicates false: inherits from the last.
static_assert(
std::is_base_of<MyFalse<2>, internal::disjunction<MyFalse<0>, MyFalse<1>,
MyFalse<2>>>::value,
"");
}
TEST(TypeTraits, IsInvocableRV) {
struct C {
int operator()() const { return 0; }
void operator()(int) & {}
std::string operator()(int) && { return ""; };
};
// The first overload is callable for const and non-const rvalues and lvalues.
// It can be used to obtain an int, void, or anything int is convertible too.
static_assert(internal::is_callable_r<int, C>::value, "");
static_assert(internal::is_callable_r<int, C&>::value, "");
static_assert(internal::is_callable_r<int, const C>::value, "");
static_assert(internal::is_callable_r<int, const C&>::value, "");
static_assert(internal::is_callable_r<void, C>::value, "");
static_assert(internal::is_callable_r<char, C>::value, "");
// It's possible to provide an int. If it's given to an lvalue, the result is
// void. Otherwise it is std::string (which is also treated as allowed for a
// void result type).
static_assert(internal::is_callable_r<void, C&, int>::value, "");
static_assert(!internal::is_callable_r<int, C&, int>::value, "");
static_assert(!internal::is_callable_r<std::string, C&, int>::value, "");
static_assert(!internal::is_callable_r<void, const C&, int>::value, "");
static_assert(internal::is_callable_r<std::string, C, int>::value, "");
static_assert(internal::is_callable_r<void, C, int>::value, "");
static_assert(!internal::is_callable_r<int, C, int>::value, "");
// It's not possible to provide other arguments.
static_assert(!internal::is_callable_r<void, C, std::string>::value, "");
static_assert(!internal::is_callable_r<void, C, int, int>::value, "");
// Nothing should choke when we try to call other arguments besides directly
// callable objects, but they should not show up as callable.
static_assert(!internal::is_callable_r<void, int>::value, "");
static_assert(!internal::is_callable_r<void, void (C::*)()>::value, "");
static_assert(!internal::is_callable_r<void, void (C::*)(), C*>::value, "");
}
// Tests that BuiltInDefaultValue<T*>::Get() returns NULL.
TEST(BuiltInDefaultValueTest, IsNullForPointerTypes) {
@ -1428,6 +1561,154 @@ TEST(MockMethodTest, CanTakeMoveOnlyValue) {
EXPECT_EQ(42, *saved);
}
// It should be possible to use callables with an &&-qualified call operator
// with WillOnce, since they will be called only once. This allows actions to
// contain and manipulate move-only types.
TEST(MockMethodTest, ActionHasRvalueRefQualifiedCallOperator) {
struct Return17 {
int operator()() && { return 17; }
};
// Action is directly compatible with mocked function type.
{
MockFunction<int()> mock;
EXPECT_CALL(mock, Call).WillOnce(Return17());
EXPECT_EQ(17, mock.AsStdFunction()());
}
// Action doesn't want mocked function arguments.
{
MockFunction<int(int)> mock;
EXPECT_CALL(mock, Call).WillOnce(Return17());
EXPECT_EQ(17, mock.AsStdFunction()(0));
}
}
// Edge case: if an action has both a const-qualified and an &&-qualified call
// operator, there should be no "ambiguous call" errors. The &&-qualified
// operator should be used by WillOnce (since it doesn't need to retain the
// action beyond one call), and the const-qualified one by WillRepeatedly.
TEST(MockMethodTest, ActionHasMultipleCallOperators) {
struct ReturnInt {
int operator()() && { return 17; }
int operator()() const& { return 19; }
};
// Directly compatible with mocked function type.
{
MockFunction<int()> mock;
EXPECT_CALL(mock, Call).WillOnce(ReturnInt()).WillRepeatedly(ReturnInt());
EXPECT_EQ(17, mock.AsStdFunction()());
EXPECT_EQ(19, mock.AsStdFunction()());
EXPECT_EQ(19, mock.AsStdFunction()());
}
// Ignores function arguments.
{
MockFunction<int(int)> mock;
EXPECT_CALL(mock, Call).WillOnce(ReturnInt()).WillRepeatedly(ReturnInt());
EXPECT_EQ(17, mock.AsStdFunction()(0));
EXPECT_EQ(19, mock.AsStdFunction()(0));
EXPECT_EQ(19, mock.AsStdFunction()(0));
}
}
// WillOnce should have no problem coping with a move-only action, whether it is
// &&-qualified or not.
TEST(MockMethodTest, MoveOnlyAction) {
// &&-qualified
{
struct Return17 {
Return17() = default;
Return17(Return17&&) = default;
Return17(const Return17&) = delete;
Return17 operator=(const Return17&) = delete;
int operator()() && { return 17; }
};
MockFunction<int()> mock;
EXPECT_CALL(mock, Call).WillOnce(Return17());
EXPECT_EQ(17, mock.AsStdFunction()());
}
// Not &&-qualified
{
struct Return17 {
Return17() = default;
Return17(Return17&&) = default;
Return17(const Return17&) = delete;
Return17 operator=(const Return17&) = delete;
int operator()() const { return 17; }
};
MockFunction<int()> mock;
EXPECT_CALL(mock, Call).WillOnce(Return17());
EXPECT_EQ(17, mock.AsStdFunction()());
}
}
// It should be possible to use an action that returns a value with a mock
// function that doesn't, both through WillOnce and WillRepeatedly.
TEST(MockMethodTest, ActionReturnsIgnoredValue) {
struct ReturnInt {
int operator()() const { return 0; }
};
MockFunction<void()> mock;
EXPECT_CALL(mock, Call).WillOnce(ReturnInt()).WillRepeatedly(ReturnInt());
mock.AsStdFunction()();
mock.AsStdFunction()();
}
// Despite the fanciness around move-only actions and so on, it should still be
// possible to hand an lvalue reference to a copyable action to WillOnce.
TEST(MockMethodTest, WillOnceCanAcceptLvalueReference) {
MockFunction<int()> mock;
const auto action = [] { return 17; };
EXPECT_CALL(mock, Call).WillOnce(action);
EXPECT_EQ(17, mock.AsStdFunction()());
}
// A callable that doesn't use SFINAE to restrict its call operator's overload
// set, but is still picky about which arguments it will accept.
struct StaticAssertSingleArgument {
template <typename... Args>
static constexpr bool CheckArgs() {
static_assert(sizeof...(Args) == 1, "");
return true;
}
template <typename... Args, bool = CheckArgs<Args...>()>
int operator()(Args...) const {
return 17;
}
};
// WillOnce and WillRepeatedly should both work fine with naïve implementations
// of actions that don't use SFINAE to limit the overload set for their call
// operator. If they are compatible with the actual mocked signature, we
// shouldn't probe them with no arguments and trip a static_assert.
TEST(MockMethodTest, ActionSwallowsAllArguments) {
MockFunction<int(int)> mock;
EXPECT_CALL(mock, Call)
.WillOnce(StaticAssertSingleArgument{})
.WillRepeatedly(StaticAssertSingleArgument{});
EXPECT_EQ(17, mock.AsStdFunction()(0));
EXPECT_EQ(17, mock.AsStdFunction()(0));
}
// Tests for std::function based action.
int Add(int val, int& ref, int* ptr) { // NOLINT
@ -1552,7 +1833,8 @@ TEST(ActionMacro, LargeArity) {
14, 15, 16, 17, 18, 19)));
}
} // Unnamed namespace
} // namespace
} // namespace testing
#ifdef _MSC_VER
#if _MSC_VER == 1900