actor-framework
deterministic.hpp
1 // This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
2 // the main distribution directory for license terms and copyright or visit
3 // https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
4 
5 #pragma once
6 
7 #include "caf/test/runnable.hpp"
8 
9 #include "caf/actor_clock.hpp"
10 #include "caf/actor_system.hpp"
11 #include "caf/actor_system_config.hpp"
12 #include "caf/binary_deserializer.hpp"
13 #include "caf/binary_serializer.hpp"
14 #include "caf/detail/source_location.hpp"
15 #include "caf/detail/test_export.hpp"
16 #include "caf/detail/type_traits.hpp"
17 #include "caf/mailbox_element.hpp"
18 #include "caf/resumable.hpp"
19 
20 #include <list>
21 #include <memory>
22 
23 namespace caf::test::fixture {
24 
29 class CAF_TEST_EXPORT deterministic {
30 private:
31  // -- private member types (implementation details) --------------------------
32 
33  class mailbox_impl;
34 
35  class scheduler_impl;
36 
38 
40  struct scheduling_event {
41  scheduling_event(resumable* target, mailbox_element_ptr payload)
42  : target(target), item(std::move(payload)) {
43  // nop
44  }
45 
48 
50  mailbox_element_ptr item;
51  };
52 
56  template <class T>
57  class value_predicate {
58  public:
59  value_predicate() {
60  predicate_ = [](const T&) { return true; };
61  }
62 
63  explicit value_predicate(decltype(std::ignore)) : value_predicate() {
64  // nop
65  }
66 
67  template <class U>
68  explicit value_predicate(
69  U value, std::enable_if_t<detail::is_comparable_v<T, U>>* = nullptr) {
70  predicate_ = [value](const T& found) { return found == value; };
71  }
72 
73  explicit value_predicate(std::reference_wrapper<T> ref) {
74  predicate_ = [ref](const T& found) {
75  ref.get() = found;
76  return true;
77  };
78  }
79 
80  template <
81  class Predicate,
82  class = std::enable_if_t<std::is_same_v<
83  bool, decltype(std::declval<Predicate>()(std::declval<const T&>()))>>>
84  explicit value_predicate(Predicate predicate) {
85  predicate_ = std::move(predicate);
86  }
87 
88  value_predicate(value_predicate&&) = default;
89 
90  value_predicate(const value_predicate&) = default;
91 
92  value_predicate& operator=(value_predicate&&) = default;
93 
94  value_predicate& operator=(const value_predicate&) = default;
95 
96  bool operator()(const T& value) {
97  return predicate_(value);
98  }
99 
100  private:
101  std::function<bool(const T&)> predicate_;
102  };
103 
105  using actor_predicate = value_predicate<strong_actor_ptr>;
106 
108  class CAF_TEST_EXPORT abstract_message_predicate {
109  public:
110  virtual ~abstract_message_predicate();
111 
112  virtual bool operator()(const message&) = 0;
113  };
114 
116  template <class... Ts>
117  class message_predicate : public abstract_message_predicate {
118  public:
119  template <class U, class... Us>
120  explicit message_predicate(U x, Us... xs) {
121  predicates_ = std::make_shared<predicates_tuple>(std::move(x),
122  std::move(xs)...);
123  }
124 
125  explicit message_predicate(decltype(std::ignore)) {
126  // nop
127  }
128 
129  message_predicate() {
130  predicates_ = std::make_shared<predicates_tuple>();
131  }
132 
133  message_predicate(message_predicate&&) = default;
134 
135  message_predicate(const message_predicate&) = default;
136 
137  message_predicate& operator=(message_predicate&&) = default;
138 
139  message_predicate& operator=(const message_predicate&) = default;
140 
141  bool operator()(const message& msg) {
142  if (!predicates_)
143  return true;
144  if (auto view = make_const_typed_message_view<Ts...>(msg))
145  return do_check(view, std::index_sequence_for<Ts...>{});
146  return false;
147  }
148 
149  private:
150  template <size_t... Is>
151  bool do_check([[maybe_unused]] const_typed_message_view<Ts...> view, //
152  std::index_sequence<Is...>) {
153  return (((std::get<Is>(*predicates_))(get<Is>(view))) && ...);
154  }
155 
156  using predicates_tuple = std::tuple<value_predicate<Ts>...>;
157 
158  std::shared_ptr<predicates_tuple> predicates_;
159  };
160 
161 public:
162  // -- public member types ----------------------------------------------------
163 
165  class system_impl : public actor_system {
166  public:
168 
169  detail::actor_local_printer_ptr printer_for(local_actor* self) override;
170 
171  private:
172  static actor_system_config& prepare(actor_system_config& cfg,
173  deterministic* fix);
174 
175  static void custom_setup(actor_system& sys, actor_system_config& cfg,
176  void* custom_setup_data);
177 
179  std::map<actor_id, detail::actor_local_printer_ptr> printers_;
180  };
181 
183  enum class evaluator_algorithm {
184  expect,
185  allow,
186  disallow,
187  prepone,
188  prepone_and_expect,
189  prepone_and_allow
190  };
191 
197  template <class... Ts>
198  class evaluator {
199  public:
201  evaluator_algorithm algorithm)
202  : fix_(fix), loc_(loc), algo_(algorithm) {
203  // nop
204  }
205 
206  evaluator() = delete;
207 
208  evaluator(evaluator&&) noexcept = default;
209 
210  evaluator& operator=(evaluator&&) noexcept = default;
211 
212  evaluator(const evaluator&) = delete;
213 
214  evaluator& operator=(const evaluator&) = delete;
215 
230  template <class... Us>
231  evaluator&& with(Us... xs) && {
232  static_assert(sizeof...(Ts) == sizeof...(Us));
233  static_assert((std::is_constructible_v<value_predicate<Ts>, Us> && ...));
234  with_ = message_predicate<Ts...>(std::move(xs)...);
235  return std::move(*this);
236  }
237 
240  evaluator&& from(const strong_actor_ptr& src) && {
241  from_ = value_predicate<strong_actor_ptr>{src};
242  return std::move(*this);
243  }
244 
247  evaluator&& from(const actor& src) && {
248  from_ = value_predicate<strong_actor_ptr>{src};
249  return std::move(*this);
250  }
251 
254  template <class... Us>
255  evaluator&& from(const typed_actor<Us...>& src) && {
256  from_ = value_predicate<strong_actor_ptr>{src};
257  return std::move(*this);
258  }
259 
262  evaluator&& from(std::nullptr_t) && {
263  return std::move(*this).from(strong_actor_ptr{});
264  }
265 
267  evaluator&& from(std::reference_wrapper<strong_actor_ptr> src) && {
268  from_ = value_predicate<strong_actor_ptr>{src};
269  return std::move(*this);
270  }
271 
272  evaluator&& priority(message_priority priority) && {
273  priority_ = priority;
274  return std::move(*this);
275  }
276 
278  template <class T>
279  bool to(const T& dst) && {
280  auto dst_ptr = actor_cast<strong_actor_ptr>(dst);
281  switch (algo_) {
282  case evaluator_algorithm::expect:
283  return eval_dispatch(dst_ptr, true);
284  case evaluator_algorithm::allow:
285  return eval_dispatch(dst_ptr, false);
286  case evaluator_algorithm::disallow:
287  if (dry_run(dst_ptr)) {
288  auto& ctx = runnable::current();
289  ctx.fail({"disallow message found", loc_});
290  }
291  return true;
292  case evaluator_algorithm::prepone:
293  return eval_prepone(dst_ptr);
294  case evaluator_algorithm::prepone_and_expect:
295  eval_prepone(dst_ptr);
296  return eval_dispatch(dst_ptr, true);
297  case evaluator_algorithm::prepone_and_allow:
298  return eval_prepone(dst_ptr) && eval_dispatch(dst_ptr, false);
299  default:
300  CAF_RAISE_ERROR(std::logic_error, "invalid algorithm");
301  }
302  }
303 
304  private:
305  using predicates = std::tuple<value_predicate<Ts>...>;
306 
307  bool eval_dispatch(const strong_actor_ptr& dst, bool fail_on_mismatch) {
308  auto& ctx = runnable::current();
309  auto* event = fix_->find_event_impl(dst);
310  if (!event) {
311  if (fail_on_mismatch)
312  ctx.fail({"no matching message found", loc_});
313  return false;
314  }
315  if (!from_(event->item->sender) || !with_(event->item->payload)) {
316  if (fail_on_mismatch)
317  ctx.fail({"no matching message found", loc_});
318  return false;
319  }
320  if (priority_) {
321  if (event->item->mid.priority() != *priority_) {
322  if (fail_on_mismatch)
323  ctx.fail({"message priority does not match", loc_});
324  return false;
325  }
326  }
327  fix_->prepone_event_impl(dst);
328  if (fail_on_mismatch) {
329  if (!fix_->dispatch_message())
330  ctx.fail({"failed to dispatch message", loc_});
331  reporter::instance().pass(loc_);
332  return true;
333  }
334  return fix_->dispatch_message();
335  }
336 
337  bool dry_run(const strong_actor_ptr& dst) {
338  auto* event = fix_->find_event_impl(dst);
339  if (!event)
340  return false;
341  return from_(event->item->sender) && !with_(event->item->payload);
342  }
343 
344  bool eval_prepone(const strong_actor_ptr& dst) {
345  return fix_->prepone_event_impl(dst, from_, with_);
346  }
347 
348  deterministic* fix_;
350  evaluator_algorithm algo_;
351  actor_predicate from_;
352  message_predicate<Ts...> with_;
353  std::optional<message_priority> priority_;
354  };
355 
358  template <class... Ts>
359  class injector {
360  public:
361  injector(deterministic* fix, detail::source_location loc, Ts... values)
362  : fix_(fix), loc_(loc), values_(std::move(values)...) {
363  // nop
364  }
365 
366  injector() = delete;
367 
368  injector(injector&&) noexcept = default;
369 
370  injector& operator=(injector&&) noexcept = default;
371 
372  injector(const injector&) = delete;
373 
374  injector& operator=(const injector&) = delete;
375 
378  injector&& from(const strong_actor_ptr& src) && {
379  from_ = src;
380  return std::move(*this);
381  }
382 
385  injector&& from(const actor& src) && {
386  from_ = actor_cast<strong_actor_ptr>(src);
387  return std::move(*this);
388  }
389 
392  template <class... Us>
393  injector&& from(const typed_actor<Us...>& src) && {
394  from_ = actor_cast<strong_actor_ptr>(src);
395  return std::move(*this);
396  }
397 
400  template <class T>
401  void to(const T& dst) && {
402  to_impl(dst, std::make_index_sequence<sizeof...(Ts)>{});
403  }
404 
405  private:
406  template <class T, size_t... Is>
407  void to_impl(const T& dst, std::index_sequence<Is...>) {
408  auto ptr = actor_cast<abstract_actor*>(dst);
409  ptr->enqueue(make_mailbox_element(from_, make_message_id(),
410  std::get<Is>(values_)...),
411  nullptr);
412  fix_->expect<Ts...>(loc_)
413  .from(from_)
414  .with(std::get<Is>(values_)...)
415  .to(dst);
416  }
417 
418  deterministic* fix_;
420  strong_actor_ptr from_;
421  std::tuple<Ts...> values_;
422  };
423 
426  public:
427  template <class Handle>
428  actor_scope_guard(deterministic* fix, const Handle& dst) noexcept
429  : fix_(fix) {
430  if (dst)
431  dst_ = actor_cast<strong_actor_ptr>(dst);
432  }
433 
434  actor_scope_guard(actor_scope_guard&&) noexcept = default;
435 
436  actor_scope_guard() = delete;
437  actor_scope_guard(const actor_scope_guard&) = delete;
438  actor_scope_guard& operator=(const actor_scope_guard&) = delete;
439 
440  ~actor_scope_guard() {
441  fix_->inject_exit(dst_, exit_reason::kill);
442  }
443 
444  private:
445  deterministic* fix_;
446  strong_actor_ptr dst_;
447  };
448 
449  // -- friends ----------------------------------------------------------------
450 
451  friend class mailbox_impl;
452 
453  friend class scheduler_impl;
454 
455  template <class... Ts>
456  friend class evaluator;
457 
458  friend class actor_scope_guard;
459 
460  // -- constructors, destructors, and assignment operators --------------------
461 
462  deterministic();
463 
464  ~deterministic();
465 
466  // -- properties -------------------------------------------------------------
467 
469  size_t mail_count();
470 
472  size_t mail_count(scheduled_actor* receiver);
473 
475  size_t mail_count(const strong_actor_ptr& receiver);
476 
478  size_t mail_count(const actor& receiver) {
479  return mail_count(actor_cast<strong_actor_ptr>(receiver));
480  }
481 
483  template <class... Ts>
484  size_t mail_count(const typed_actor<Ts...>& receiver) {
485  return mail_count(actor_cast<strong_actor_ptr>(receiver));
486  }
487 
489  bool terminated(const strong_actor_ptr& hdl);
490 
492  template <class Handle>
493  bool terminated(const Handle& hdl) {
494  return terminated(actor_cast<strong_actor_ptr>(hdl));
495  }
496 
497  // -- control flow -----------------------------------------------------------
498 
500  bool dispatch_message();
501 
503  size_t dispatch_messages();
504 
505  // -- actor management -------------------------------------------------------
506 
509  void inject_exit(const strong_actor_ptr& hdl,
510  error reason = exit_reason::user_shutdown);
511 
514  template <class Handle>
515  void
516  inject_exit(const Handle& hdl, error reason = exit_reason::user_shutdown) {
517  inject_exit(actor_cast<strong_actor_ptr>(hdl), std::move(reason));
518  }
519 
520  // -- time management --------------------------------------------------------
521 
524  size_t set_time(actor_clock::time_point value,
525  const detail::source_location& loc
526  = detail::source_location::current());
527 
530  size_t advance_time(actor_clock::duration_type amount,
531  const detail::source_location& loc
532  = detail::source_location::current());
533 
536  bool trigger_timeout(const detail::source_location& loc
537  = detail::source_location::current());
538 
541  size_t trigger_all_timeouts(const detail::source_location& loc
542  = detail::source_location::current());
543 
545  [[nodiscard]] size_t num_timeouts() noexcept;
546 
548  [[nodiscard]] bool has_pending_timeout() noexcept {
549  return num_timeouts() > 0;
550  }
551 
553  [[nodiscard]] actor_clock::time_point
554  next_timeout(const detail::source_location& loc
555  = detail::source_location::current());
556 
558  [[nodiscard]] actor_clock::time_point
559  last_timeout(const detail::source_location& loc
560  = detail::source_location::current());
561 
562  // -- message-based predicates -----------------------------------------------
563 
567  template <class... Ts>
569  = detail::source_location::current()) {
570  return evaluator<Ts...>{this, loc, evaluator_algorithm::expect};
571  }
572 
575  template <class... Ts>
577  = detail::source_location::current()) {
578  return evaluator<Ts...>{this, loc, evaluator_algorithm::allow};
579  }
580 
583  template <class... Ts>
585  = detail::source_location::current()) {
586  return evaluator<Ts...>{this, loc, evaluator_algorithm::disallow};
587  }
588 
590  struct inject_helper {
591  deterministic* fix;
593 
594  template <class... Ts>
595  auto with(Ts... values) {
596  return injector<Ts...>{fix, loc, std::move(values)...};
597  }
598  };
599 
604  = detail::source_location::current()) {
605  return inject_helper{this, loc};
606  }
607 
611  template <class... Ts>
613  = detail::source_location::current()) {
614  return evaluator<Ts...>{this, loc, evaluator_algorithm::prepone};
615  }
616 
618  template <class... Ts>
620  = detail::source_location::current()) {
621  return evaluator<Ts...>{this, loc, evaluator_algorithm::prepone_and_expect};
622  }
623 
625  template <class... Ts>
627  = detail::source_location::current()) {
628  return evaluator<Ts...>{this, loc, evaluator_algorithm::prepone_and_allow};
629  }
630 
631  // -- serialization ----------------------------------------------------------
632 
633  template <class T>
634  expected<T> serialization_roundtrip(const T& value) {
635  byte_buffer buf;
636  {
637  binary_serializer sink{sys, buf};
638  if (!sink.apply(value))
639  return sink.get_error();
640  }
641  T result;
642  {
643  binary_deserializer source{sys, buf};
644  if (!source.apply(result))
645  return source.get_error();
646  }
647  return result;
648  }
649 
650  // -- factories --------------------------------------------------------------
651 
653  template <class Handle>
654  [[nodiscard]] actor_scope_guard make_actor_scope_guard(const Handle& hdl) {
655  return {this, hdl};
656  }
657 
658 private:
659  // Note: this is put here because this member variable must be destroyed
660  // *after* the actor system (and thus must come before `sys` in
661  // the declaration order).
662 
664  std::list<std::unique_ptr<scheduling_event>> events_;
665 
666 public:
669 
672 
673 private:
675  void drop_events();
676 
679  bool prepone_event_impl(const strong_actor_ptr& receiver);
680 
683  bool prepone_event_impl(const strong_actor_ptr& receiver,
684  actor_predicate& sender_pred,
685  abstract_message_predicate& payload_pred);
686 
688  scheduler_impl& sched_impl();
689 
691  scheduling_event* find_event_impl(const strong_actor_ptr& receiver);
692 
694  mailbox_element_ptr pop_msg_impl(scheduled_actor* receiver);
695 };
696 
697 } // namespace caf::test::fixture
auto allow(const detail::source_location &loc=detail::source_location::current())
Tries to match a message with types Ts... and executes it if it is the next message in the mailbox of...
Definition: deterministic.hpp:576
evaluator && with(Us... xs) &&
Matches the values of the message.
Definition: deterministic.hpp:231
bool terminated(const Handle &hdl)
Checks whether hdl has terminated.
Definition: deterministic.hpp:493
Utility class for injecting messages into the mailbox of an actor and then checking whether the actor...
Definition: deterministic.hpp:359
A cooperatively scheduled entity.
Definition: resumable.hpp:15
evaluator_algorithm
Configures the algorithm to evaluate for an evaluator instances.
Definition: deterministic.hpp:183
evaluator && from(const strong_actor_ptr &src) &&
Adds a predicate for the sender of the next message that matches only if the sender is src...
Definition: deterministic.hpp:240
actor_scope_guard make_actor_scope_guard(const Handle &hdl)
Creates a new guard for hdl that will kill the actor at scope exit.
Definition: deterministic.hpp:654
Wraps the result of a message handler to represent either a value (wrapped into a message)...
Definition: fwd.hpp:64
Helper class for inject that only provides with.
Definition: deterministic.hpp:590
Provides a fluent interface for matching messages.
Definition: deterministic.hpp:198
Base class for actors running on this node, either living in an own thread or cooperatively scheduled...
Definition: local_actor.hpp:41
auto expect(const detail::source_location &loc=detail::source_location::current())
Expects a message with types Ts... as the next message in the mailbox of the receiver and aborts the ...
Definition: deterministic.hpp:568
A fixture for writing unit tests that require deterministic scheduling.
Definition: deterministic.hpp:29
actor_system_config cfg
Configures the actor system with deterministic scheduling.
Definition: deterministic.hpp:668
Definition: deterministic.cpp:35
size_t mail_count(const actor &receiver)
Returns the number of pending messages for receiver.
Definition: deterministic.hpp:478
auto prepone_and_allow(const detail::source_location &loc=detail::source_location::current())
Shortcut for calling prepone and then allow with the same arguments.
Definition: deterministic.hpp:626
evaluator && from(const actor &src) &&
Adds a predicate for the sender of the next message that matches only if the sender is src...
Definition: deterministic.hpp:247
An intrusive, reference counting smart pointer implementation.
Definition: fwd.hpp:32
size_t mail_count(const typed_actor< Ts... > &receiver)
Returns the number of pending messages for receiver.
Definition: deterministic.hpp:484
Describes a fixed-length, copy-on-write, type-erased tuple with elements of any type.
Definition: message.hpp:32
Actor environment including scheduler, registry, and optional components such as a middleman...
Definition: actor_system.hpp:87
A serializable type for storing error codes with category and optional, human-readable context inform...
Definition: error.hpp:50
Definition: deterministic.cpp:443
Encountered faulty logic in the program.
injector && from(const typed_actor< Us... > &src) &&
Adds a predicate for the sender of the next message that matches only if the sender is src...
Definition: deterministic.hpp:393
typename clock_type::duration duration_type
Time interval.
Definition: actor_clock.hpp:28
T actor_cast(U &&what)
Converts the actor handle what to a different actor handle or raw pointer of type T...
Definition: actor_cast.hpp:148
typename clock_type::time_point time_point
Discrete point in time.
Definition: actor_clock.hpp:25
void inject_exit(const Handle &hdl, error reason=exit_reason::user_shutdown)
Injects an exit message into the mailbox of hdl and dispatches it immediately.
Definition: deterministic.hpp:516
void to(const T &dst) &&
Sets the target actor for this injector, sends the message, and then checks whether the actor handles...
Definition: deterministic.hpp:401
bool has_pending_timeout() noexcept
Returns whether there is at least one pending timeout.
Definition: deterministic.hpp:548
Definition: const_typed_message_view.hpp:18
Serializes C++ objects into a sequence of bytes.
Definition: binary_serializer.hpp:25
A cooperatively scheduled, event-based actor implementation.
Definition: scheduled_actor.hpp:73
auto inject(const detail::source_location &loc=detail::source_location::current())
Starts an inject clause.
Definition: deterministic.hpp:603
system_impl sys
The actor system instance for the tests.
Definition: deterministic.hpp:671
auto prepone_and_expect(const detail::source_location &loc=detail::source_location::current())
Shortcut for calling prepone and then expect with the same arguments.
Definition: deterministic.hpp:619
Represents the result of a computation which can either complete successfully with an instance of typ...
Definition: expected.hpp:45
bool to(const T &dst) &&
Sets the target actor for this evaluator and evaluate the predicate.
Definition: deterministic.hpp:279
evaluator && from(std::reference_wrapper< strong_actor_ptr > src) &&
Causes the evaluator to store the sender of a matched message in src.
Definition: deterministic.hpp:267
Deserializes C++ objects from sequence of bytes.
Definition: binary_deserializer.hpp:26
static runnable & current()
Returns the runnable instance that is currently running.
Definition: runnable.cpp:83
Identifies an untyped actor.
Definition: actor.hpp:28
Base class for all actor implementations.
Definition: abstract_actor.hpp:48
The custom system implementation for this fixture.
Definition: deterministic.hpp:165
evaluator && from(const typed_actor< Us... > &src) &&
Adds a predicate for the sender of the next message that matches only if the sender is src...
Definition: deterministic.hpp:255
injector && from(const strong_actor_ptr &src) &&
Adds a predicate for the sender of the next message that matches only if the sender is src...
Definition: deterministic.hpp:378
injector && from(const actor &src) &&
Adds a predicate for the sender of the next message that matches only if the sender is src...
Definition: deterministic.hpp:385
virtual bool enqueue(mailbox_element_ptr what, scheduler *sched)=0
Enqueues a new message wrapped in a mailbox_element to the actor.
auto disallow(const detail::source_location &loc=detail::source_location::current())
Tries to match a message with types Ts... and executes it if it is the next message in the mailbox of...
Definition: deterministic.hpp:584
auto prepone(const detail::source_location &loc=detail::source_location::current())
Tries to prepone a message, i.e., reorders the messages in the mailbox of the receiver such that the ...
Definition: deterministic.hpp:612
Utility class for unconditionally killing an actor at scope exit.
Definition: deterministic.hpp:425
std::vector< std::byte > byte_buffer
A buffer for storing binary data.
Definition: byte_buffer.hpp:13
Definition: source_location.hpp:12
Definition: fwd.hpp:65
static reporter & instance()
Returns the registered reporter instance.
Definition: reporter.cpp:546
evaluator && from(std::nullptr_t) &&
Adds a predicate for the sender of the next message that matches only anonymous messages, i.e., messages without a sender.
Definition: deterministic.hpp:262
Definition: deterministic.cpp:115
Configures an actor_system on startup.
Definition: actor_system_config.hpp:28