blob: 128b13ca423dc8fb10d7d8c877e0f34f9873d5e2 [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>
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.
template <typename T>
class TestDispatcherBound final : public DispatcherBound<T> {
public:
using DispatcherBound<T>::DispatcherBound;
// Schedules an asynchronous |member| call on |dispatcher| with |args| and
// then block the caller until the asynchronous call is processed, returning
// the result of the asynchronous call. 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 |member| returns |void|, use |AsyncCall| to schedule a fire-and-forget
// call instead.
template <typename Member, typename... Args>
auto SyncCall(Member T::*member, Args&&... args) {
return BlockOn(DispatcherBound<T>::AsyncCall(member, std::forward<Args>(args)...));
}
private:
template <typename Task>
std::invoke_result_t<Task> BlockOn(
async_patterns::internal::DispatcherBoundStorage::AsyncCallBuilder<Task>&& builder) {
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(builder).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());
}
};
// 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_