Commit faaaf3d5 authored by Dominik Charousset's avatar Dominik Charousset

Add docu, fix UB, ODR violation and memory leak

parent a65ae292
...@@ -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 {
......
...@@ -151,7 +151,7 @@ protected: ...@@ -151,7 +151,7 @@ protected:
} }
void stop() override { void stop() override {
// nop fix_->drop_events();
} }
void enqueue(resumable* ptr) override { void enqueue(resumable* ptr) override {
...@@ -216,16 +216,18 @@ deterministic::~deterministic() { ...@@ -216,16 +216,18 @@ deterministic::~deterministic() {
// Note: we need clean up all remaining messages manually. This in turn may // 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 // clean up actors as unreachable if the test did not consume all
// messages. Otherwise, the destructor of `sys` will wait for all // messages. Otherwise, the destructor of `sys` will wait for all
// actors, potentially waiting forever. However, we cannot just call // actors, potentially waiting forever.
// `events_.clear()`, because that would potentially cause an actor to drop_events();
// become unreachable and close its mailbox. This would call }
// `pop_msg_impl` in turn, which then tries to alter the list while
// we're clearing it. 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()) { while (!events_.empty()) {
std::list<std::unique_ptr<scheduling_event>> tmp; std::list<std::unique_ptr<scheduling_event>> tmp;
tmp.splice(tmp.end(), events_); tmp.splice(tmp.end(), events_);
// Here, tmp will be destroyed and cleanup code of actors might send more
// messages. Hence the loop.
} }
} }
......
...@@ -204,38 +204,63 @@ public: ...@@ -204,38 +204,63 @@ public:
evaluator& operator=(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> template <class... Us>
evaluator&& with(Us... xs) && { evaluator&& with(Us... xs) && {
static_assert(sizeof...(Ts) == sizeof...(Us));
static_assert((std::is_constructible_v<value_predicate<Ts>, Us> && ...)); static_assert((std::is_constructible_v<value_predicate<Ts>, Us> && ...));
with_ = message_predicate<Ts...>(std::move(xs)...); with_ = message_predicate<Ts...>(std::move(xs)...);
return std::move(*this); 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) && { evaluator&& from(const strong_actor_ptr& src) && {
from_ = value_predicate<strong_actor_ptr>{std::move(src)}; from_ = value_predicate<strong_actor_ptr>{std::move(src)};
return std::move(*this); return std::move(*this);
} }
evaluator&& from(std::nullptr_t) && { /// Adds a predicate for the sender of the next message that matches only if
return std::move(*this).from(strong_actor_ptr{}); /// the sender is `src`.
}
evaluator&& from(const actor& src) && { evaluator&& from(const actor& src) && {
from_ = value_predicate<strong_actor_ptr>{std::move(src)}; from_ = value_predicate<strong_actor_ptr>{std::move(src)};
return std::move(*this); 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> template <class... Us>
evaluator&& from(const typed_actor<Us...>& src) && { evaluator&& from(const typed_actor<Us...>& src) && {
from_ = value_predicate<strong_actor_ptr>{std::move(src)}; from_ = value_predicate<strong_actor_ptr>{std::move(src)};
return std::move(*this); 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) && { evaluator&& from(std::reference_wrapper<strong_actor_ptr> src) && {
from_ = value_predicate<strong_actor_ptr>{src}; from_ = value_predicate<strong_actor_ptr>{src};
return std::move(*this); return std::move(*this);
} }
/// Sets the target actor for this evaluator and evaluate the predicate.
template <class T> template <class T>
bool to(const T& dst) && { bool to(const T& dst) && {
auto dst_ptr = actor_cast<strong_actor_ptr>(dst); auto dst_ptr = actor_cast<strong_actor_ptr>(dst);
...@@ -276,7 +301,7 @@ public: ...@@ -276,7 +301,7 @@ public:
if (fail_on_mismatch) { if (fail_on_mismatch) {
if (!fix_->dispatch_message()) if (!fix_->dispatch_message())
ctx.fail({"failed to dispatch message", loc_}); ctx.fail({"failed to dispatch message", loc_});
reporter::instance->pass(loc_); reporter::instance().pass(loc_);
return true; return true;
} }
return fix_->dispatch_message(); return fix_->dispatch_message();
...@@ -379,6 +404,15 @@ public: ...@@ -379,6 +404,15 @@ public:
// -- member variables ------------------------------------------------------- // -- 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. /// Configures the actor system with deterministic scheduling.
config cfg; config cfg;
...@@ -386,6 +420,9 @@ public: ...@@ -386,6 +420,9 @@ public:
actor_system sys; actor_system sys;
private: private:
/// Removes all events from the queue.
void drop_events();
/// Tries find a message in `events_` that matches the given predicate and /// Tries find a message in `events_` that matches the given predicate and
/// moves it to the front of the queue. /// moves it to the front of the queue.
bool prepone_event_impl(const strong_actor_ptr& receiver); bool prepone_event_impl(const strong_actor_ptr& receiver);
...@@ -401,9 +438,6 @@ private: ...@@ -401,9 +438,6 @@ private:
/// Removes the next message for `receiver` from the queue and returns it. /// Removes the next message for `receiver` from the queue and returns it.
mailbox_element_ptr pop_msg_impl(scheduled_actor* receiver); mailbox_element_ptr pop_msg_impl(scheduled_actor* receiver);
/// Stores all pending messages of scheduled actors.
std::list<std::unique_ptr<scheduling_event>> events_;
}; };
} // namespace caf::test } // namespace caf::test
...@@ -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:
...@@ -472,8 +470,20 @@ private: ...@@ -472,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>();
} }
......
...@@ -94,8 +94,11 @@ public: ...@@ -94,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();
......
...@@ -51,9 +51,9 @@ void runnable::run() { ...@@ -51,9 +51,9 @@ 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;
} }
......
...@@ -49,9 +49,9 @@ public: ...@@ -49,9 +49,9 @@ public:
[[noreturn]] void fail(detail::format_string_with_location fwl, Ts&&... xs) { [[noreturn]] void fail(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->fail(msg, fwl.location); reporter::instance().fail(msg, fwl.location);
} else { } else {
reporter::instance->fail(fwl.value, fwl.location); reporter::instance().fail(fwl.value, fwl.location);
} }
CAF_RAISE_ERROR(std::logic_error, "requirement failed: abort test"); CAF_RAISE_ERROR(std::logic_error, "requirement failed: abort test");
} }
...@@ -61,9 +61,9 @@ public: ...@@ -61,9 +61,9 @@ public:
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);
} }
} }
...@@ -74,11 +74,11 @@ public: ...@@ -74,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;
} }
...@@ -89,11 +89,11 @@ public: ...@@ -89,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;
} }
...@@ -104,11 +104,11 @@ public: ...@@ -104,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;
} }
...@@ -119,11 +119,11 @@ public: ...@@ -119,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;
} }
...@@ -134,11 +134,11 @@ public: ...@@ -134,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;
} }
...@@ -149,11 +149,11 @@ public: ...@@ -149,11 +149,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::ge, stringify(lhs), reporter::instance().fail(binary_predicate::ge, stringify(lhs),
stringify(rhs), location); stringify(rhs), location);
return false; return false;
} }
...@@ -169,22 +169,22 @@ public: ...@@ -169,22 +169,22 @@ public:
template <class Expr> template <class Expr>
void should_fail(Expr&& expr, const caf::detail::source_location& location void should_fail(Expr&& expr, const caf::detail::source_location& location
= caf::detail::source_location::current()) { = caf::detail::source_location::current()) {
auto* rep = reporter::instance; auto& rep = reporter::instance();
auto lvl = rep->verbosity(CAF_LOG_LEVEL_QUIET); auto lvl = rep.verbosity(CAF_LOG_LEVEL_QUIET);
auto before = rep->test_stats(); auto before = rep.test_stats();
{ {
auto lvl_guard = detail::make_scope_guard([&] { rep->verbosity(lvl); }); auto lvl_guard = detail::make_scope_guard([&] { rep.verbosity(lvl); });
expr(); expr();
} }
auto after = rep->test_stats(); auto after = rep.test_stats();
auto passed_count_ok = before.passed == after.passed; auto passed_count_ok = before.passed == after.passed;
auto failed_count_ok = before.failed + 1 == after.failed; auto failed_count_ok = before.failed + 1 == after.failed;
if (passed_count_ok && failed_count_ok) { if (passed_count_ok && failed_count_ok) {
reporter::instance->pass(location); reporter::instance().pass(location);
rep->test_stats({before.passed + 1, before.failed}); rep.test_stats({before.passed + 1, before.failed});
} else { } else {
reporter::instance->fail("nested check should fail", location); reporter::instance().fail("nested check should fail", location);
rep->test_stats({before.passed, before.failed + 1}); rep.test_stats({before.passed, before.failed + 1});
} }
} }
...@@ -198,21 +198,21 @@ public: ...@@ -198,21 +198,21 @@ public:
try { try {
expr(); 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 {
expr(); 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);
} }
} }
...@@ -222,9 +222,9 @@ public: ...@@ -222,9 +222,9 @@ public:
void should_fail_with_exception(Expr&& expr, void should_fail_with_exception(Expr&& expr,
const caf::detail::source_location& location const caf::detail::source_location& location
= caf::detail::source_location::current()) { = caf::detail::source_location::current()) {
auto* rep = reporter::instance; auto& rep = reporter::instance();
auto before = rep->test_stats(); auto before = rep.test_stats();
auto lvl = rep->verbosity(CAF_LOG_LEVEL_QUIET); auto lvl = rep.verbosity(CAF_LOG_LEVEL_QUIET);
auto caught = false; auto caught = false;
if constexpr (std::is_same_v<Exception, void>) { if constexpr (std::is_same_v<Exception, void>) {
try { try {
...@@ -241,21 +241,21 @@ public: ...@@ -241,21 +241,21 @@ public:
// TODO: print error message // TODO: print error message
} }
} }
rep->verbosity(lvl); rep.verbosity(lvl);
auto after = rep->test_stats(); auto after = rep.test_stats();
auto passed_count_ok = before.passed == after.passed; auto passed_count_ok = before.passed == after.passed;
auto failed_count_ok = before.failed + 1 == after.failed; auto failed_count_ok = before.failed + 1 == after.failed;
if (caught && passed_count_ok && failed_count_ok) { if (caught && passed_count_ok && failed_count_ok) {
reporter::instance->pass(location); reporter::instance().pass(location);
rep->test_stats({before.passed + 1, before.failed}); rep.test_stats({before.passed + 1, before.failed});
} else { } else {
if (!caught) { if (!caught) {
reporter::instance->fail("nested check should throw an Exception", reporter::instance().fail("nested check should throw an Exception",
location); location);
} else if (!passed_count_ok || !failed_count_ok) { } else if (!passed_count_ok || !failed_count_ok) {
reporter::instance->fail("nested check should fail", location); reporter::instance().fail("nested check should fail", location);
} }
rep->test_stats({before.passed, before.failed + 1}); rep.test_stats({before.passed, before.failed + 1});
} }
} }
......
...@@ -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