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 diff is collapsed.
This diff is collapsed.
......@@ -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