Commit cd8363aa authored by Dominik Charousset's avatar Dominik Charousset

Merge pull request #1518

parents c677bf5b faaaf3d5
...@@ -38,7 +38,7 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -38,7 +38,7 @@ is based on [Keep a Changelog](https://keepachangelog.com).
- We renamed `caf::flow::item_publisher` to `caf::flow::multicaster` to better - We renamed `caf::flow::item_publisher` to `caf::flow::multicaster` to better
reflect its purpose and to avoid confusion with the new reflect its purpose and to avoid confusion with the new
`caf::async::publisher`. `caf::async::publisher`.
- When failing to deserialize a request, the sender will receive an error of - When failing to deserialize a request, the sender will receive an error of
kind `sec::malformed_message`. kind `sec::malformed_message`.
### Fixed ### Fixed
...@@ -52,6 +52,8 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -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 `on_complete` on its subscriber on the first activity of the source
observable. Previously, the created observable would never reach its threshold observable. Previously, the created observable would never reach its threshold
and attempt to buffer all values indefinitely. 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 ## [0.19.2] - 2023-06-13
......
...@@ -541,4 +541,8 @@ void actor_system::release_private_thread(detail::private_thread* ptr) { ...@@ -541,4 +541,8 @@ void actor_system::release_private_thread(detail::private_thread* ptr) {
private_threads_.release(ptr); private_threads_.release(ptr);
} }
detail::mailbox_factory* actor_system::mailbox_factory() {
return cfg_.mailbox_factory();
}
} // namespace caf } // namespace caf
...@@ -398,6 +398,7 @@ public: ...@@ -398,6 +398,7 @@ public:
infer_handle_from_class_t<C> spawn(Ts&&... xs) { infer_handle_from_class_t<C> spawn(Ts&&... xs) {
check_invariants<C>(); check_invariants<C>();
actor_config cfg; actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_impl<C, Os>(cfg, detail::spawn_fwd<Ts>(xs)...); return spawn_impl<C, Os>(cfg, detail::spawn_fwd<Ts>(xs)...);
} }
...@@ -436,6 +437,7 @@ public: ...@@ -436,6 +437,7 @@ public:
static_assert(spawnable, static_assert(spawnable,
"cannot spawn function-based actor with given arguments"); "cannot spawn function-based actor with given arguments");
actor_config cfg; actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_functor<Os>(detail::bool_token<spawnable>{}, cfg, fun, return spawn_functor<Os>(detail::bool_token<spawnable>{}, cfg, fun,
std::forward<Ts>(xs)...); std::forward<Ts>(xs)...);
} }
...@@ -500,6 +502,7 @@ public: ...@@ -500,6 +502,7 @@ public:
infer_handle_from_fun_t<F> infer_handle_from_fun_t<F>
spawn_in_groups(std::initializer_list<group> gs, F fun, Ts&&... xs) { spawn_in_groups(std::initializer_list<group> gs, F fun, Ts&&... xs) {
actor_config cfg; actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_fun_in_groups<Os>(cfg, gs.begin(), gs.end(), fun, return spawn_fun_in_groups<Os>(cfg, gs.begin(), gs.end(), fun,
std::forward<Ts>(xs)...); std::forward<Ts>(xs)...);
} }
...@@ -508,6 +511,7 @@ public: ...@@ -508,6 +511,7 @@ public:
template <spawn_options Os = no_spawn_options, class Gs, class F, class... Ts> 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) { infer_handle_from_fun_t<F> spawn_in_groups(const Gs& gs, F fun, Ts&&... xs) {
actor_config cfg; actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_fun_in_groups<Os>(cfg, gs.begin(), gs.end(), fun, return spawn_fun_in_groups<Os>(cfg, gs.begin(), gs.end(), fun,
std::forward<Ts>(xs)...); std::forward<Ts>(xs)...);
} }
...@@ -524,6 +528,7 @@ public: ...@@ -524,6 +528,7 @@ public:
infer_handle_from_class_t<T> infer_handle_from_class_t<T>
spawn_in_groups(std::initializer_list<group> gs, Ts&&... xs) { spawn_in_groups(std::initializer_list<group> gs, Ts&&... xs) {
actor_config cfg; actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_class_in_groups<T, Os>(cfg, gs.begin(), gs.end(), return spawn_class_in_groups<T, Os>(cfg, gs.begin(), gs.end(),
std::forward<Ts>(xs)...); std::forward<Ts>(xs)...);
} }
...@@ -532,6 +537,7 @@ public: ...@@ -532,6 +537,7 @@ public:
template <class T, spawn_options Os = no_spawn_options, class Gs, class... Ts> 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) { infer_handle_from_class_t<T> spawn_in_groups(const Gs& gs, Ts&&... xs) {
actor_config cfg; actor_config cfg;
cfg.mbox_factory = mailbox_factory();
return spawn_class_in_groups<T, Os>(cfg, gs.begin(), gs.end(), return spawn_class_in_groups<T, Os>(cfg, gs.begin(), gs.end(),
std::forward<Ts>(xs)...); std::forward<Ts>(xs)...);
} }
...@@ -696,6 +702,7 @@ public: ...@@ -696,6 +702,7 @@ public:
"only scheduled actors may get spawned inactively"); "only scheduled actors may get spawned inactively");
CAF_SET_LOGGER_SYS(this); CAF_SET_LOGGER_SYS(this);
actor_config cfg{dummy_execution_unit(), nullptr}; actor_config cfg{dummy_execution_unit(), nullptr};
cfg.mbox_factory = mailbox_factory();
auto res = make_actor<Impl>(next_actor_id(), node(), this, cfg, auto res = make_actor<Impl>(next_actor_id(), node(), this, cfg,
std::forward<Ts>(xs)...); std::forward<Ts>(xs)...);
auto ptr = static_cast<Impl*>(actor_cast<abstract_actor*>(res)); auto ptr = static_cast<Impl*>(actor_cast<abstract_actor*>(res));
...@@ -790,6 +797,8 @@ private: ...@@ -790,6 +797,8 @@ private:
config_serv_ = std::move(x); config_serv_ = std::move(x);
} }
detail::mailbox_factory* mailbox_factory();
// -- member variables ------------------------------------------------------- // -- member variables -------------------------------------------------------
/// Provides system-wide callbacks for several actor operations. /// Provides system-wide callbacks for several actor operations.
......
...@@ -478,6 +478,10 @@ actor_system_config::extract_config_file_path(string_list& args) { ...@@ -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) { const settings& content(const actor_system_config& cfg) {
return cfg.content; return cfg.content;
} }
......
...@@ -32,6 +32,10 @@ namespace caf { ...@@ -32,6 +32,10 @@ namespace caf {
/// Configures an `actor_system` on startup. /// Configures an `actor_system` on startup.
class CAF_CORE_EXPORT actor_system_config { class CAF_CORE_EXPORT actor_system_config {
public: public:
// -- friends ----------------------------------------------------------------
friend class actor_system;
// -- member types ----------------------------------------------------------- // -- member types -----------------------------------------------------------
using hook_factory = std::function<io::hook*(actor_system&)>; using hook_factory = std::function<io::hook*(actor_system&)>;
...@@ -303,6 +307,8 @@ protected: ...@@ -303,6 +307,8 @@ protected:
config_option_set custom_options_; config_option_set custom_options_;
private: private:
virtual detail::mailbox_factory* mailbox_factory();
void set_remainder(string_list args); void set_remainder(string_list args);
mutable std::vector<char*> c_args_remainder_; mutable std::vector<char*> c_args_remainder_;
......
...@@ -175,6 +175,9 @@ public: ...@@ -175,6 +175,9 @@ public:
static constexpr bool value = std::is_same_v<bool, result_type>; 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. /// Checks whether `T` behaves like a forward iterator.
template <class T> template <class T>
class is_forward_iterator { class is_forward_iterator {
......
...@@ -231,27 +231,31 @@ bool operator!=(std::nullptr_t, const intrusive_ptr<T>& x) { ...@@ -231,27 +231,31 @@ bool operator!=(std::nullptr_t, const intrusive_ptr<T>& x) {
// -- comparison to raw pointer ------------------------------------------------ // -- comparison to raw pointer ------------------------------------------------
/// @relates intrusive_ptr /// @relates intrusive_ptr
template <class T> template <class T, class U>
bool operator==(const intrusive_ptr<T>& x, const T* y) { detail::enable_if_t<detail::is_comparable<T*, U*>::value, bool>
return x.get() == y; operator==(const intrusive_ptr<T>& lhs, const U* rhs) {
return lhs.get() == rhs;
} }
/// @relates intrusive_ptr /// @relates intrusive_ptr
template <class T> template <class T, class U>
bool operator==(const T* x, const intrusive_ptr<T>& y) { detail::enable_if_t<detail::is_comparable<T*, U*>::value, bool>
return x == y.get(); operator==(const T* lhs, const intrusive_ptr<U>& rhs) {
return lhs == rhs.get();
} }
/// @relates intrusive_ptr /// @relates intrusive_ptr
template <class T> template <class T, class U>
bool operator!=(const intrusive_ptr<T>& x, const T* y) { detail::enable_if_t<detail::is_comparable<T*, U*>::value, bool>
return x.get() != y; operator!=(const intrusive_ptr<T>& lhs, const U* rhs) {
return lhs.get() != rhs;
} }
/// @relates intrusive_ptr /// @relates intrusive_ptr
template <class T> template <class T, class U>
bool operator!=(const T* x, const intrusive_ptr<T>& y) { detail::enable_if_t<detail::is_comparable<T*, U*>::value, bool>
return x != y.get(); operator!=(const T* lhs, const intrusive_ptr<U>& rhs) {
return lhs != rhs.get();
} }
// -- comparison to intrusive_pointer ------------------------------------------ // -- comparison to intrusive_pointer ------------------------------------------
......
...@@ -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/fixture/flow.cpp caf/test/fixture/flow.cpp
caf/test/fixture/flow.test.cpp caf/test/fixture/flow.test.cpp
caf/test/given.cpp caf/test/given.cpp
......
...@@ -15,13 +15,13 @@ void context::on_enter(block* ptr) { ...@@ -15,13 +15,13 @@ void context::on_enter(block* ptr) {
call_stack.push_back(ptr); call_stack.push_back(ptr);
unwind_stack.clear(); unwind_stack.clear();
path.push_back(ptr); path.push_back(ptr);
reporter::instance->begin_step(ptr); reporter::instance().begin_step(ptr);
} }
void context::on_leave(block* ptr) { void context::on_leave(block* ptr) {
call_stack.pop_back(); call_stack.pop_back();
unwind_stack.push_back(ptr); unwind_stack.push_back(ptr);
reporter::instance->end_step(ptr); reporter::instance().end_step(ptr);
} }
bool context::activated(block* ptr) const noexcept { 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 diff is collapsed.
This diff is collapsed.
...@@ -22,8 +22,6 @@ reporter::~reporter() { ...@@ -22,8 +22,6 @@ reporter::~reporter() {
// nop // nop
} }
reporter* reporter::instance;
namespace { namespace {
/// Implements a mini-DSL for colored output: /// Implements a mini-DSL for colored output:
...@@ -334,8 +332,8 @@ public: ...@@ -334,8 +332,8 @@ public:
set_live(); set_live();
format_to(colored(), format_to(colored(),
"{0:{1}}$R(error): check failed\n" "{0:{1}}$R(error): check failed\n"
"{0:{1}} loc: $C({3}):$Y({4})$0\n" "{0:{1}} loc: $C({2}):$Y({3})$0\n"
"{0:{1}} check: {5}\n", "{0:{1}} check: {4}\n",
' ', indent_, location.file_name(), location.line(), arg); ' ', indent_, location.file_name(), location.line(), arg);
} }
...@@ -388,8 +386,10 @@ public: ...@@ -388,8 +386,10 @@ public:
' ', indent_, location.file_name(), location.line(), msg); ' ', indent_, location.file_name(), location.line(), msg);
} }
void verbosity(unsigned level) override { unsigned verbosity(unsigned level) override {
auto result = level_;
level_ = level; level_ = level;
return result;
} }
void no_colors(bool new_value) override { void no_colors(bool new_value) override {
...@@ -470,8 +470,20 @@ private: ...@@ -470,8 +470,20 @@ private:
context_ptr current_ctx_; context_ptr current_ctx_;
}; };
reporter* global_instance;
} // namespace } // 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() { std::unique_ptr<reporter> reporter::make_default() {
return std::make_unique<default_reporter>(); return std::make_unique<default_reporter>();
} }
......
...@@ -75,7 +75,8 @@ public: ...@@ -75,7 +75,8 @@ public:
info(std::string_view msg, const detail::source_location& location) info(std::string_view msg, const detail::source_location& location)
= 0; = 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 /// Sets whether the reporter disables colored output even when writing to a
/// TTY. /// TTY.
...@@ -93,8 +94,11 @@ public: ...@@ -93,8 +94,11 @@ public:
/// Returns statistics for the entire run. /// Returns statistics for the entire run.
virtual stats total_stats() = 0; virtual stats total_stats() = 0;
/// Stores a pointer to the currently active reporter. /// Returns the registered reporter instance.
static 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. /// Creates a default reporter that writes to the standard output.
static std::unique_ptr<reporter> make_default(); static std::unique_ptr<reporter> make_default();
......
...@@ -10,13 +10,23 @@ ...@@ -10,13 +10,23 @@
#include "caf/test/scope.hpp" #include "caf/test/scope.hpp"
#include "caf/test/test.hpp" #include "caf/test/test.hpp"
#include "caf/detail/scope_guard.hpp"
namespace caf::test { namespace caf::test {
namespace {
thread_local runnable* current_runnable;
} // namespace
runnable::~runnable() { runnable::~runnable() {
// nop // nop
} }
void runnable::run() { void runnable::run() {
current_runnable = this;
auto guard = detail::make_scope_guard([] { current_runnable = nullptr; });
switch (root_type_) { switch (root_type_) {
case block_type::scenario: case block_type::scenario:
if (auto guard = ctx_->get<scenario>(0, description_, loc_)->commit()) { if (auto guard = ctx_->get<scenario>(0, description_, loc_)->commit()) {
...@@ -41,13 +51,20 @@ void runnable::run() { ...@@ -41,13 +51,20 @@ void runnable::run() {
bool runnable::check(bool value, const detail::source_location& location) { bool runnable::check(bool value, const detail::source_location& location) {
if (value) { if (value) {
reporter::instance->pass(location); reporter::instance().pass(location);
} else { } else {
reporter::instance->fail("should be true", location); reporter::instance().fail("should be true", location);
} }
return value; 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() { block& runnable::current_block() {
if (ctx_->call_stack.empty()) if (ctx_->call_stack.empty())
CAF_RAISE_ERROR(std::logic_error, "no current block"); CAF_RAISE_ERROR(std::logic_error, "no current block");
......
...@@ -12,8 +12,10 @@ ...@@ -12,8 +12,10 @@
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/deep_to_string.hpp" #include "caf/deep_to_string.hpp"
#include "caf/detail/format.hpp" #include "caf/detail/format.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/detail/source_location.hpp" #include "caf/detail/source_location.hpp"
#include "caf/detail/test_export.hpp" #include "caf/detail/test_export.hpp"
#include "caf/raise_error.hpp"
#include <string_view> #include <string_view>
...@@ -42,14 +44,26 @@ public: ...@@ -42,14 +44,26 @@ public:
/// Runs the next branch of the test. /// Runs the next branch of the test.
void run(); 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. /// Generates a message with the INFO severity level.
template <class... Ts> template <class... Ts>
void info(detail::format_string_with_location fwl, Ts&&... xs) { void info(detail::format_string_with_location fwl, Ts&&... xs) {
if constexpr (sizeof...(Ts) > 0) { if constexpr (sizeof...(Ts) > 0) {
auto msg = detail::format(fwl.value, std::forward<Ts>(xs)...); auto msg = detail::format(fwl.value, std::forward<Ts>(xs)...);
reporter::instance->info(msg, fwl.location); reporter::instance().info(msg, fwl.location);
} else { } else {
reporter::instance->info(fwl.value, fwl.location); reporter::instance().info(fwl.value, fwl.location);
} }
} }
...@@ -60,11 +74,11 @@ public: ...@@ -60,11 +74,11 @@ public:
= detail::source_location::current()) { = detail::source_location::current()) {
assert_save_comparison<T0, T1>(); assert_save_comparison<T0, T1>();
if (lhs == rhs) { if (lhs == rhs) {
reporter::instance->pass(location); reporter::instance().pass(location);
return true; return true;
} }
reporter::instance->fail(binary_predicate::eq, stringify(lhs), reporter::instance().fail(binary_predicate::eq, stringify(lhs),
stringify(rhs), location); stringify(rhs), location);
return false; return false;
} }
...@@ -75,11 +89,11 @@ public: ...@@ -75,11 +89,11 @@ public:
= detail::source_location::current()) { = detail::source_location::current()) {
assert_save_comparison<T0, T1>(); assert_save_comparison<T0, T1>();
if (lhs != rhs) { if (lhs != rhs) {
reporter::instance->pass(location); reporter::instance().pass(location);
return true; return true;
} }
reporter::instance->fail(binary_predicate::ne, stringify(lhs), reporter::instance().fail(binary_predicate::ne, stringify(lhs),
stringify(rhs), location); stringify(rhs), location);
return false; return false;
} }
...@@ -90,11 +104,11 @@ public: ...@@ -90,11 +104,11 @@ public:
= detail::source_location::current()) { = detail::source_location::current()) {
assert_save_comparison<T0, T1>(); assert_save_comparison<T0, T1>();
if (lhs < rhs) { if (lhs < rhs) {
reporter::instance->pass(location); reporter::instance().pass(location);
return true; return true;
} }
reporter::instance->fail(binary_predicate::lt, stringify(lhs), reporter::instance().fail(binary_predicate::lt, stringify(lhs),
stringify(rhs), location); stringify(rhs), location);
return false; return false;
} }
...@@ -105,11 +119,11 @@ public: ...@@ -105,11 +119,11 @@ public:
= detail::source_location::current()) { = detail::source_location::current()) {
assert_save_comparison<T0, T1>(); assert_save_comparison<T0, T1>();
if (lhs <= rhs) { if (lhs <= rhs) {
reporter::instance->pass(location); reporter::instance().pass(location);
return true; return true;
} }
reporter::instance->fail(binary_predicate::le, stringify(lhs), reporter::instance().fail(binary_predicate::le, stringify(lhs),
stringify(rhs), location); stringify(rhs), location);
return false; return false;
} }
...@@ -120,11 +134,11 @@ public: ...@@ -120,11 +134,11 @@ public:
= detail::source_location::current()) { = detail::source_location::current()) {
assert_save_comparison<T0, T1>(); assert_save_comparison<T0, T1>();
if (lhs > rhs) { if (lhs > rhs) {
reporter::instance->pass(location); reporter::instance().pass(location);
return true; return true;
} }
reporter::instance->fail(binary_predicate::gt, stringify(lhs), reporter::instance().fail(binary_predicate::gt, stringify(lhs),
stringify(rhs), location); stringify(rhs), location);
return false; return false;
} }
...@@ -135,58 +149,118 @@ public: ...@@ -135,58 +149,118 @@ public:
= detail::source_location::current()) { = detail::source_location::current()) {
assert_save_comparison<T0, T1>(); assert_save_comparison<T0, T1>();
if (lhs >= rhs) { if (lhs >= rhs) {
reporter::instance->pass(location); reporter::instance().pass(location);
return true; return true;
} }
reporter::instance->fail(binary_predicate::ge, stringify(lhs), reporter::instance().fail(binary_predicate::ge, stringify(lhs),
stringify(rhs), location); stringify(rhs), location);
return false; return false;
} }
/// Checks whether `value` is `true`.
bool check(bool value, const detail::source_location& location bool check(bool value, const detail::source_location& location
= detail::source_location::current()); = detail::source_location::current());
/// Returns the `runnable` instance that is currently running.
static runnable& current();
block& current_block(); 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 #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`.
= detail::source_location::current()) { 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>) { if constexpr (std::is_same_v<Exception, void>) {
try { try {
fn(); expr();
} catch (...) { } catch (...) {
reporter::instance->pass(location); reporter::instance().pass(location);
return; return;
} }
reporter::instance->fail("throws", location); reporter::instance().fail("throws", location);
} else { } else {
try { try {
fn(); expr();
} catch (const Exception&) { } catch (const Exception&) {
reporter::instance->pass(location); reporter::instance().pass(location);
return; return;
} catch (...) { } catch (...) {
reporter::instance->fail("throws Exception", location); reporter::instance().fail("throws Exception", location);
return; return;
} }
reporter::instance->fail("throws Exception", location); reporter::instance().fail("throws Exception", location);
} }
} }
#endif
template <class Expr> /// Checks whether `expr()` throws an exception of type `Exception` and
void should_fail(Expr&& expr, const caf::detail::source_location& location /// increases the failure count.
= caf::detail::source_location::current()) { template <class Exception = void, class Expr>
auto* rep = reporter::instance; void should_fail_with_exception(Expr&& expr,
auto before = rep->test_stats(); const caf::detail::source_location& location
expr(); = caf::detail::source_location::current()) {
auto after = rep->test_stats(); auto& rep = reporter::instance();
check_eq(before.passed, after.passed, location); auto before = rep.test_stats();
if (check_eq(before.failed + 1, after.failed, location)) auto lvl = rep.verbosity(CAF_LOG_LEVEL_QUIET);
rep->test_stats({before.passed + 1, before.failed}); auto caught = false;
if constexpr (std::is_same_v<Exception, void>) {
try {
expr();
} catch (...) {
caught = true;
}
} else {
try {
expr();
} 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: protected:
context_ptr ctx_; context_ptr ctx_;
std::string_view description_; std::string_view description_;
......
...@@ -131,7 +131,7 @@ runner::runner() : suites_(caf::test::registry::suites()) { ...@@ -131,7 +131,7 @@ runner::runner() : suites_(caf::test::registry::suites()) {
int runner::run(int argc, char** argv) { int runner::run(int argc, char** argv) {
auto default_reporter = reporter::make_default(); 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) { if (auto [ok, help_printed] = parse_cli(argc, argv); !ok) {
return EXIT_FAILURE; return EXIT_FAILURE;
} else if (help_printed) { } else if (help_printed) {
...@@ -240,7 +240,7 @@ runner::parse_cli_result runner::parse_cli(int argc, char** argv) { ...@@ -240,7 +240,7 @@ runner::parse_cli_result runner::parse_cli(int argc, char** argv) {
*verbosity); *verbosity);
return {false, true}; return {false, true};
} }
reporter::instance->verbosity(*level); reporter::instance().verbosity(*level);
} }
return {true, false}; return {true, false};
} }
......
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
using caf::test::block_type; using caf::test::block_type;
TEST("tests can contain different types of checks") { TEST("tests can contain different types of checks") {
auto* rep = caf::test::reporter::instance; auto& rep = caf::test::reporter::instance();
auto stats = rep->test_stats(); auto stats = rep.test_stats();
SECTION("check_ne checks for inequality") { SECTION("check_ne checks for inequality") {
check_ne(0, 1); check_ne(0, 1);
should_fail([this]() { check_ne(0, 0); }); should_fail([this]() { check_ne(0, 0); });
...@@ -38,16 +38,16 @@ TEST("tests can contain different types of checks") { ...@@ -38,16 +38,16 @@ TEST("tests can contain different types of checks") {
should_fail([this]() { check_lt(1, 1); }); should_fail([this]() { check_lt(1, 1); });
should_fail([this]() { check_lt(2, 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") { TEST("failed checks increment the failed counter") {
check_eq(1, 2); 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.passed, 0u);
check_eq(stats.failed, 1u); check_eq(stats.failed, 1u);
info("reset error count to not fail the test"); 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") { 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