Commit 2a43ea7d authored by Dominik Charousset's avatar Dominik Charousset

Implement new fixture for deterministic testing

parent 6b59bb87
...@@ -241,6 +241,11 @@ public: ...@@ -241,6 +241,11 @@ public:
return *mailbox_; return *mailbox_;
} }
/// Checks whether this actor is fully initialized.
bool initialized() const noexcept {
return getf(is_initialized_flag);
}
// -- event handlers --------------------------------------------------------- // -- event handlers ---------------------------------------------------------
/// Sets a custom handler for unexpected messages. /// Sets a custom handler for unexpected messages.
......
...@@ -22,6 +22,8 @@ caf_add_component( ...@@ -22,6 +22,8 @@ caf_add_component(
caf/test/block.cpp caf/test/block.cpp
caf/test/context.cpp caf/test/context.cpp
caf/test/factory.cpp caf/test/factory.cpp
caf/test/fixture/deterministic.cpp
caf/test/fixture/deterministic.test.cpp
caf/test/given.cpp caf/test/given.cpp
caf/test/nesting_error.cpp caf/test/nesting_error.cpp
caf/test/registry.cpp caf/test/registry.cpp
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/fixture/deterministic.hpp"
#include "caf/actor_control_block.hpp"
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/detail/mailbox_factory.hpp"
#include "caf/detail/source_location.hpp"
#include "caf/detail/sync_request_bouncer.hpp"
#include "caf/detail/test_export.hpp"
#include "caf/scheduled_actor.hpp"
namespace caf::test::fixture {
// -- mailbox ------------------------------------------------------------------
class deterministic::mailbox_impl : public ref_counted,
public abstract_mailbox {
public:
mailbox_impl(deterministic* fix, scheduled_actor* owner)
: fix_(fix), owner_(owner) {
}
intrusive::inbox_result push_back(mailbox_element_ptr ptr) override {
if (closed_) {
detail::sync_request_bouncer bouncer{close_reason_};
bouncer(*ptr);
return intrusive::inbox_result::queue_closed;
}
using event_t = deterministic::scheduling_event;
auto unblocked = fix_->mail_count(owner_) == 0;
auto event = std::make_unique<event_t>(owner_, std::move(ptr));
fix_->events_.push_back(std::move(event));
return unblocked ? intrusive::inbox_result::unblocked_reader
: intrusive::inbox_result::success;
}
void push_front(mailbox_element_ptr ptr) override {
using event_t = deterministic::scheduling_event;
auto event = std::make_unique<event_t>(owner_, std::move(ptr));
fix_->events_.emplace_front(std::move(event));
}
mailbox_element_ptr pop_front() override {
return fix_->pop_msg_impl(owner_);
}
bool closed() const noexcept override {
return closed_;
}
bool blocked() const noexcept override {
return blocked_;
}
bool try_block() override {
blocked_ = true;
return true;
}
bool try_unblock() override {
if (!blocked_)
return false;
blocked_ = false;
return true;
}
size_t close(const error& reason) override {
closed_ = true;
close_reason_ = reason;
auto result = size_t{0};
auto envelope = fix_->pop_msg_impl(owner_);
while (envelope != nullptr) {
detail::sync_request_bouncer bouncer{reason};
++result;
envelope = fix_->pop_msg_impl(owner_);
}
return result;
}
size_t size() override {
return fix_->mail_count(owner_);
}
void ref_mailbox() noexcept override {
ref();
}
void deref_mailbox() noexcept override {
deref();
}
mailbox_element* peek(message_id) override {
// Note: this function only exists for backwards compatibility with the old
// unit testing framework. It is not used by the new test runner and thus
// not implemented.
CAF_RAISE_ERROR(std::logic_error, "peek not supported by this mailbox");
}
private:
bool blocked_ = false;
bool closed_ = false;
error close_reason_;
deterministic* fix_;
scheduled_actor* owner_;
};
class deterministic::mailbox_factory_impl : public detail::mailbox_factory {
public:
explicit mailbox_factory_impl(deterministic* fix) : fix_(fix) {
// nop
}
abstract_mailbox* make(scheduled_actor* owner) override {
return new deterministic::mailbox_impl(fix_, owner);
}
abstract_mailbox* make(blocking_actor*) override {
return nullptr;
}
private:
deterministic* fix_;
};
// -- scheduler ----------------------------------------------------------------
class deterministic::scheduler_impl : public scheduler::abstract_coordinator {
public:
using super = caf::scheduler::abstract_coordinator;
scheduler_impl(actor_system& sys, deterministic* fix)
: super(sys), fix_(fix) {
// nop
}
bool detaches_utility_actors() const override {
return false;
}
detail::test_actor_clock& clock() noexcept override {
return clock_;
}
protected:
void start() override {
// nop
}
void stop() override {
// nop
}
void enqueue(resumable* ptr) override {
using event_t = deterministic::scheduling_event;
using subtype_t = resumable::subtype_t;
switch (ptr->subtype()) {
case subtype_t::scheduled_actor:
case subtype_t::io_actor: {
// Actors put their messages into events_ directly. However, we do run
// them right away if they aren't initialized yet.
auto dptr = static_cast<scheduled_actor*>(ptr);
if (!dptr->initialized())
dptr->resume(system_.dummy_execution_unit(), 0);
break;
}
default:
fix_->events_.push_back(std::make_unique<event_t>(ptr, nullptr));
}
// Before calling this function, CAF *always* bumps the reference count.
// Hence, we need to release one reference count here.
intrusive_ptr_release(ptr);
}
private:
/// The fixture this scheduler belongs to.
deterministic* fix_;
/// Allows users to fake time at will.
detail::test_actor_clock clock_;
};
// -- config -------------------------------------------------------------------
deterministic::config::config(deterministic* fix) {
factory_ = std::make_unique<mailbox_factory_impl>(fix);
module_factories.push_back([fix](actor_system& sys) -> actor_system::module* {
return new scheduler_impl(sys, fix);
});
}
deterministic::config::~config() {
// nop
}
detail::mailbox_factory* deterministic::config::mailbox_factory() {
return factory_.get();
}
// -- abstract_message_predicate -----------------------------------------------
deterministic::abstract_message_predicate::~abstract_message_predicate() {
// nop
}
// -- fixture ------------------------------------------------------------------
deterministic::deterministic() : cfg(this), sys(cfg) {
// nop
}
deterministic::~deterministic() {
events_.clear();
}
bool deterministic::prepone_event_impl(const strong_actor_ptr& receiver) {
actor_predicate any_sender{std::ignore};
message_predicate any_payload{std::ignore};
return prepone_event_impl(receiver, any_sender, any_payload);
}
bool deterministic::prepone_event_impl(
const strong_actor_ptr& receiver, actor_predicate& sender_pred,
abstract_message_predicate& payload_pred) {
if (events_.empty() || !receiver)
return false;
auto first = events_.begin();
auto last = events_.end();
auto i = std::find_if(first, last, [&](const auto& event) {
auto self = actor_cast<abstract_actor*>(receiver);
return event->target == static_cast<scheduled_actor*>(self)
&& sender_pred(event->item->sender)
&& payload_pred(event->item->payload);
});
if (i == last)
return false;
if (i != first) {
auto ptr = std::move(*i);
events_.erase(i);
events_.insert(events_.begin(), std::move(ptr));
}
return true;
}
deterministic::scheduling_event*
deterministic::find_event_impl(const strong_actor_ptr& receiver) {
if (events_.empty() || !receiver)
return nullptr;
auto last = events_.end();
auto i = std::find_if(events_.begin(), last, [&](const auto& event) {
auto raw_ptr = actor_cast<abstract_actor*>(receiver);
return event->target == static_cast<scheduled_actor*>(raw_ptr);
});
if (i != last)
return i->get();
return nullptr;
}
mailbox_element_ptr deterministic::pop_msg_impl(scheduled_actor* receiver) {
auto pred = [&](const auto& event) { return event->target == receiver; };
auto first = events_.begin();
auto last = events_.end();
auto i = std::find_if(first, last, pred);
if (i == last)
return nullptr;
auto result = std::move((*i)->item);
events_.erase(i);
return result;
}
size_t deterministic::mail_count(scheduled_actor* receiver) {
if (receiver == nullptr)
return 0;
auto pred = [&](const auto& event) { return event->target == receiver; };
return std::count_if(events_.begin(), events_.end(), pred);
}
size_t deterministic::mail_count(const strong_actor_ptr& receiver) {
auto raw_ptr = actor_cast<abstract_actor*>(receiver);
return mail_count(dynamic_cast<scheduled_actor*>(raw_ptr));
}
bool deterministic::dispatch_message() {
if (events_.empty())
return false;
if (events_.front()->item == nullptr) {
// Regular resumable.
auto ev = std::move(events_.front());
events_.pop_front();
ev->target->resume(sys.dummy_execution_unit(), 0);
return true;
}
// Actor: we simply resume the next actor and it will pick up its message.
auto next = events_.front()->target;
next->resume(sys.dummy_execution_unit(), 1);
return true;
}
size_t deterministic::dispatch_messages() {
size_t result = 0;
while (dispatch_message())
++result;
return result;
}
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/test/runnable.hpp"
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/detail/source_location.hpp"
#include "caf/detail/test_actor_clock.hpp"
#include "caf/detail/test_export.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/scheduler/abstract_coordinator.hpp"
#include <list>
#include <memory>
namespace caf::test::fixture {
/// A fixture for writing unit tests that require deterministic scheduling. The
/// fixture equips tests with an actor system that uses a deterministic
/// scheduler and provides a DSL for writing high-level tests for message
/// passing between actors.
class CAF_TEST_EXPORT deterministic {
private:
// -- private member types (implementation details) --------------------------
class mailbox_impl;
class scheduler_impl;
class mailbox_factory_impl;
/// Wraps a resumable pointer and a mailbox element pointer.
struct scheduling_event {
scheduling_event(resumable* target, mailbox_element_ptr payload)
: target(target), item(std::move(payload)) {
// nop
}
/// The target of the event.
intrusive_ptr<resumable> target;
/// The message for the event or `nullptr` if the target is not an actor.
mailbox_element_ptr item;
};
/// A predicate for checking of single value. When constructing from a
/// reference wrapper, the predicate assigns the found value to the reference
/// instead of checking it.
template <class T>
class value_predicate {
public:
value_predicate() {
predicate_ = [](const T&) { return true; };
}
explicit value_predicate(decltype(std::ignore)) : value_predicate() {
// nop
}
template <class U>
explicit value_predicate(
U value, std::enable_if_t<detail::is_comparable_v<T, U>>* = nullptr) {
predicate_ = [value](const T& found) { return found == value; };
}
explicit value_predicate(std::reference_wrapper<T> ref) {
predicate_ = [ref](const T& found) {
ref.get() = found;
return true;
};
}
template <
class Predicate,
class = std::enable_if_t<std::is_same_v<
bool, decltype(std::declval<Predicate>()(std::declval<const T&>()))>>>
explicit value_predicate(Predicate predicate) {
predicate_ = std::move(predicate);
}
value_predicate(value_predicate&&) = default;
value_predicate(const value_predicate&) = default;
value_predicate& operator=(value_predicate&&) = default;
value_predicate& operator=(const value_predicate&) = default;
bool operator()(const T& value) {
return predicate_(value);
}
private:
std::function<bool(const T&)> predicate_;
};
/// Convenience alias for predicates on strong actor pointers.
using actor_predicate = value_predicate<strong_actor_ptr>;
/// Abstract base type for message predicates.
class abstract_message_predicate {
public:
virtual ~abstract_message_predicate();
virtual bool operator()(const message&) = 0;
};
/// A predicate for checking type and (optionally) content of a message.
template <class... Ts>
class message_predicate : public abstract_message_predicate {
public:
template <class U, class... Us>
explicit message_predicate(U x, Us... xs) {
predicates_ = std::make_shared<predicates_tuple>(std::move(x),
std::move(xs)...);
}
explicit message_predicate(decltype(std::ignore)) {
// nop
}
message_predicate() {
predicates_ = std::make_shared<predicates_tuple>();
}
message_predicate(message_predicate&&) = default;
message_predicate(const message_predicate&) = default;
message_predicate& operator=(message_predicate&&) = default;
message_predicate& operator=(const message_predicate&) = default;
bool operator()(const message& msg) {
if (!predicates_)
return true;
if (auto view = make_const_typed_message_view<Ts...>(msg))
return do_check(view, std::index_sequence_for<Ts...>{});
return false;
}
private:
template <size_t... Is>
bool do_check(const_typed_message_view<Ts...> view, //
std::index_sequence<Is...>) {
return (((std::get<Is>(*predicates_))(get<Is>(view))) && ...);
}
using predicates_tuple = std::tuple<value_predicate<Ts>...>;
std::shared_ptr<predicates_tuple> predicates_;
};
public:
// -- public member types ----------------------------------------------------
/// The configuration type for this fixture.
class config : public actor_system_config {
public:
config(deterministic* fix);
~config() override;
private:
detail::mailbox_factory* mailbox_factory() override;
std::unique_ptr<detail::mailbox_factory> factory_;
};
/// Configures the algorithm to evaluate for an `evaluator` instances.
enum class evaluator_algorithm {
expect,
allow,
prepone,
prepone_and_expect,
prepone_and_allow
};
/// Provides a fluent interface for matching messages. The `evaluator` allows
/// setting `from` and `with` parameters for an algorithm that matches
/// messages against a predicate. When setting the only mandatory parameter
/// `to`, the `evaluator` evaluates the predicate against the next message
/// in the mailbox of the target actor.
template <class... Ts>
class evaluator {
public:
evaluator(deterministic* fix, detail::source_location loc,
evaluator_algorithm algorithm)
: fix_(fix), loc_(loc), algo_(algorithm) {
// nop
}
evaluator() = delete;
evaluator(evaluator&&) noexcept = default;
evaluator& operator=(evaluator&&) noexcept = default;
evaluator(const evaluator&) = delete;
evaluator& operator=(const evaluator&) = delete;
template <class... Us>
evaluator&& with(Us... xs) && {
with_ = message_predicate<Ts...>(std::move(xs)...);
return std::move(*this);
}
evaluator&& from(const strong_actor_ptr& src) && {
from_ = value_predicate<strong_actor_ptr>{std::move(src)};
return std::move(*this);
}
evaluator&& from(std::nullptr_t) && {
return std::move(*this).from(strong_actor_ptr{});
}
evaluator&& from(const actor& src) && {
from_ = value_predicate<strong_actor_ptr>{std::move(src)};
return std::move(*this);
}
template <class... Us>
evaluator&& from(const typed_actor<Us...>& src) && {
from_ = value_predicate<strong_actor_ptr>{std::move(src)};
return std::move(*this);
}
evaluator&& from(std::reference_wrapper<strong_actor_ptr> src) && {
from_ = value_predicate<strong_actor_ptr>{src};
return std::move(*this);
}
template <class T>
bool to(const T& dst) && {
auto dst_ptr = actor_cast<strong_actor_ptr>(dst);
switch (algo_) {
case evaluator_algorithm::expect:
return eval_dispatch(dst_ptr, true);
case evaluator_algorithm::allow:
return eval_dispatch(dst_ptr, false);
case evaluator_algorithm::prepone:
return eval_prepone(dst_ptr);
case evaluator_algorithm::prepone_and_expect:
eval_prepone(dst_ptr);
return eval_dispatch(dst_ptr, true);
case evaluator_algorithm::prepone_and_allow:
return eval_prepone(dst_ptr) && eval_dispatch(dst_ptr, false);
default:
CAF_RAISE_ERROR(std::logic_error, "invalid algorithm");
}
}
private:
using predicates = std::tuple<value_predicate<Ts>...>;
bool eval_dispatch(const strong_actor_ptr& dst, bool fail_on_mismatch) {
auto& ctx = runnable::current();
auto* event = fix_->find_event_impl(dst);
if (!event) {
if (fail_on_mismatch)
ctx.fail({"no matching message found", loc_});
return false;
}
if (!from_(event->item->sender) || !with_(event->item->payload)) {
if (fail_on_mismatch)
ctx.fail({"no matching message found", loc_});
return false;
}
fix_->prepone_event_impl(dst);
if (fail_on_mismatch) {
if (!fix_->dispatch_message())
ctx.fail({"failed to dispatch message", loc_});
reporter::instance->pass(loc_);
return true;
}
return fix_->dispatch_message();
}
bool eval_prepone(const strong_actor_ptr& dst) {
return fix_->prepone_event_impl(dst, from_, with_);
}
deterministic* fix_;
detail::source_location loc_;
evaluator_algorithm algo_;
actor_predicate from_;
message_predicate<Ts...> with_;
};
// -- friends ----------------------------------------------------------------
friend class mailbox_impl;
friend class scheduler_impl;
template <class... Ts>
friend class evaluator;
// -- constructors, destructors, and assignment operators --------------------
deterministic();
~deterministic();
// -- properties -------------------------------------------------------------
/// Returns the number of pending messages for `receiver`.
size_t mail_count(scheduled_actor* receiver);
/// Returns the number of pending messages for `receiver`.
size_t mail_count(const strong_actor_ptr& receiver);
/// Returns the number of pending messages for `receiver`.
size_t mail_count(const actor& receiver) {
return mail_count(actor_cast<strong_actor_ptr>(receiver));
}
/// Returns the number of pending messages for `receiver`.
template <class... Ts>
size_t mail_count(const typed_actor<Ts...>& receiver) {
return mail_count(actor_cast<strong_actor_ptr>(receiver));
}
// -- control flow -----------------------------------------------------------
/// Tries to dispatch a single message.
bool dispatch_message();
/// Dispatches all pending messages.
size_t dispatch_messages();
// -- message-based predicates -----------------------------------------------
/// Expects a message with types `Ts...` as the next message in the mailbox of
/// the receiver and aborts the test if the message is missing. Otherwise
/// executes the message.
template <class... Ts>
auto expect(const detail::source_location& loc
= detail::source_location::current()) {
return evaluator<Ts...>{this, loc, evaluator_algorithm::expect};
}
/// Tries to match a message with types `Ts...` and executes it if it is the
/// next message in the mailbox of the receiver.
template <class... Ts>
auto allow(const detail::source_location& loc
= detail::source_location::current()) {
return evaluator<Ts...>{this, loc, evaluator_algorithm::allow};
}
/// Tries to prepone a message, i.e., reorders the messages in the mailbox of
/// the receiver such that the next call to `dispatch_message` will run it.
/// @returns `true` if the message could be preponed, `false` otherwise.
template <class... Ts>
auto prepone(const detail::source_location& loc
= detail::source_location::current()) {
return evaluator<Ts...>{this, loc, evaluator_algorithm::prepone};
}
/// Shortcut for calling `prepone` and then `expect` with the same arguments.
template <class... Ts>
auto prepone_and_expect(const detail::source_location& loc
= detail::source_location::current()) {
return evaluator<Ts...>{this, loc, evaluator_algorithm::prepone_and_expect};
}
/// Shortcut for calling `prepone` and then `allow` with the same arguments.
template <class... Ts>
auto prepone_and_allow(const detail::source_location& loc
= detail::source_location::current()) {
return evaluator<Ts...>{this, loc, evaluator_algorithm::prepone_and_allow};
}
// -- member variables -------------------------------------------------------
/// Configures the actor system with deterministic scheduling.
config cfg;
/// The actor system instance for the tests.
actor_system sys;
private:
/// Tries find a message in `events_` that matches the given predicate and
/// moves it to the front of the queue.
bool prepone_event_impl(const strong_actor_ptr& receiver);
/// Tries find a message in `events_` that matches the given predicates and
/// moves it to the front of the queue.
bool prepone_event_impl(const strong_actor_ptr& receiver,
actor_predicate& sender_pred,
abstract_message_predicate& payload_pred);
/// Returns the next event for `receiver` or `nullptr` if there is none.
scheduling_event* find_event_impl(const strong_actor_ptr& receiver);
/// Removes the next message for `receiver` from the queue and returns it.
mailbox_element_ptr pop_msg_impl(scheduled_actor* receiver);
/// Stores all pending messages of scheduled actors.
std::list<std::unique_ptr<scheduling_event>> events_;
};
} // namespace caf::test
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/test/fixture/deterministic.hpp"
#include "caf/test/caf_test_main.hpp"
#include "caf/test/test.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/scoped_actor.hpp"
#include "caf/send.hpp"
namespace fixture = caf::test::fixture;
template <class T>
using value_predicate = fixture::deterministic::value_predicate<T>;
template <class... Ts>
using message_predicate = fixture::deterministic::message_predicate<Ts...>;
struct my_int {
int value;
};
bool operator==(my_int lhs, int rhs) noexcept {
return lhs.value == rhs;
}
bool operator==(int lhs, my_int rhs) noexcept {
return lhs == rhs.value;
}
TEST("value predicates check or extract individual values") {
using predicate_t = value_predicate<int>;
SECTION("catch-all predicates are constructible from std::ignore") {
predicate_t f{std::ignore};
check(f(1));
check(f(2));
check(f(3));
}
SECTION("a default-constructed predicate always returns true") {
predicate_t f;
check(f(1));
check(f(2));
check(f(3));
}
SECTION("exact match predicates are constructible from a value") {
predicate_t f{2};
check(!f(1));
check(f(2));
check(!f(3));
}
SECTION("exact match predicates are constructible from any comparable type") {
predicate_t f{my_int{2}};
check(!f(1));
check(f(2));
check(!f(3));
}
SECTION("custom predicates are constructible from a function object") {
predicate_t f{[](int x) { return x <= 2; }};
check(f(1));
check(f(2));
check(!f(3));
}
SECTION("extractors are constructible from a reference wrapper") {
int x = 0;
predicate_t f{std::ref(x)};
check(f(1)) && check_eq(x, 1);
check(f(2)) && check_eq(x, 2);
check(f(3)) && check_eq(x, 3);
}
}
TEST("message predicates check all values in a message") {
using predicate_t = message_predicate<int, std::string, double>;
SECTION("a default-constructed message predicate matches anything") {
predicate_t f;
check(f(caf::make_message(1, "two", 3.0)));
check(f(caf::make_message(42, "hello world", 7.7)));
}
SECTION("message predicates can match values") {
predicate_t f{1, "two", [](double x) { return x < 5.0; }};
check(f(caf::make_message(1, "two", 3.0)));
check(!f(caf::make_message(1, "two", 6.0)));
}
SECTION("message predicates can extract values") {
auto x0 = 0;
auto x1 = std::string{};
auto x2 = 0.0;
predicate_t f{std::ref(x0), std::ref(x1), std::ref(x2)};
check(f(caf::make_message(1, "two", 3.0)));
check_eq(x0, 1);
check_eq(x1, "two");
check_eq(x2, 3.0);
}
}
WITH_FIXTURE(fixture::deterministic) {
TEST("the deterministic fixture provides a deterministic scheduler") {
auto initialized = std::make_shared<bool>(false);
auto count = std::make_shared<int32_t>(0);
auto worker = sys.spawn([initialized, count] {
*initialized = true;
return caf::behavior{
[count](int32_t value) { *count += value; },
[count](const std::string& str) {
if (auto ival = caf::get_as<int32_t>(caf::config_value{str}))
*count += *ival;
},
};
});
caf::scoped_actor self{sys};
check(*initialized);
check_eq(mail_count(worker), 0u);
anon_send(worker, 1);
check_eq(mail_count(worker), 1u);
self->send(worker, 2);
check_eq(mail_count(worker), 2u);
anon_send(worker, 3);
check_eq(mail_count(worker), 3u);
SECTION("calling dispatch_message processes a single message") {
check(dispatch_message());
check_eq(mail_count(worker), 2u);
check_eq(*count, 1);
check(dispatch_message());
check_eq(mail_count(worker), 1u);
check_eq(*count, 3);
check(dispatch_message());
check_eq(mail_count(worker), 0u);
check_eq(*count, 6);
check(!dispatch_message());
}
SECTION("calling dispatch_messages processes all messages") {
check_eq(dispatch_messages(), 3u);
check_eq(*count, 6);
}
#ifdef CAF_ENABLE_EXCEPTIONS
SECTION("expect() checks for required messages") {
expect<int32_t>().to(worker);
check_eq(mail_count(worker), 2u);
check_eq(*count, 1);
expect<int32_t>().to(worker);
check_eq(mail_count(worker), 1u);
check_eq(*count, 3);
expect<int32_t>().to(worker);
check_eq(mail_count(worker), 0u);
check_eq(*count, 6);
should_fail_with_exception([this, worker] { //
expect<int32_t>().with(3).to(worker);
});
}
SECTION("expect() matches the types of the next message") {
anon_send(worker, "4");
should_fail_with_exception([this, worker] { //
expect<std::string>().to(worker);
});
should_fail_with_exception([this, worker] { //
expect<int32_t, int32_t>().to(worker);
});
check_eq(*count, 0);
check_eq(dispatch_messages(), 4u);
check_eq(*count, 10);
}
SECTION("expect() optionally matches the content of the next message") {
should_fail_with_exception([this, worker] { //
expect<int32_t>().with(3).to(worker);
});
check_eq(*count, 0);
expect<int32_t>().with(1).to(worker);
expect<int32_t>().with(2).to(worker);
expect<int32_t>().with(3).to(worker);
check_eq(*count, 6);
}
SECTION("expect() optionally matches the sender of the next message") {
// First message has no sender.
should_fail_with_exception([this, worker, &self] { //
expect<int32_t>().from(self).to(worker);
});
check_eq(*count, 0);
expect<int32_t>().from(nullptr).to(worker);
check_eq(*count, 1);
// Second message is from self.
should_fail_with_exception([this, worker] { //
expect<int32_t>().from(nullptr).to(worker);
});
check_eq(*count, 1);
expect<int32_t>().from(self).to(worker);
check_eq(*count, 3);
}
SECTION("prepone_and_expect() processes out of order based on types") {
anon_send(worker, "4");
prepone_and_expect<std::string>().to(worker);
check_eq(*count, 4);
should_fail_with_exception([this, worker] { //
prepone_and_expect<std::string>().to(worker);
});
check_eq(*count, 4);
check_eq(dispatch_messages(), 3u);
check_eq(*count, 10);
}
SECTION("prepone_and_expect() processes out of order based on content") {
prepone_and_expect<int32_t>().with(3).to(worker);
check_eq(mail_count(worker), 2u);
check_eq(*count, 3);
should_fail_with_exception([this, worker] { //
prepone_and_expect<int32_t>().with(3).to(worker);
});
check_eq(mail_count(worker), 2u);
check_eq(*count, 3);
prepone_and_expect<int32_t>().with(1).to(worker);
check_eq(*count, 4);
prepone_and_expect<int32_t>().with(2).to(worker);
check_eq(*count, 6);
check(!dispatch_message());
}
SECTION("prepone_and_expect() processes out of order based on senders") {
prepone_and_expect<int32_t>().from(self).to(worker);
check_eq(mail_count(worker), 2u);
check_eq(*count, 2);
should_fail_with_exception([this, worker, &self] { //
prepone_and_expect<int32_t>().from(self).to(worker);
});
check_eq(mail_count(worker), 2u);
check_eq(*count, 2);
prepone_and_expect<int32_t>().from(nullptr).to(worker);
check_eq(*count, 3);
prepone_and_expect<int32_t>().from(nullptr).to(worker);
check_eq(*count, 6);
check(!dispatch_message());
}
#endif // CAF_ENABLE_EXCEPTIONS
SECTION("allow() checks for optional messages") {
check(allow<int32_t>().to(worker));
check_eq(mail_count(worker), 2u);
check_eq(*count, 1);
check(allow<int32_t>().to(worker));
check_eq(mail_count(worker), 1u);
check_eq(*count, 3);
check(allow<int32_t>().to(worker));
check_eq(mail_count(worker), 0u);
check_eq(*count, 6);
check(!allow<int32_t>().with(3).to(worker));
}
SECTION("allow() matches the types of the next message") {
anon_send(worker, "4");
check(!allow<std::string>().to(worker));
check(!allow<int32_t, int32_t>().to(worker));
check_eq(*count, 0);
check_eq(dispatch_messages(), 4u);
check_eq(*count, 10);
}
SECTION("allow() optionally matches the content of the next message") {
check(!allow<int32_t>().with(3).to(worker));
check_eq(*count, 0);
check(allow<int32_t>().with(1).to(worker));
check(allow<int32_t>().with(2).to(worker));
check(allow<int32_t>().with(3).to(worker));
check_eq(*count, 6);
}
SECTION("allow() optionally matches the sender of the next message") {
// First message has no sender.
check(!allow<int32_t>().from(self).to(worker));
check_eq(*count, 0);
check(allow<int32_t>().from(nullptr).to(worker));
check_eq(*count, 1);
// Second message is from self.
check(!allow<int32_t>().from(nullptr).to(worker));
check_eq(*count, 1);
check(allow<int32_t>().from(self).to(worker));
check_eq(*count, 3);
}
SECTION("prepone_and_allow() checks for optional messages") {
check(prepone_and_allow<int32_t>().to(worker));
check_eq(mail_count(worker), 2u);
check_eq(*count, 1);
check(prepone_and_allow<int32_t>().to(worker));
check_eq(mail_count(worker), 1u);
check_eq(*count, 3);
check(prepone_and_allow<int32_t>().to(worker));
check_eq(mail_count(worker), 0u);
check_eq(*count, 6);
check(!prepone_and_allow<int32_t>().with(3).to(worker));
}
SECTION("prepone_and_allow() processes out of order based on types") {
anon_send(worker, "4");
check(prepone_and_allow<std::string>().to(worker));
check(!prepone_and_allow<std::string>().to(worker));
check_eq(*count, 4);
check_eq(dispatch_messages(), 3u);
check_eq(*count, 10);
}
SECTION("prepone_and_allow() processes out of order based on content") {
check(prepone_and_allow<int32_t>().with(3).to(worker));
check_eq(mail_count(worker), 2u);
check_eq(*count, 3);
check(!prepone_and_allow<int32_t>().with(3).to(worker));
check_eq(mail_count(worker), 2u);
check_eq(*count, 3);
check(prepone_and_allow<int32_t>().with(1).to(worker));
check_eq(*count, 4);
check(prepone_and_allow<int32_t>().with(2).to(worker));
check_eq(*count, 6);
check(!dispatch_message());
}
SECTION("prepone_and_allow() processes out of order based on senders") {
check(prepone_and_allow<int32_t>().from(self).to(worker));
check_eq(mail_count(worker), 2u);
check_eq(*count, 2);
check(!prepone_and_allow<int32_t>().from(self).to(worker));
check_eq(mail_count(worker), 2u);
check_eq(*count, 2);
check(prepone_and_allow<int32_t>().from(nullptr).to(worker));
check_eq(*count, 3);
check(prepone_and_allow<int32_t>().from(nullptr).to(worker));
check_eq(*count, 6);
check(!dispatch_message());
}
SECTION("prepone_and_allow() ignores non-existent combinations") {
// There is no message with content (4).
check(!prepone_and_allow<int32_t>().with(4).from(self).to(worker));
// There is no message with content (3) from self.
check(!prepone_and_allow<int32_t>().with(3).from(self).to(worker));
// The original order should be preserved.
check(dispatch_message());
check_eq(*count, 1);
check(dispatch_message());
check_eq(*count, 3);
check(dispatch_message());
check_eq(*count, 6);
}
}
} // WITH_FIXTURE(fixture::deterministic)
CAF_TEST_MAIN()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment