Commit cd8363aa authored by Dominik Charousset's avatar Dominik Charousset

Merge pull request #1518

parents c677bf5b faaaf3d5
......@@ -52,6 +52,8 @@ is based on [Keep a Changelog](https://keepachangelog.com).
`on_complete` on its subscriber on the first activity of the source
observable. Previously, the created observable would never reach its threshold
and attempt to buffer all values indefinitely.
- The comparison operator of `intrusive_ptr` no longer accidentally creates new
`intrusive_ptr` instances when comparing to raw pointers.
## [0.19.2] - 2023-06-13
......
......@@ -541,4 +541,8 @@ void actor_system::release_private_thread(detail::private_thread* ptr) {
private_threads_.release(ptr);
}
detail::mailbox_factory* actor_system::mailbox_factory() {
return cfg_.mailbox_factory();
}
} // namespace caf
......@@ -398,6 +398,7 @@ public:
infer_handle_from_class_t<C> spawn(Ts&&... xs) {
check_invariants<C>();
actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_impl<C, Os>(cfg, detail::spawn_fwd<Ts>(xs)...);
}
......@@ -436,6 +437,7 @@ public:
static_assert(spawnable,
"cannot spawn function-based actor with given arguments");
actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_functor<Os>(detail::bool_token<spawnable>{}, cfg, fun,
std::forward<Ts>(xs)...);
}
......@@ -500,6 +502,7 @@ public:
infer_handle_from_fun_t<F>
spawn_in_groups(std::initializer_list<group> gs, F fun, Ts&&... xs) {
actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_fun_in_groups<Os>(cfg, gs.begin(), gs.end(), fun,
std::forward<Ts>(xs)...);
}
......@@ -508,6 +511,7 @@ public:
template <spawn_options Os = no_spawn_options, class Gs, class F, class... Ts>
infer_handle_from_fun_t<F> spawn_in_groups(const Gs& gs, F fun, Ts&&... xs) {
actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_fun_in_groups<Os>(cfg, gs.begin(), gs.end(), fun,
std::forward<Ts>(xs)...);
}
......@@ -524,6 +528,7 @@ public:
infer_handle_from_class_t<T>
spawn_in_groups(std::initializer_list<group> gs, Ts&&... xs) {
actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_class_in_groups<T, Os>(cfg, gs.begin(), gs.end(),
std::forward<Ts>(xs)...);
}
......@@ -532,6 +537,7 @@ public:
template <class T, spawn_options Os = no_spawn_options, class Gs, class... Ts>
infer_handle_from_class_t<T> spawn_in_groups(const Gs& gs, Ts&&... xs) {
actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_class_in_groups<T, Os>(cfg, gs.begin(), gs.end(),
std::forward<Ts>(xs)...);
}
......@@ -696,6 +702,7 @@ public:
"only scheduled actors may get spawned inactively");
CAF_SET_LOGGER_SYS(this);
actor_config cfg{dummy_execution_unit(), nullptr};
cfg.mbox_factory = mailbox_factory();
auto res = make_actor<Impl>(next_actor_id(), node(), this, cfg,
std::forward<Ts>(xs)...);
auto ptr = static_cast<Impl*>(actor_cast<abstract_actor*>(res));
......@@ -790,6 +797,8 @@ private:
config_serv_ = std::move(x);
}
detail::mailbox_factory* mailbox_factory();
// -- member variables -------------------------------------------------------
/// Provides system-wide callbacks for several actor operations.
......
......@@ -478,6 +478,10 @@ actor_system_config::extract_config_file_path(string_list& args) {
}
}
detail::mailbox_factory* actor_system_config::mailbox_factory() {
return nullptr;
}
const settings& content(const actor_system_config& cfg) {
return cfg.content;
}
......
......@@ -32,6 +32,10 @@ namespace caf {
/// Configures an `actor_system` on startup.
class CAF_CORE_EXPORT actor_system_config {
public:
// -- friends ----------------------------------------------------------------
friend class actor_system;
// -- member types -----------------------------------------------------------
using hook_factory = std::function<io::hook*(actor_system&)>;
......@@ -303,6 +307,8 @@ protected:
config_option_set custom_options_;
private:
virtual detail::mailbox_factory* mailbox_factory();
void set_remainder(string_list args);
mutable std::vector<char*> c_args_remainder_;
......
......@@ -175,6 +175,9 @@ public:
static constexpr bool value = std::is_same_v<bool, result_type>;
};
template <class T1, class T2>
constexpr bool is_comparable_v = is_comparable<T1, T2>::value;
/// Checks whether `T` behaves like a forward iterator.
template <class T>
class is_forward_iterator {
......
......@@ -231,27 +231,31 @@ bool operator!=(std::nullptr_t, const intrusive_ptr<T>& x) {
// -- comparison to raw pointer ------------------------------------------------
/// @relates intrusive_ptr
template <class T>
bool operator==(const intrusive_ptr<T>& x, const T* y) {
return x.get() == y;
template <class T, class U>
detail::enable_if_t<detail::is_comparable<T*, U*>::value, bool>
operator==(const intrusive_ptr<T>& lhs, const U* rhs) {
return lhs.get() == rhs;
}
/// @relates intrusive_ptr
template <class T>
bool operator==(const T* x, const intrusive_ptr<T>& y) {
return x == y.get();
template <class T, class U>
detail::enable_if_t<detail::is_comparable<T*, U*>::value, bool>
operator==(const T* lhs, const intrusive_ptr<U>& rhs) {
return lhs == rhs.get();
}
/// @relates intrusive_ptr
template <class T>
bool operator!=(const intrusive_ptr<T>& x, const T* y) {
return x.get() != y;
template <class T, class U>
detail::enable_if_t<detail::is_comparable<T*, U*>::value, bool>
operator!=(const intrusive_ptr<T>& lhs, const U* rhs) {
return lhs.get() != rhs;
}
/// @relates intrusive_ptr
template <class T>
bool operator!=(const T* x, const intrusive_ptr<T>& y) {
return x != y.get();
template <class T, class U>
detail::enable_if_t<detail::is_comparable<T*, U*>::value, bool>
operator!=(const T* lhs, const intrusive_ptr<U>& rhs) {
return lhs != rhs.get();
}
// -- comparison to intrusive_pointer ------------------------------------------
......
......@@ -241,6 +241,11 @@ public:
return *mailbox_;
}
/// Checks whether this actor is fully initialized.
bool initialized() const noexcept {
return getf(is_initialized_flag);
}
// -- event handlers ---------------------------------------------------------
/// Sets a custom handler for unexpected messages.
......
......@@ -22,6 +22,8 @@ caf_add_component(
caf/test/block.cpp
caf/test/context.cpp
caf/test/factory.cpp
caf/test/fixture/deterministic.cpp
caf/test/fixture/deterministic.test.cpp
caf/test/fixture/flow.cpp
caf/test/fixture/flow.test.cpp
caf/test/given.cpp
......
......@@ -15,13 +15,13 @@ void context::on_enter(block* ptr) {
call_stack.push_back(ptr);
unwind_stack.clear();
path.push_back(ptr);
reporter::instance->begin_step(ptr);
reporter::instance().begin_step(ptr);
}
void context::on_leave(block* ptr) {
call_stack.pop_back();
unwind_stack.push_back(ptr);
reporter::instance->end_step(ptr);
reporter::instance().end_step(ptr);
}
bool context::activated(block* ptr) const noexcept {
......
// 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 {
fix_->drop_events();
}
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() {
// Note: we need clean up all remaining messages manually. This in turn may
// clean up actors as unreachable if the test did not consume all
// messages. Otherwise, the destructor of `sys` will wait for all
// actors, potentially waiting forever.
drop_events();
}
void deterministic::drop_events() {
// Note: We cannot just call `events_.clear()`, because that would potentially
// cause an actor to become unreachable and close its mailbox. This
// could call `pop_msg_impl` in turn, which then tries to alter the list
// while we're clearing it.
while (!events_.empty()) {
std::list<std::unique_ptr<scheduling_event>> tmp;
tmp.splice(tmp.end(), events_);
}
}
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([[maybe_unused]] 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;
/// Matches the values of the message. The evaluator will match a message
/// only if all individual values match the corresponding predicate.
///
/// The template parameter pack `xs...` contains a list of match expressions
/// that all must evaluate to true for a message to match. For each match
/// expression:
///
/// - Passing a value creates a predicate that matches the value exactly.
/// - Passing a predicate (a function object taking one argument and
/// returning `bool`) will match any value for which the predicate returns
/// `true`.
/// - Passing `std::ignore` accepts any value at that position.
/// - Passing a `std::reference_wrapper<T>` will match any value and stores
/// the value in the reference wrapper.
template <class... Us>
evaluator&& with(Us... xs) && {
static_assert(sizeof...(Ts) == sizeof...(Us));
static_assert((std::is_constructible_v<value_predicate<Ts>, Us> && ...));
with_ = message_predicate<Ts...>(std::move(xs)...);
return std::move(*this);
}
/// Adds a predicate for the sender of the next message that matches only if
/// the sender is `src`.
evaluator&& from(const strong_actor_ptr& src) && {
from_ = value_predicate<strong_actor_ptr>{std::move(src)};
return std::move(*this);
}
/// Adds a predicate for the sender of the next message that matches only if
/// the sender is `src`.
evaluator&& from(const actor& src) && {
from_ = value_predicate<strong_actor_ptr>{std::move(src)};
return std::move(*this);
}
/// Adds a predicate for the sender of the next message that matches only if
/// the sender is `src`.
template <class... Us>
evaluator&& from(const typed_actor<Us...>& src) && {
from_ = value_predicate<strong_actor_ptr>{std::move(src)};
return std::move(*this);
}
/// Adds a predicate for the sender of the next message that matches only
/// anonymous messages, i.e., messages without a sender.
evaluator&& from(std::nullptr_t) && {
return std::move(*this).from(strong_actor_ptr{});
}
/// Causes the evaluator to store the sender of a matched message in `src`.
evaluator&& from(std::reference_wrapper<strong_actor_ptr> src) && {
from_ = value_predicate<strong_actor_ptr>{src};
return std::move(*this);
}
/// Sets the target actor for this evaluator and evaluate the predicate.
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 -------------------------------------------------------
private:
// Note: this is put here because this member variable must be destroyed
// *after* the actor system (and thus must come before `sys` in
// the declaration order).
/// Stores all pending messages of scheduled actors.
std::list<std::unique_ptr<scheduling_event>> events_;
public:
/// Configures the actor system with deterministic scheduling.
config cfg;
/// The actor system instance for the tests.
actor_system sys;
private:
/// Removes all events from the queue.
void drop_events();
/// 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);
};
} // 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;
struct my_int {
int value;
};
template <class Inspector>
auto inspect(Inspector& f, my_int& x) {
return f.object(x).fields(f.field("value", x.value));
}
bool operator==(my_int lhs, my_int rhs) noexcept {
return lhs.value == rhs.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;
}
bool operator!=(my_int lhs, my_int rhs) noexcept {
return lhs.value != rhs.value;
}
CAF_BEGIN_TYPE_ID_BLOCK(test, caf::first_custom_type_id)
CAF_ADD_TYPE_ID(test, (my_int))
CAF_END_TYPE_ID_BLOCK(test)
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);
}
}
TEST("evaluator expressions can check or extract individual values") {
auto worker = sys.spawn([](caf::event_based_actor* self) -> caf::behavior {
self->set_default_handler(caf::drop);
return {
[](int32_t) {},
};
});
SECTION("omitting with() matches on the types only") {
anon_send(worker, 1);
check(!allow<std::string>().to(worker));
check(allow<int>().to(worker));
anon_send(worker, 1, "two", 3.0);
check(!allow<int>().to(worker));
check(allow<int, std::string, double>().to(worker));
anon_send(worker, 42, "hello world", 7.7);
check(allow<int, std::string, double>().to(worker));
}
SECTION("reference wrappers turn evaluators into extractors") {
auto tmp = 0;
anon_send(worker, 1);
check(allow<int>().with(std::ref(tmp)).to(worker));
check_eq(tmp, 1);
}
SECTION("std::ignore matches any value") {
anon_send(worker, 1);
check(allow<int>().with(std::ignore).to(worker));
anon_send(worker, 2);
check(allow<int>().with(std::ignore).to(worker));
anon_send(worker, 3);
check(allow<int>().with(std::ignore).to(worker));
anon_send(worker, 1, 2, 3);
check(!allow<int, int, int>().with(1, std::ignore, 4).to(worker));
check(!allow<int, int, int>().with(2, std::ignore, 3).to(worker));
check(allow<int, int, int>().with(1, std::ignore, 3).to(worker));
}
SECTION("value predicates allow user-defined types") {
anon_send(worker, my_int{1});
check(allow<my_int>().to(worker));
anon_send(worker, my_int{1});
check(allow<my_int>().with(std::ignore).to(worker));
anon_send(worker, my_int{1});
check(!allow<my_int>().with(my_int{2}).to(worker));
check(allow<my_int>().with(my_int{1}).to(worker));
anon_send(worker, my_int{1});
check(allow<my_int>().with(1).to(worker));
auto tmp = my_int{0};
anon_send(worker, my_int{42});
check(allow<my_int>().with(std::ref(tmp)).to(worker));
check_eq(tmp.value, 42);
}
SECTION("value predicates allow user-defined predicates") {
auto le2 = [](my_int x) { return x.value <= 2; };
anon_send(worker, my_int{1});
check(allow<my_int>().with(le2).to(worker));
anon_send(worker, my_int{2});
check(allow<my_int>().with(le2).to(worker));
anon_send(worker, my_int{3});
check(!allow<my_int>().with(le2).to(worker));
}
}
} // WITH_FIXTURE(fixture::deterministic)
CAF_TEST_MAIN(caf::id_block::test)
......@@ -22,8 +22,6 @@ reporter::~reporter() {
// nop
}
reporter* reporter::instance;
namespace {
/// Implements a mini-DSL for colored output:
......@@ -334,8 +332,8 @@ public:
set_live();
format_to(colored(),
"{0:{1}}$R(error): check failed\n"
"{0:{1}} loc: $C({3}):$Y({4})$0\n"
"{0:{1}} check: {5}\n",
"{0:{1}} loc: $C({2}):$Y({3})$0\n"
"{0:{1}} check: {4}\n",
' ', indent_, location.file_name(), location.line(), arg);
}
......@@ -388,8 +386,10 @@ public:
' ', indent_, location.file_name(), location.line(), msg);
}
void verbosity(unsigned level) override {
unsigned verbosity(unsigned level) override {
auto result = level_;
level_ = level;
return result;
}
void no_colors(bool new_value) override {
......@@ -470,8 +470,20 @@ private:
context_ptr current_ctx_;
};
reporter* global_instance;
} // namespace
reporter& reporter::instance() {
if (global_instance == nullptr)
CAF_RAISE_ERROR("no reporter instance available");
return *global_instance;
}
void reporter::instance(reporter* ptr) {
global_instance = ptr;
}
std::unique_ptr<reporter> reporter::make_default() {
return std::make_unique<default_reporter>();
}
......
......@@ -75,7 +75,8 @@ public:
info(std::string_view msg, const detail::source_location& location)
= 0;
virtual void verbosity(unsigned level) = 0;
/// Sets the verbosity level of the reporter and returns the previous value.
virtual unsigned verbosity(unsigned level) = 0;
/// Sets whether the reporter disables colored output even when writing to a
/// TTY.
......@@ -93,8 +94,11 @@ public:
/// Returns statistics for the entire run.
virtual stats total_stats() = 0;
/// Stores a pointer to the currently active reporter.
static reporter* instance;
/// Returns the registered reporter instance.
static reporter& instance();
/// Sets the reporter instance for the current test run.
static void instance(reporter* ptr);
/// Creates a default reporter that writes to the standard output.
static std::unique_ptr<reporter> make_default();
......
......@@ -10,13 +10,23 @@
#include "caf/test/scope.hpp"
#include "caf/test/test.hpp"
#include "caf/detail/scope_guard.hpp"
namespace caf::test {
namespace {
thread_local runnable* current_runnable;
} // namespace
runnable::~runnable() {
// nop
}
void runnable::run() {
current_runnable = this;
auto guard = detail::make_scope_guard([] { current_runnable = nullptr; });
switch (root_type_) {
case block_type::scenario:
if (auto guard = ctx_->get<scenario>(0, description_, loc_)->commit()) {
......@@ -41,13 +51,20 @@ void runnable::run() {
bool runnable::check(bool value, const detail::source_location& location) {
if (value) {
reporter::instance->pass(location);
reporter::instance().pass(location);
} else {
reporter::instance->fail("should be true", location);
reporter::instance().fail("should be true", location);
}
return value;
}
runnable& runnable::current() {
auto ptr = current_runnable;
if (!ptr)
CAF_RAISE_ERROR(std::logic_error, "no current runnable");
return *ptr;
}
block& runnable::current_block() {
if (ctx_->call_stack.empty())
CAF_RAISE_ERROR(std::logic_error, "no current block");
......
......@@ -12,8 +12,10 @@
#include "caf/config.hpp"
#include "caf/deep_to_string.hpp"
#include "caf/detail/format.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/detail/source_location.hpp"
#include "caf/detail/test_export.hpp"
#include "caf/raise_error.hpp"
#include <string_view>
......@@ -42,14 +44,26 @@ public:
/// Runs the next branch of the test.
void run();
/// Generates a message with the INFO severity level.
template <class... Ts>
[[noreturn]] void fail(detail::format_string_with_location fwl, Ts&&... xs) {
if constexpr (sizeof...(Ts) > 0) {
auto msg = detail::format(fwl.value, std::forward<Ts>(xs)...);
reporter::instance().fail(msg, fwl.location);
} else {
reporter::instance().fail(fwl.value, fwl.location);
}
CAF_RAISE_ERROR(std::logic_error, "requirement failed: abort test");
}
/// Generates a message with the INFO severity level.
template <class... Ts>
void info(detail::format_string_with_location fwl, Ts&&... xs) {
if constexpr (sizeof...(Ts) > 0) {
auto msg = detail::format(fwl.value, std::forward<Ts>(xs)...);
reporter::instance->info(msg, fwl.location);
reporter::instance().info(msg, fwl.location);
} else {
reporter::instance->info(fwl.value, fwl.location);
reporter::instance().info(fwl.value, fwl.location);
}
}
......@@ -60,10 +74,10 @@ public:
= detail::source_location::current()) {
assert_save_comparison<T0, T1>();
if (lhs == rhs) {
reporter::instance->pass(location);
reporter::instance().pass(location);
return true;
}
reporter::instance->fail(binary_predicate::eq, stringify(lhs),
reporter::instance().fail(binary_predicate::eq, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -75,10 +89,10 @@ public:
= detail::source_location::current()) {
assert_save_comparison<T0, T1>();
if (lhs != rhs) {
reporter::instance->pass(location);
reporter::instance().pass(location);
return true;
}
reporter::instance->fail(binary_predicate::ne, stringify(lhs),
reporter::instance().fail(binary_predicate::ne, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -90,10 +104,10 @@ public:
= detail::source_location::current()) {
assert_save_comparison<T0, T1>();
if (lhs < rhs) {
reporter::instance->pass(location);
reporter::instance().pass(location);
return true;
}
reporter::instance->fail(binary_predicate::lt, stringify(lhs),
reporter::instance().fail(binary_predicate::lt, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -105,10 +119,10 @@ public:
= detail::source_location::current()) {
assert_save_comparison<T0, T1>();
if (lhs <= rhs) {
reporter::instance->pass(location);
reporter::instance().pass(location);
return true;
}
reporter::instance->fail(binary_predicate::le, stringify(lhs),
reporter::instance().fail(binary_predicate::le, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -120,10 +134,10 @@ public:
= detail::source_location::current()) {
assert_save_comparison<T0, T1>();
if (lhs > rhs) {
reporter::instance->pass(location);
reporter::instance().pass(location);
return true;
}
reporter::instance->fail(binary_predicate::gt, stringify(lhs),
reporter::instance().fail(binary_predicate::gt, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -135,58 +149,118 @@ public:
= detail::source_location::current()) {
assert_save_comparison<T0, T1>();
if (lhs >= rhs) {
reporter::instance->pass(location);
reporter::instance().pass(location);
return true;
}
reporter::instance->fail(binary_predicate::ge, stringify(lhs),
reporter::instance().fail(binary_predicate::ge, stringify(lhs),
stringify(rhs), location);
return false;
}
/// Checks whether `value` is `true`.
bool check(bool value, const detail::source_location& location
= detail::source_location::current());
/// Returns the `runnable` instance that is currently running.
static runnable& current();
block& current_block();
template <class Expr>
void should_fail(Expr&& expr, const caf::detail::source_location& location
= caf::detail::source_location::current()) {
auto& rep = reporter::instance();
auto lvl = rep.verbosity(CAF_LOG_LEVEL_QUIET);
auto before = rep.test_stats();
{
auto lvl_guard = detail::make_scope_guard([&] { rep.verbosity(lvl); });
expr();
}
auto after = rep.test_stats();
auto passed_count_ok = before.passed == after.passed;
auto failed_count_ok = before.failed + 1 == after.failed;
if (passed_count_ok && failed_count_ok) {
reporter::instance().pass(location);
rep.test_stats({before.passed + 1, before.failed});
} else {
reporter::instance().fail("nested check should fail", location);
rep.test_stats({before.passed, before.failed + 1});
}
}
#ifdef CAF_ENABLE_EXCEPTIONS
template <class Exception = void, class CodeBlock>
void check_throws(CodeBlock&& fn, const detail::source_location& location
/// Checks whether `expr()` throws an exception of type `Exception`.
template <class Exception = void, class Expr>
void check_throws(Expr&& expr, const detail::source_location& location
= detail::source_location::current()) {
if constexpr (std::is_same_v<Exception, void>) {
try {
fn();
expr();
} catch (...) {
reporter::instance->pass(location);
reporter::instance().pass(location);
return;
}
reporter::instance->fail("throws", location);
reporter::instance().fail("throws", location);
} else {
try {
fn();
expr();
} catch (const Exception&) {
reporter::instance->pass(location);
reporter::instance().pass(location);
return;
} catch (...) {
reporter::instance->fail("throws Exception", location);
reporter::instance().fail("throws Exception", location);
return;
}
reporter::instance->fail("throws Exception", location);
reporter::instance().fail("throws Exception", location);
}
}
#endif
template <class Expr>
void should_fail(Expr&& expr, const caf::detail::source_location& location
/// Checks whether `expr()` throws an exception of type `Exception` and
/// increases the failure count.
template <class Exception = void, class Expr>
void should_fail_with_exception(Expr&& expr,
const caf::detail::source_location& location
= caf::detail::source_location::current()) {
auto* rep = reporter::instance;
auto before = rep->test_stats();
auto& rep = reporter::instance();
auto before = rep.test_stats();
auto lvl = rep.verbosity(CAF_LOG_LEVEL_QUIET);
auto caught = false;
if constexpr (std::is_same_v<Exception, void>) {
try {
expr();
} catch (...) {
caught = true;
}
} else {
try {
expr();
auto after = rep->test_stats();
check_eq(before.passed, after.passed, location);
if (check_eq(before.failed + 1, after.failed, location))
rep->test_stats({before.passed + 1, before.failed});
} catch (const Exception&) {
caught = true;
} catch (...) {
// TODO: print error message
}
}
rep.verbosity(lvl);
auto after = rep.test_stats();
auto passed_count_ok = before.passed == after.passed;
auto failed_count_ok = before.failed + 1 == after.failed;
if (caught && passed_count_ok && failed_count_ok) {
reporter::instance().pass(location);
rep.test_stats({before.passed + 1, before.failed});
} else {
if (!caught) {
reporter::instance().fail("nested check should throw an Exception",
location);
} else if (!passed_count_ok || !failed_count_ok) {
reporter::instance().fail("nested check should fail", location);
}
rep.test_stats({before.passed, before.failed + 1});
}
}
#endif
protected:
context_ptr ctx_;
std::string_view description_;
......
......@@ -131,7 +131,7 @@ runner::runner() : suites_(caf::test::registry::suites()) {
int runner::run(int argc, char** argv) {
auto default_reporter = reporter::make_default();
reporter::instance = default_reporter.get();
reporter::instance(default_reporter.get());
if (auto [ok, help_printed] = parse_cli(argc, argv); !ok) {
return EXIT_FAILURE;
} else if (help_printed) {
......@@ -240,7 +240,7 @@ runner::parse_cli_result runner::parse_cli(int argc, char** argv) {
*verbosity);
return {false, true};
}
reporter::instance->verbosity(*level);
reporter::instance().verbosity(*level);
}
return {true, false};
}
......
......@@ -8,8 +8,8 @@
using caf::test::block_type;
TEST("tests can contain different types of checks") {
auto* rep = caf::test::reporter::instance;
auto stats = rep->test_stats();
auto& rep = caf::test::reporter::instance();
auto stats = rep.test_stats();
SECTION("check_ne checks for inequality") {
check_ne(0, 1);
should_fail([this]() { check_ne(0, 0); });
......@@ -38,16 +38,16 @@ TEST("tests can contain different types of checks") {
should_fail([this]() { check_lt(1, 1); });
should_fail([this]() { check_lt(2, 1); });
}
info("this test had {} checks", rep->test_stats().total());
info("this test had {} checks", rep.test_stats().total());
}
TEST("failed checks increment the failed counter") {
check_eq(1, 2);
auto stats = caf::test::reporter::instance->test_stats();
auto stats = caf::test::reporter::instance().test_stats();
check_eq(stats.passed, 0u);
check_eq(stats.failed, 1u);
info("reset error count to not fail the test");
caf::test::reporter::instance->test_stats({2, 0});
caf::test::reporter::instance().test_stats({2, 0});
}
TEST("each run starts with fresh local variables") {
......
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