c++-actor-framework

Segmentation Faults when testing typed actors with custom atoms


I am trying to use the testing macros with my actors but I am getting a lot of segmentation faults. I believe I have narrowed down the problem to my use of custom atoms. To demonstrate the issue I modified the 'simple actor test' from here to make the adder strongly typed.

#include "caf/test/dsl.hpp"
#include "caf/test/unit_test_impl.hpp"
#include "caf/all.hpp"

namespace {

    struct fixture {
        caf::actor_system_config cfg;
        caf::actor_system sys;
        caf::scoped_actor self;

        fixture() : sys(cfg), self(sys) {
            // nop
        }
    };

    using calculator_type = caf::typed_actor<caf::result<int>(int, int)>;

    calculator_type::behavior_type adder() {
        return {
          [=](int x, int y) {
            return x + y;
          }
        };
    }

} // namespace

CAF_TEST_FIXTURE_SCOPE(actor_tests, fixture)

CAF_TEST(simple actor test) {
    // Our Actor-Under-Test.
    auto aut = self->spawn(adder);
    self->request(aut, caf::infinite, 3, 4).receive(
        [=](int r) {
            CAF_CHECK(r == 7);
        },
        [&](caf::error& err) {
            // Must not happen, stop test.
            CAF_FAIL(err);
        });
}

CAF_TEST_FIXTURE_SCOPE_END()

This works great. I then took it one step further to add a custom atom called "add_numbers"

#include "caf/test/dsl.hpp"
#include "caf/test/unit_test_impl.hpp"
#include "caf/all.hpp"

CAF_BEGIN_TYPE_ID_BLOCK(calc_msgs, first_custom_type_id)
    CAF_ADD_ATOM(calc_msgs, add_numbers)
CAF_END_TYPE_ID_BLOCK(calc_msgs)

namespace {

    struct fixture {
        caf::actor_system_config cfg;
        caf::actor_system sys;
        caf::scoped_actor self;

        fixture() : sys(cfg), self(sys) {
            // nop
        }
    };

   using calculator_type = caf::typed_actor<caf::result<int>(add_numbers, int, int)>;

    calculator_type::behavior_type adder() {
        return {
          [=](add_numbers, int x, int y) {
            return x + y;
          }
        };
    }

} // namespace

CAF_TEST_FIXTURE_SCOPE(actor_tests, fixture)

CAF_TEST(simple actor test) {
    // Our Actor-Under-Test.
    auto aut = self->spawn(adder);
    self->request(aut, caf::infinite, add_numbers_v, 3, 4).receive(
        [=](int r) {
            CAF_CHECK(r == 7);
        },
        [&](caf::error& err) {
            // Must not happen, stop test.
            CAF_FAIL(err);
        });
}

CAF_TEST_FIXTURE_SCOPE_END()

This compiles fine but produces a segmentation fault at runtime. I suspect it has something to do with the fact that I am not passing calc_msgs to anything. How do I do that? Or is something else going on?


Solution

  • The ID block adds the compile-time meta data. But you also need to initialize some run-time state via

    init_global_meta_objects<caf::id_block::calc_msgs>();
    

    Ideally, you initialize this state before calling any other CAF function. In particular before initializing the actor system. CAF itself uses custom main functions for its test suites to do that (cf. core-test.cpp). In your case, it would look somewhat like this:

    int main(int argc, char** argv) {
      using namespace caf;
      init_global_meta_objects<id_block::calc_msgs>();
      core::init_global_meta_objects();
      return test::main(argc, argv);
    }
    

    This probably means that you would need to put the type ID block into a header file. This is nothing special about the unit tests, though. If you run a regular CAF application, you need to initialize the global meta objects as well. CAF_MAIN can do that for you as long as you pass it the type ID block(s) or you need to call the functions by hand. The CAF manual covers this in a bit more detail here: https://actor-framework.readthedocs.io/en/0.18.5/ConfiguringActorApplications.html#configuring-actor-applications.

    If this is your only test at the moment, you can define CAF_TEST_NO_MAIN before including caf/test/unit_test_impl.hpp and then add the custom main function. Once you have multiple test suites, it makes sense to move the main to its own file, though.