Skip to content

Commit

Permalink
Merge pull request #1839
Browse files Browse the repository at this point in the history
Migrate actor lifetime test to new framework
  • Loading branch information
Neverlord committed Apr 20, 2024
2 parents 0d1f801 + 0e745f2 commit 9089a2f
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 1,045 deletions.
11 changes: 2 additions & 9 deletions libcaf_core/CMakeLists.txt
Expand Up @@ -83,6 +83,7 @@ caf_add_component(
caf/actor_control_block.cpp
caf/actor_factory.test.cpp
caf/actor_from_state.test.cpp
caf/actor_lifetime.test.cpp
caf/actor_ostream.cpp
caf/actor_pool.cpp
caf/actor_pool.test.cpp
Expand Down Expand Up @@ -389,19 +390,11 @@ caf_add_component(
caf/uuid.cpp
caf/uuid.test.cpp
caf/version.cpp
caf/version.test.cpp
LEGACY_TEST_SOURCES
tests/legacy/core-test.cpp
LEGACY_TEST_SUITES
actor_lifetime)
caf/version.test.cpp)

# Additional log component for (potentially critical) system-wide events.
caf_add_log_component(system)

if(NOT CAF_USE_STD_FORMAT)
target_sources(libcaf_core PRIVATE caf/detail/format.cpp)
endif()

if(TARGET caf-core-legacy-test AND CAF_ENABLE_EXCEPTIONS)
caf_add_legacy_test_suites(caf-core-legacy-test custom_exception_handler)
endif()
Expand Up @@ -2,11 +2,11 @@
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.

#define CAF_SUITE actor_lifetime
#include "caf/test/fixture/deterministic.hpp"
#include "caf/test/test.hpp"

#include "caf/all.hpp"

#include "core-test.hpp"
#include "caf/anon_mail.hpp"
#include "caf/event_based_actor.hpp"

#include <atomic>
#include <condition_variable>
Expand All @@ -15,6 +15,7 @@
CAF_PUSH_DEPRECATED_WARNING

using namespace caf;
using namespace std::literals;

namespace {

Expand All @@ -35,6 +36,11 @@ class testee : public event_based_actor {

~testee() override {
--s_testees;
{
std::unique_lock guard{s_mtx};
s_testee_cleanup_done = true;
s_cv.notify_one();
}
}

const char* name() const override {
Expand All @@ -57,45 +63,60 @@ behavior tester(event_based_actor* self, const actor& aut) {
if (std::is_same_v<ExitMsgType, exit_msg>) {
self->set_exit_handler([self](exit_msg& msg) {
// must be still alive at this point
CHECK_EQ(s_testees.load(), 1);
CHECK_EQ(msg.reason, exit_reason::user_shutdown);
self->mail(ok_atom_v).send(self);
if (s_testees.load() != 1)
CAF_RAISE_ERROR(detail::format("Unexpected number of actors alive: {}",
s_testees.load())
.c_str());
if (msg.reason != exit_reason::user_shutdown)
CAF_RAISE_ERROR("Unexpected exit reason");
self->mail(ok_atom_v).schedule(self->clock().now() + 5ms).send(self);
});
self->link_to(aut);
anon_mail(exit_msg{self->address(), exit_reason::user_shutdown}).send(self);
} else {
self->set_down_handler([self](down_msg& msg) {
// must be still alive at this point
CHECK_EQ(s_testees.load(), 1);
CHECK_EQ(msg.reason, exit_reason::user_shutdown);
if (s_testees.load() != 1)
CAF_RAISE_ERROR(detail::format("Unexpected number of actors alive: {}",
s_testees.load())
.c_str());
if (msg.reason != exit_reason::user_shutdown)
CAF_RAISE_ERROR("Unexpected exit reason");
// testee might be still running its cleanup code in
// another worker thread; by waiting some milliseconds, we make sure
// testee had enough time to return control to the scheduler
// which in turn destroys it by dropping the last remaining reference
self->mail(ok_atom_v).send(self);
self->mail(ok_atom_v).schedule(self->clock().now() + 5ms).send(self);
});
self->monitor(aut);
anon_mail(down_msg{self->address(), exit_reason::user_shutdown}).send(self);
}
anon_send_exit(aut, exit_reason::user_shutdown);
anon_mail(exit_msg{self->address(), exit_reason::user_shutdown}).send(aut);
{
std::unique_lock<std::mutex> guard{s_mtx};
s_tester_init_done = true;
s_cv.notify_one();
}
return {
[self](ok_atom) {
{ // make sure aut's dtor and on_exit() have been called
std::unique_lock<std::mutex> guard{s_mtx};
while (!s_testee_cleanup_done.load())
s_cv.wait(guard);
}
CHECK_EQ(s_testees.load(), 0);
CHECK_EQ(s_pending_on_exits.load(), 0);
if (s_pending_on_exits.load() != 0)
CAF_RAISE_ERROR(
detail::format("Unexpected number of actors pending exit: {}",
s_pending_on_exits.load())
.c_str());
self->quit();
},
};
}

struct fixture : public test_coordinator_fixture<> {
struct fixture {
actor_system_config cfg;
actor_system sys;

fixture() : sys(cfg) {
// no op
}

template <spawn_options Os, class... Ts>
actor spawn(Ts&&... xs) {
return sys.spawn<Os>(xs...);
Expand All @@ -114,9 +135,7 @@ struct fixture : public test_coordinator_fixture<> {
s_testee_cleanup_done = false;
// Spawn test subject and tester.
auto tst_subject = spawn<testee, TesteeOptions>();
sched.run();
auto tst_driver = spawn<TesterOptions>(tester<ExitMsgType>, tst_subject);
tst_subject = nullptr;
if (has_detach_flag(TesterOptions)) {
// When dealing with a detached tester we need to insert two
// synchronization points: 1) exit_msg sent and 2) cleanup code of tester
Expand All @@ -126,53 +145,71 @@ struct fixture : public test_coordinator_fixture<> {
while (!s_tester_init_done)
s_cv.wait(guard);
}
// Run the exit_msg.
sched.run_once();
// expect((exit_msg), from(tst_driver).to(tst_subject));
{ // Resume driver.
std::unique_lock<std::mutex> guard{s_mtx};
s_testee_cleanup_done = true;
s_cv.notify_one();
}
} else {
// When both actors are running in the scheduler we don't need any extra
// synchronization.
s_tester_init_done = true;
s_testee_cleanup_done = true;
sched.run();
}
}
};

CAF_TEST(destructor_call) {
TEST("destructor call") {
{ // lifetime scope of actor system
actor_system_config cfg;
actor_system system{cfg};
system.spawn<testee>();
}
CHECK_EQ(s_testees.load(), 0);
CHECK_EQ(s_pending_on_exits.load(), 0);
check_eq(s_testees.load(), 0);
check_eq(s_pending_on_exits.load(), 0);
}

BEGIN_FIXTURE_SCOPE(fixture)
WITH_FIXTURE(fixture) {

CAF_TEST(no_spawn_options_and_exit_msg) {
TEST("no spawn options and exit_msg") {
tst<exit_msg, no_spawn_options, no_spawn_options>();
{
std::unique_lock<std::mutex> guard{s_mtx};
while (!s_testee_cleanup_done.load())
s_cv.wait(guard);
}
check_eq(s_testees.load(), 0);
check_eq(s_pending_on_exits.load(), 0);
}

CAF_TEST(no_spawn_options_and_down_msg) {
TEST("no spawn options and down_msg") {
tst<down_msg, no_spawn_options, no_spawn_options>();
{
std::unique_lock<std::mutex> guard{s_mtx};
while (!s_testee_cleanup_done.load())
s_cv.wait(guard);
}
check_eq(s_testees.load(), 0);
check_eq(s_pending_on_exits.load(), 0);
}

CAF_TEST(mixed_spawn_options_and_exit_msg) {
TEST("detached spawn options and exit_msg") {
tst<exit_msg, detached, no_spawn_options>();
{
std::unique_lock<std::mutex> guard{s_mtx};
while (!s_testee_cleanup_done.load())
s_cv.wait(guard);
}
check_eq(s_testees.load(), 0);
check_eq(s_pending_on_exits.load(), 0);
}

CAF_TEST(mixed_spawn_options_and_down_msg) {
TEST("detached spawn options and down_msg") {
tst<down_msg, detached, no_spawn_options>();
{
std::unique_lock<std::mutex> guard{s_mtx};
while (!s_testee_cleanup_done.load())
s_cv.wait(guard);
}
check_eq(s_testees.load(), 0);
check_eq(s_pending_on_exits.load(), 0);
}

END_FIXTURE_SCOPE()
} // WITH_FIXTURE(fixture)

} // namespace

Expand Down
62 changes: 62 additions & 0 deletions libcaf_core/caf/scheduled_actor.test.cpp
Expand Up @@ -6,6 +6,11 @@

#include "caf/test/test.hpp"

#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/scoped_actor.hpp"

using namespace caf;

#define ASSERT_COMPILES(expr, msg) \
Expand Down Expand Up @@ -136,6 +141,63 @@ struct const_exception_fn {
ASSERT_COMPILES(set_exception_handler(const_exception_fn{}),
"set_exception_handler must accept const function objects");

behavior testee() {
return {
[](const std::string&) { throw std::runtime_error("whatever"); },
};
}

behavior exception_testee(event_based_actor* self) {
self->set_exception_handler([](std::exception_ptr&) -> error {
return exit_reason::remote_link_unreachable;
});
return testee();
}

TEST("the default exception handler includes the error message") {
using std::string;
actor_system_config cfg;
actor_system system{cfg};
scoped_actor self{system};
auto aut = self->spawn(testee);
self->mail("hello world")
.request(aut, infinite)
.receive( //
[this] { fail("unexpected response"); },
[this](const error& err) {
check_eq(err.what(),
"unhandled exception of type std.runtime_error: whatever");
});
}

TEST("actors can override the default exception handler") {
actor_system_config cfg;
actor_system system{cfg};
auto handler = [](std::exception_ptr& eptr) -> error {
try {
std::rethrow_exception(eptr);
} catch (std::runtime_error&) {
return exit_reason::normal;
} catch (...) {
// "fall through"
}
return sec::runtime_error;
};
scoped_actor self{system};
auto testee1 = self->spawn<monitored>([=](event_based_actor* eb_self) {
eb_self->set_exception_handler(handler);
throw std::runtime_error("ping");
});
auto testee2 = self->spawn<monitored>([=](event_based_actor* eb_self) {
eb_self->set_exception_handler(handler);
throw std::logic_error("pong");
});
auto testee3 = self->spawn<monitored>(exception_testee);
self->mail("foo").send(testee3);
// receive all down messages
self->wait_for(testee1, testee2, testee3);
}

#endif // CAF_ENABLE_EXCEPTIONS

} // namespace

0 comments on commit 9089a2f

Please sign in to comment.