blob: 3e8097062d06c0557b3f148693bff9b71ea05ff8 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LIB_ASYNC_PATTERNS_TESTING_CPP_DISPATCHER_BOUND_H_
#define LIB_ASYNC_PATTERNS_TESTING_CPP_DISPATCHER_BOUND_H_
#include <lib/async-loop/cpp/loop.h>
#include <lib/async_patterns/cpp/dispatcher_bound.h>
#include <lib/async_patterns/cpp/receiver.h>
#include <future>
namespace async_patterns {
// |TestDispatcherBound| adds the ability to issue synchronous calls on top of
// |DispatcherBound|. Synchronous test code may use this class instead of
// manually running an event loop.
//
// See |async_patterns::DispatcherBound| for general information.
//
// See |async_patterns::SyncProxy| which uses this class to manage a thread-unsafe
// object and expose synchronous wrapper methods for every member method you specify.
template <typename T>
class TestDispatcherBound final : public DispatcherBound<T> {
public:
// See |async_patterns::DispatcherBound| for how to construct a |DispatcherBound|.
// The same applies to |TestDispatcherBound|.
using DispatcherBound<T>::DispatcherBound;
// See |async_patterns::DispatcherBound::emplace| for how to construct an object
// inside a |DispatcherBound|. The same applies to |TestDispatcherBound::emplace|.
using DispatcherBound<T>::emplace;
// See |async_patterns::DispatcherBound::reset| for how to destroy an object
// inside a |DispatcherBound|. The same applies to |TestDispatcherBound::reset|.
using DispatcherBound<T>::reset;
// Refer to |DispatcherBound<T>::AsyncCall| for documentation.
//
// This test version of |AsyncCall| adds the ability to encapsulate the result
// of the call into a |std::future<R>|. This way, tests can synchronously block
// on that future, leading to more direct code:
//
// std::future<std::string> fut = some_dispatcher_bound
// .AsyncCall(&Object::GetSomeString)
// .ToFuture();
//
template <typename Member, typename... Args>
auto AsyncCall(Member T::*member, Args&&... args) {
ZX_ASSERT(DispatcherBound<T>::has_value());
constexpr bool kIsInvocable = std::is_invocable_v<Member, Args...>;
static_assert(kIsInvocable,
"|Member| must be callable with the provided |Args|. "
"Check that you specified each argument correctly to the |member| function.");
if constexpr (kIsInvocable) {
DispatcherBound<T>::CheckArgs(typename fit::callable_traits<Member>::args{});
return DispatcherBound<T>::template UnsafeAsyncCallImpl<TestAsyncCallBuilder>(
member, std::forward<Args>(args)...);
}
}
// Schedules an asynchronous |callable| on |dispatcher| with |args| and then
// block the caller until the asynchronous call is processed, returning the
// result of the asynchronous call.
//
// |callable| should take |T*| as the first argument, followed by optionally
// |Args| if any. A special case of |callable| is a pointer-to-member, e.g.
// |&T::SomeFunction|. Example:
//
// struct Foo {
// int Add(int a, int b) { return a + b; }
// };
// async_patterns::TestDispatcherBound<Foo> object{foo_loop.dispatcher()};
//
// int result = object.SyncCall(&Object::Add, 1, 2); // result == 3
//
// If |Object::Add| is an overloaded member function, you may disambiguate it
// by spelling out its signature:
//
// int result = object.SyncCall<int(int, int)>(&Object::Add, 1, 2);
//
// General callables are also supported. The provided callable can safely
// capture state without data races, because the current thread will be
// suspended while the dispatcher thread is accessing the captures. However,
// if you capture a pointer or reference, be careful to not let them escape to
// the wrapped object. When in doubt, only use the captured data inside the
// lambda scope:
//
// int result = object.SyncCall([&] (Foo* foo) {
// return foo->Add(1, 2);
// }); // result == 3
//
// If |callable| returns |void|, |SyncCall| will still block until the
// |callable| finishes executing on the target |dispatcher|. To schedule a
// fire-and-forget call, use |AsyncCall|.
template <typename Callable, typename... Args>
auto SyncCall(Callable&& callable, Args&&... args) {
ZX_ASSERT(DispatcherBound<T>::has_value());
constexpr bool kIsInvocable = std::is_invocable_v<Callable, T*, Args...>;
static_assert(kIsInvocable,
"|Callable| must be callable with |T*| and the provided |Args|. "
"Check that you specified each argument correctly to the |callable| function.");
if constexpr (kIsInvocable) {
using Result = std::invoke_result_t<Callable, T*, Args...>;
if constexpr (std::is_void_v<Result>) {
// Wrap a void-returning function into one that returns a unit type.
[[maybe_unused]] std::monostate unit = BlockOn(DispatcherBound<T>::UnsafeAsyncCallImpl(
[callable = std::forward<Callable>(callable)](T* ptr, auto&&... args) mutable {
std::invoke(callable, ptr, std::move(args)...);
return std::monostate{};
},
std::forward<Args>(args)...));
return;
} else {
return BlockOn(DispatcherBound<T>::UnsafeAsyncCallImpl(std::forward<Callable>(callable),
std::forward<Args>(args)...));
}
}
}
template <typename Member, typename... Args>
auto SyncCall(Member T::*member, Args&&... args) {
return SyncCall(std::mem_fn(member), std::forward<Args>(args)...);
}
private:
template <typename ReturnType, typename Task, typename Submit>
class TestAsyncCallBuilder;
template <typename ReturnType, typename Task, typename Submit>
std::invoke_result_t<Task> BlockOn(PendingCall<ReturnType, Task, Submit>&& pending_call) {
using R = std::invoke_result_t<Task>;
struct Slot {
async::Loop loop{&kAsyncLoopConfigNeverAttachToThread};
std::optional<R> result;
async_patterns::Receiver<Slot> receiver{this, loop.dispatcher()};
} slot;
std::move(pending_call).Then(slot.receiver.Once([](Slot* slot, R result) {
slot->result.emplace(std::move(result));
slot->loop.Quit();
}));
ZX_ASSERT(slot.loop.Run() == ZX_ERR_CANCELED);
return std::move(slot.result.value());
}
};
// The return type of |TestDispatcherBound<T>::AsyncCall|. In addition to
// everything supported by |async_patterns::PendingCall|, it also supports
// transforming the pending result to a |std::future|.
template <typename T>
template <typename ReturnType, typename Task, typename Submit>
class TestDispatcherBound<T>::TestAsyncCallBuilder : public PendingCall<ReturnType, Task, Submit> {
private:
using Base = PendingCall<ReturnType, Task, Submit>;
public:
// Arranges the |on_result| callback to be called with the result of the async
// call. See |DispatcherBound<T>::AsyncCall| for documentation.
using Base::Then;
// Gets an asynchronous promise that will be resolved with the result of the async
// call. See |DispatcherBound<T>::AsyncCall| for documentation.
using Base::promise;
// Gets a |std::future| that will resolve with the return value of the
// async call. Because one needs to block on an |std::future| to obtain its
// value, this function is only for use in tests. Use |promise| or |Then| with a
// |Callback| in production code instead.
std::future<ReturnType> ToFuture() && {
std::promise<ReturnType> promise;
std::future<ReturnType> fut = promise.get_future();
Base::CallWithContinuation(
[promise = std::move(promise)](ReturnType r) mutable { promise.set_value(std::move(r)); });
return fut;
}
using Base::Base;
};
// Constructs a |TestDispatcherBound<T>| that holds an instance of |T| by sending
// the |args| to the constructor of |T| run from a |dispatcher| task.
//
// See |TestDispatcherBound| constructor for details.
template <typename T, typename... Args>
TestDispatcherBound<T> MakeTestDispatcherBound(async_dispatcher_t* dispatcher, Args&&... args) {
return TestDispatcherBound<T>{dispatcher, std::in_place, std::forward<Args>(args)...};
}
} // namespace async_patterns
#endif // LIB_ASYNC_PATTERNS_TESTING_CPP_DISPATCHER_BOUND_H_