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) {
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 {
......
......@@ -151,7 +151,7 @@ protected:
}
void stop() override {
// nop
fix_->drop_events();
}
void enqueue(resumable* ptr) override {
......@@ -216,16 +216,18 @@ 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. However, we cannot just call
// `events_.clear()`, because that would potentially cause an actor to
// 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.
// 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_);
// Here, tmp will be destroyed and cleanup code of actors might send more
// messages. Hence the loop.
}
}
......
......@@ -204,38 +204,63 @@ public:
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);
}
evaluator&& from(std::nullptr_t) && {
return std::move(*this).from(strong_actor_ptr{});
}
/// 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);
......@@ -276,7 +301,7 @@ public:
if (fail_on_mismatch) {
if (!fix_->dispatch_message())
ctx.fail({"failed to dispatch message", loc_});
reporter::instance->pass(loc_);
reporter::instance().pass(loc_);
return true;
}
return fix_->dispatch_message();
......@@ -379,6 +404,15 @@ public:
// -- 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;
......@@ -386,6 +420,9 @@ public:
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);
......@@ -401,9 +438,6 @@ private:
/// Removes the next message for `receiver` from the queue and returns it.
mailbox_element_ptr pop_msg_impl(scheduled_actor* receiver);
/// Stores all pending messages of scheduled actors.
std::list<std::unique_ptr<scheduling_event>> events_;
};
} // namespace caf::test
......@@ -22,8 +22,6 @@ reporter::~reporter() {
// nop
}
reporter* reporter::instance;
namespace {
/// Implements a mini-DSL for colored output:
......@@ -472,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>();
}
......
......@@ -94,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();
......
......@@ -51,9 +51,9 @@ 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;
}
......
......@@ -49,9 +49,9 @@ public:
[[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);
reporter::instance().fail(msg, fwl.location);
} 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");
}
......@@ -61,9 +61,9 @@ public:
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);
}
}
......@@ -74,11 +74,11 @@ 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),
stringify(rhs), location);
reporter::instance().fail(binary_predicate::eq, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -89,11 +89,11 @@ 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),
stringify(rhs), location);
reporter::instance().fail(binary_predicate::ne, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -104,11 +104,11 @@ 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),
stringify(rhs), location);
reporter::instance().fail(binary_predicate::lt, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -119,11 +119,11 @@ 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),
stringify(rhs), location);
reporter::instance().fail(binary_predicate::le, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -134,11 +134,11 @@ 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),
stringify(rhs), location);
reporter::instance().fail(binary_predicate::gt, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -149,11 +149,11 @@ 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),
stringify(rhs), location);
reporter::instance().fail(binary_predicate::ge, stringify(lhs),
stringify(rhs), location);
return false;
}
......@@ -169,22 +169,22 @@ public:
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& 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); });
auto lvl_guard = detail::make_scope_guard([&] { rep.verbosity(lvl); });
expr();
}
auto after = rep->test_stats();
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});
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});
reporter::instance().fail("nested check should fail", location);
rep.test_stats({before.passed, before.failed + 1});
}
}
......@@ -198,21 +198,21 @@ public:
try {
expr();
} catch (...) {
reporter::instance->pass(location);
reporter::instance().pass(location);
return;
}
reporter::instance->fail("throws", location);
reporter::instance().fail("throws", location);
} else {
try {
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);
}
}
......@@ -222,9 +222,9 @@ public:
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 lvl = rep->verbosity(CAF_LOG_LEVEL_QUIET);
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 {
......@@ -241,21 +241,21 @@ public:
// TODO: print error message
}
}
rep->verbosity(lvl);
auto after = rep->test_stats();
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});
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);
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);
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()) {
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