ActionTesting.hpp
1 // Distributed under the MIT License.
2 // See LICENSE.txt for details.
3 
4 #pragma once
5 
6 #include <algorithm>
7 #include <array>
8 #include <boost/preprocessor/control/if.hpp>
9 #include <boost/preprocessor/logical/compl.hpp>
10 #include <boost/preprocessor/logical/not.hpp>
11 #include <boost/preprocessor/punctuation/comma_if.hpp>
12 #include <boost/preprocessor/repetition/repeat.hpp>
13 #include <cstddef>
14 #include <deque>
15 #include <exception>
16 #include <lrtslock.h>
17 #include <memory>
18 #include <ostream>
19 #include <random>
20 #include <tuple>
21 #include <unordered_map>
22 #include <unordered_set>
23 #include <utility>
24 #include <vector>
25 
27 #include "ErrorHandling/Assert.hpp"
28 #include "ErrorHandling/Error.hpp"
29 #include "Parallel/AlgorithmMetafunctions.hpp"
31 #include "Parallel/NodeLock.hpp"
32 #include "Parallel/ParallelComponentHelpers.hpp"
33 #include "Parallel/SimpleActionVisitation.hpp"
37 #include "Utilities/Gsl.hpp"
38 #include "Utilities/Overloader.hpp"
39 #include "Utilities/PrettyType.hpp"
40 #include "Utilities/Requires.hpp"
41 #include "Utilities/StdHelpers.hpp"
42 #include "Utilities/TMPL.hpp"
44 #include "Utilities/TypeTraits.hpp"
45 
46 /// \cond
47 // IWYU pragma: no_forward_declare db::DataBox
48 namespace PUP {
49 class er;
50 } // namespace PUP
51 namespace Parallel {
52 template <class ChareType>
53 struct get_array_index;
54 } // namespace Parallel
55 /// \endcond
56 
57 namespace ActionTesting {
58 namespace detail {
59 #define ACTION_TESTING_CHECK_MOCK_ACTION_LIST(NAME) \
60  template <typename Component, typename = cpp17::void_t<>> \
61  struct get_##NAME##_mocking_list { \
62  using replace_these_##NAME = tmpl::list<>; \
63  using with_these_##NAME = tmpl::list<>; \
64  }; \
65  template <typename Component> \
66  struct get_##NAME##_mocking_list< \
67  Component, cpp17::void_t<typename Component::replace_these_##NAME, \
68  typename Component::with_these_##NAME>> { \
69  using replace_these_##NAME = typename Component::replace_these_##NAME; \
70  using with_these_##NAME = typename Component::with_these_##NAME; \
71  }; \
72  template <typename Component> \
73  using replace_these_##NAME##_t = \
74  typename get_##NAME##_mocking_list<Component>::replace_these_##NAME; \
75  template <typename Component> \
76  using with_these_##NAME##_t = \
77  typename get_##NAME##_mocking_list<Component>::with_these_##NAME
78 
79 ACTION_TESTING_CHECK_MOCK_ACTION_LIST(simple_actions);
80 ACTION_TESTING_CHECK_MOCK_ACTION_LIST(threaded_actions);
81 #undef ACTION_TESTING_CHECK_MOCK_ACTION_LIST
82 } // namespace detail
83 
84 // MockDistributedObject mocks the AlgorithmImpl class.
85 template <typename Component>
87  private:
88  class InvokeActionBase {
89  public:
90  InvokeActionBase() = default;
91  InvokeActionBase(const InvokeActionBase&) = default;
92  InvokeActionBase& operator=(const InvokeActionBase&) = default;
93  InvokeActionBase(InvokeActionBase&&) = default;
94  InvokeActionBase& operator=(InvokeActionBase&&) = default;
95  virtual ~InvokeActionBase() = default;
96  virtual void invoke_action() noexcept = 0;
97  };
98 
99  // Holds the arguments to be passed to the simple action once it is invoked.
100  // We delay simple action calls that are made from within an action for
101  // several reasons:
102  // - This is consistent with what actually happens in the parallel code
103  // - This prevents possible stack overflows
104  // - Allows better introspection and control over the Actions' behavior
105  template <typename Action, typename... Args>
106  class InvokeSimpleAction : public InvokeActionBase {
107  public:
108  InvokeSimpleAction(MockDistributedObject* local_alg,
109  std::tuple<Args...> args)
110  : local_algorithm_(local_alg), args_(std::move(args)) {}
111 
112  explicit InvokeSimpleAction(MockDistributedObject* local_alg)
113  : local_algorithm_(local_alg) {}
114 
115  void invoke_action() noexcept override {
116  if (not valid_) {
117  ERROR(
118  "Cannot invoke the exact same simple action twice. This is an "
119  "internal bug in the action testing framework. Please file an "
120  "issue.");
121  }
122  valid_ = false;
123  invoke_action_impl(std::move(args_));
124  }
125 
126  private:
127  template <typename Arg0, typename... Rest>
128  void invoke_action_impl(std::tuple<Arg0, Rest...> args) noexcept {
129  local_algorithm_->simple_action<Action>(std::move(args), true);
130  }
131 
132  template <typename... LocalArgs,
133  Requires<sizeof...(LocalArgs) == 0> = nullptr>
134  void invoke_action_impl(std::tuple<LocalArgs...> /*args*/) noexcept {
135  local_algorithm_->simple_action<Action>(true);
136  }
137 
138  MockDistributedObject* local_algorithm_;
139  std::tuple<Args...> args_{};
140  bool valid_{true};
141  };
142 
143  template <typename Action, typename... Args>
144  class InvokeThreadedAction : public InvokeActionBase {
145  public:
146  InvokeThreadedAction(MockDistributedObject* local_alg,
147  std::tuple<Args...> args)
148  : local_algorithm_(local_alg), args_(std::move(args)) {}
149 
150  explicit InvokeThreadedAction(MockDistributedObject* local_alg)
151  : local_algorithm_(local_alg) {}
152 
153  void invoke_action() noexcept override {
154  if (not valid_) {
155  ERROR(
156  "Cannot invoke the exact same threaded action twice. This is an "
157  "internal bug in the action testing framework. Please file an "
158  "issue.");
159  }
160  valid_ = false;
161  invoke_action_impl(std::move(args_));
162  }
163 
164  private:
165  template <typename Arg0, typename... Rest>
166  void invoke_action_impl(std::tuple<Arg0, Rest...> args) noexcept {
167  local_algorithm_->threaded_action<Action>(std::move(args), true);
168  }
169 
170  template <typename... LocalArgs,
171  Requires<sizeof...(LocalArgs) == 0> = nullptr>
172  void invoke_action_impl(std::tuple<LocalArgs...> /*args*/) noexcept {
173  local_algorithm_->threaded_action<Action>(true);
174  }
175 
176  MockDistributedObject* local_algorithm_;
177  std::tuple<Args...> args_{};
178  bool valid_{true};
179  };
180 
181  public:
182  using actions_list = typename Component::action_list;
183 
184  using inbox_tags_list = Parallel::get_inbox_tags<actions_list>;
185 
186  using metavariables = typename Component::metavariables;
187 
188  private:
189  template <typename ActionsList>
190  struct compute_databox_type;
191 
192  template <typename... ActionsPack>
193  struct compute_databox_type<tmpl::list<ActionsPack...>> {
194  using type = Parallel::Algorithm_detail::build_action_return_typelist<
195  typename Component::initial_databox,
196  tmpl::list<
197  tuples::tagged_tuple_from_typelist<inbox_tags_list>,
199  typename Component::array_index, actions_list,
201  ActionsPack...>;
202  };
203 
204  public:
205  using databox_types = typename compute_databox_type<actions_list>::type;
206 
207  MockDistributedObject() = default;
208 
209  explicit MockDistributedObject(
210  typename Component::initial_databox initial_box)
211  : box_(std::move(initial_box)) {}
212 
213  void set_index(typename Component::array_index index) noexcept {
214  array_index_ = std::move(index);
215  }
216 
218  cache_ptr) noexcept {
219  const_global_cache_ = cache_ptr;
220  }
221 
222  void set_inboxes(tuples::tagged_tuple_from_typelist<
224  inboxes_ptr) noexcept {
225  inboxes_ = inboxes_ptr;
226  }
227 
228  void set_terminate(bool t) { terminate_ = t; }
229  bool get_terminate() { return terminate_; }
230 
231  // Actions may call this, but since tests step through actions manually it has
232  // no effect.
233  void perform_algorithm() noexcept {}
234 
235  template <typename BoxType>
236  BoxType& get_databox() noexcept {
237  return boost::get<BoxType>(box_);
238  }
239 
240  template <typename BoxType>
241  const BoxType& get_databox() const noexcept {
242  return boost::get<BoxType>(box_);
243  }
244 
245  auto& get_variant_box() noexcept { return box_; }
246 
247  const auto& get_variant_box() const noexcept { return box_; }
248 
249  void force_next_action_to_be(const size_t next_action_id) noexcept {
250  algorithm_step_ = next_action_id;
251  }
252 
253  size_t get_next_action_index() const noexcept { return algorithm_step_; }
254 
255  template <size_t... Is>
256  void next_action(std::index_sequence<Is...> /*meta*/) noexcept;
257 
258  template <size_t... Is>
259  bool is_ready(std::index_sequence<Is...> /*meta*/) noexcept;
260 
261 #define SIMPLE_AND_THREADED_ACTIONS(USE_SIMPLE_ACTION, NAME) \
262  template <typename Action, typename... Args, \
263  Requires<not tmpl::list_contains_v< \
264  detail::replace_these_##NAME##s_t<Component>, Action>> = \
265  nullptr> \
266  void NAME(std::tuple<Args...> args, \
267  const bool direct_from_action_runner = false) noexcept { \
268  if (direct_from_action_runner) { \
269  performing_action_ = true; \
270  forward_tuple_to_##NAME<Action>( \
271  std::move(args), std::make_index_sequence<sizeof...(Args)>{}); \
272  performing_action_ = false; \
273  } else { \
274  NAME##_queue_.push_back( \
275  std::make_unique<BOOST_PP_IF(USE_SIMPLE_ACTION, InvokeSimpleAction, \
276  InvokeThreadedAction) < Action, \
277  Args...>> (this, std::move(args))); \
278  } \
279  } \
280  template <typename Action, typename... Args, \
281  Requires<tmpl::list_contains_v< \
282  detail::replace_these_##NAME##s_t<Component>, Action>> = \
283  nullptr> \
284  void NAME(std::tuple<Args...> args, \
285  const bool direct_from_action_runner = false) noexcept { \
286  using index_of_action = \
287  tmpl::index_of<detail::replace_these_##NAME##s_t<Component>, Action>; \
288  using new_action = tmpl::at_c<detail::with_these_##NAME##s_t<Component>, \
289  index_of_action::value>; \
290  if (direct_from_action_runner) { \
291  performing_action_ = true; \
292  forward_tuple_to_##NAME<new_action>( \
293  std::move(args), std::make_index_sequence<sizeof...(Args)>{}); \
294  performing_action_ = false; \
295  } else { \
296  NAME##_queue_.push_back( \
297  std::make_unique<BOOST_PP_IF(USE_SIMPLE_ACTION, InvokeSimpleAction, \
298  InvokeThreadedAction) < new_action, \
299  Args...>> (this, std::move(args))); \
300  } \
301  } \
302  template <typename Action, \
303  Requires<not tmpl::list_contains_v< \
304  detail::replace_these_##NAME##s_t<Component>, Action>> = \
305  nullptr> \
306  void NAME(const bool direct_from_action_runner = false) noexcept { \
307  if (direct_from_action_runner) { \
308  performing_action_ = true; \
309  Parallel::Algorithm_detail::simple_action_visitor< \
310  Action, typename Component::initial_databox>( \
311  box_, *inboxes_, *const_global_cache_, \
312  cpp17::as_const(array_index_), actions_list{}, \
313  std::add_pointer_t<Component>{nullptr}); \
314  performing_action_ = false; \
315  } else { \
316  NAME##_queue_.push_back( \
317  std::make_unique<BOOST_PP_IF(USE_SIMPLE_ACTION, InvokeSimpleAction, \
318  InvokeThreadedAction) < Action>> \
319  (this)); \
320  } \
321  } \
322  template <typename Action, \
323  Requires<tmpl::list_contains_v< \
324  detail::replace_these_##NAME##s_t<Component>, Action>> = \
325  nullptr> \
326  void NAME(const bool direct_from_action_runner = false) noexcept { \
327  using index_of_action = \
328  tmpl::index_of<detail::replace_these_##NAME##s_t<Component>, Action>; \
329  using new_action = tmpl::at_c<detail::with_these_##NAME##s_t<Component>, \
330  index_of_action::value>; \
331  if (direct_from_action_runner) { \
332  performing_action_ = true; \
333  Parallel::Algorithm_detail::simple_action_visitor< \
334  new_action, typename Component::initial_databox>( \
335  box_, *inboxes_, *const_global_cache_, \
336  cpp17::as_const(array_index_), actions_list{}, \
337  std::add_pointer_t<Component>{nullptr} BOOST_PP_COMMA_IF( \
338  BOOST_PP_NOT(USE_SIMPLE_ACTION)) \
339  BOOST_PP_IF(USE_SIMPLE_ACTION, , make_not_null(&node_lock_))); \
340  performing_action_ = false; \
341  } else { \
342  simple_action_queue_.push_back( \
343  std::make_unique<BOOST_PP_IF(USE_SIMPLE_ACTION, InvokeSimpleAction, \
344  InvokeThreadedAction) < new_action>> \
345  (this)); \
346  } \
347  }
348 
349  SIMPLE_AND_THREADED_ACTIONS(1, simple_action)
350  SIMPLE_AND_THREADED_ACTIONS(0, threaded_action)
351 #undef SIMPLE_AND_THREADED_ACTIONS
352 
353  bool is_simple_action_queue_empty() noexcept {
354  return simple_action_queue_.empty();
355  }
356 
357  void invoke_queued_simple_action() noexcept {
358  if (simple_action_queue_.empty()) {
359  ERROR(
360  "There are no queued simple actions to invoke. Are you sure a "
361  "previous action invoked a simple action on this component?");
362  }
363  simple_action_queue_.front()->invoke_action();
364  simple_action_queue_.pop_front();
365  }
366 
367  bool is_threaded_action_queue_empty() noexcept {
368  return threaded_action_queue_.empty();
369  }
370 
371  void invoke_queued_threaded_action() noexcept {
372  if (threaded_action_queue_.empty()) {
373  ERROR(
374  "There are no queued threaded actions to invoke. Are you sure a "
375  "previous action invoked a threaded action on this component?");
376  }
377  threaded_action_queue_.front()->invoke_action();
378  threaded_action_queue_.pop_front();
379  }
380 
381  template <typename InboxTag, typename Data>
382  void receive_data(const typename InboxTag::temporal_id& id, const Data& data,
383  const bool enable_if_disabled = false) {
384  // The variable `enable_if_disabled` might be useful in the future but is
385  // not needed now. However, it is required by the interface to be compliant
386  // with the Algorithm invocations.
387  (void)enable_if_disabled;
388  tuples::get<InboxTag>(*inboxes_)[id].emplace(data);
389  }
390 
391  private:
392  template <typename Action, typename... Args, size_t... Is>
393  void forward_tuple_to_simple_action(
394  std::tuple<Args...>&& args,
395  std::index_sequence<Is...> /*meta*/) noexcept {
396  Parallel::Algorithm_detail::simple_action_visitor<
397  Action, typename Component::initial_databox>(
398  box_, *inboxes_, *const_global_cache_, cpp17::as_const(array_index_),
399  actions_list{}, std::add_pointer_t<Component>{nullptr},
400  std::forward<Args>(std::get<Is>(args))...);
401  }
402 
403  template <typename Action, typename... Args, size_t... Is>
404  void forward_tuple_to_threaded_action(
405  std::tuple<Args...>&& args,
406  std::index_sequence<Is...> /*meta*/) noexcept {
407  Parallel::Algorithm_detail::simple_action_visitor<
408  Action, typename Component::initial_databox>(
409  box_, *inboxes_, *const_global_cache_, cpp17::as_const(array_index_),
410  actions_list{}, std::add_pointer_t<Component>{nullptr},
411  make_not_null(&node_lock_), std::forward<Args>(std::get<Is>(args))...);
412  }
413 
414  bool terminate_{false};
416  tmpl::push_front<databox_types, db::DataBox<tmpl::list<>>>>
417  box_ = db::DataBox<tmpl::list<>>{};
418  // The next action we should execute.
419  size_t algorithm_step_ = 0;
420  bool performing_action_ = false;
421 
422  typename Component::array_index array_index_{};
424  const_global_cache_{nullptr};
425  tuples::tagged_tuple_from_typelist<
427  nullptr};
428  std::deque<std::unique_ptr<InvokeActionBase>> simple_action_queue_;
429  std::deque<std::unique_ptr<InvokeActionBase>> threaded_action_queue_;
430  CmiNodeLock node_lock_ = Parallel::create_lock();
431 };
432 
433 template <typename Component>
434 template <size_t... Is>
436  std::index_sequence<Is...> /*meta*/) noexcept {
437  auto& const_global_cache = *const_global_cache_;
438  if (UNLIKELY(performing_action_)) {
439  ERROR(
440  "Cannot call an Action while already calling an Action on the same "
441  "MockDistributedObject (an element of a parallel component array, or a "
442  "parallel component singleton).");
443  }
444  // Keep track of if we already evaluated an action since we want `next_action`
445  // to only evaluate one per call.
446  bool already_did_an_action = false;
447  const auto helper = [
448  this, &array_index = array_index_, &inboxes = *inboxes_,
449  &const_global_cache, &already_did_an_action
450  ](auto iteration) noexcept {
451  constexpr size_t iter = decltype(iteration)::value;
452  using this_action = tmpl::at_c<actions_list, iter>;
453  using this_databox =
454  tmpl::at_c<databox_types,
455  iter == 0 ? tmpl::size<databox_types>::value - 1 : iter>;
456  if (already_did_an_action or algorithm_step_ != iter) {
457  return;
458  }
459 
460  this_databox* box_ptr{};
461  try {
462  box_ptr = &boost::get<this_databox>(box_);
463  } catch (std::exception& e) {
464  ERROR(
465  "\nFailed to retrieve Databox in take_next_action:\nCaught "
466  "exception: '"
467  << e.what() << "'\nDataBox type: '"
468  << pretty_type::get_name<this_databox>() << "'\nIteration: " << iter
469  << "\nAction: '" << pretty_type::get_name<this_action>()
470  << "'\nBoost::Variant id: " << box_.which()
471  << "\nBoost::Variant type is: '" << type_of_current_state(box_)
472  << "'\n\n");
473  }
474  this_databox& box = *box_ptr;
475 
476  const auto check_if_ready = make_overloader(
477  [&box, &array_index, &const_global_cache, &inboxes](
478  std::true_type /*has_is_ready*/, auto t) {
479  return decltype(t)::is_ready(
480  cpp17::as_const(box), cpp17::as_const(inboxes),
481  const_global_cache, cpp17::as_const(array_index));
482  },
483  [](std::false_type /*has_is_ready*/, auto) { return true; });
484  if (not check_if_ready(
485  Parallel::Algorithm_detail::is_is_ready_callable_t<
486  this_action, this_databox,
487  tuples::tagged_tuple_from_typelist<inbox_tags_list>,
489  typename Component::array_index>{},
490  this_action{})) {
491  ERROR("Tried to invoke the action '"
492  << pretty_type::get_name<this_action>()
493  << "' but have not received all the "
494  "necessary data.");
495  }
496  performing_action_ = true;
497  algorithm_step_++;
498  constexpr Component const* const component_ptr = nullptr;
500  [ this, &array_index, component_ptr, &const_global_cache, &inboxes ](
501  auto& my_box, std::integral_constant<size_t, 1> /*meta*/)
502  SPECTRE_JUST_ALWAYS_INLINE noexcept {
503  std::tie(box_) = this_action::apply(
504  my_box, inboxes, const_global_cache,
505  cpp17::as_const(array_index), actions_list{}, component_ptr);
506  },
507  [ this, &array_index, component_ptr, &const_global_cache, &inboxes ](
508  auto& my_box, std::integral_constant<size_t, 2> /*meta*/)
509  SPECTRE_JUST_ALWAYS_INLINE noexcept {
510  std::tie(box_, terminate_) = this_action::apply(
511  my_box, inboxes, const_global_cache,
512  cpp17::as_const(array_index), actions_list{}, component_ptr);
513  },
514  [ this, &array_index, component_ptr, &const_global_cache, &inboxes ](
515  auto& my_box, std::integral_constant<size_t, 3> /*meta*/)
516  SPECTRE_JUST_ALWAYS_INLINE noexcept {
517  std::tie(box_, terminate_, algorithm_step_) = this_action::apply(
518  my_box, inboxes, const_global_cache,
519  cpp17::as_const(array_index), actions_list{}, component_ptr);
520  })(
521  box, typename std::tuple_size<decltype(this_action::apply(
522  box, inboxes, const_global_cache, cpp17::as_const(array_index),
523  actions_list{}, component_ptr))>::type{});
524  performing_action_ = false;
525  already_did_an_action = true;
526 
527  // Wrap counter if necessary
528  if (algorithm_step_ >= tmpl::size<actions_list>::value) {
529  algorithm_step_ = 0;
530  }
531  };
532  // Silence compiler warning when there are no Actions.
533  (void)helper;
535 }
536 
537 template <typename Component>
538 template <size_t... Is>
540  std::index_sequence<Is...> /*meta*/) noexcept {
541  bool next_action_is_ready = false;
542  const auto helper = [
543  this, &array_index = array_index_, &inboxes = *inboxes_,
544  &const_global_cache = const_global_cache_, &next_action_is_ready
545  ](auto iteration) noexcept {
546  constexpr size_t iter = decltype(iteration)::value;
547  using this_action = tmpl::at_c<actions_list, iter>;
548  using this_databox =
549  tmpl::at_c<databox_types,
550  iter == 0 ? tmpl::size<databox_types>::value - 1 : iter>;
551  if (iter != algorithm_step_) {
552  return;
553  }
554 
555  this_databox* box_ptr{};
556  try {
557  box_ptr = &boost::get<this_databox>(box_);
558  } catch (std::exception& e) {
559  ERROR(
560  "\nFailed to retrieve Databox in take_next_action:\nCaught "
561  "exception: '"
562  << e.what() << "'\nDataBox type: '"
563  << pretty_type::get_name<this_databox>() << "'\nIteration: " << iter
564  << "\nAction: '" << pretty_type::get_name<this_action>()
565  << "'\nBoost::Variant id: " << box_.which()
566  << "\nBoost::Variant type is: '" << type_of_current_state(box_)
567  << "'\n\n");
568  }
569  this_databox& box = *box_ptr;
570 
571  const auto check_if_ready = make_overloader(
572  [&box, &array_index, &const_global_cache, &inboxes](
573  std::true_type /*has_is_ready*/, auto t) {
574  return decltype(t)::is_ready(
575  cpp17::as_const(box), cpp17::as_const(inboxes),
576  *const_global_cache, cpp17::as_const(array_index));
577  },
578  [](std::false_type /*has_is_ready*/, auto) { return true; });
579 
580  next_action_is_ready =
581  check_if_ready(Parallel::Algorithm_detail::is_is_ready_callable_t<
582  this_action, this_databox,
583  tuples::tagged_tuple_from_typelist<inbox_tags_list>,
585  typename Component::array_index>{},
586  this_action{});
587  };
588  // Silence compiler warning when there are no Actions.
589  (void)helper;
591  return next_action_is_ready;
592 }
593 
594 namespace ActionTesting_detail {
595 // A mock class for the Charm++ generated CProxyElement_AlgorithmArray (we use
596 // an array for everything, so no need to mock groups, nodegroups, singletons).
597 template <typename Component, typename InboxTagList>
598 class MockArrayElementProxy {
599  public:
600  using Inbox = tuples::tagged_tuple_from_typelist<InboxTagList>;
601 
602  MockArrayElementProxy(MockDistributedObject<Component>& local_algorithm,
603  Inbox& inbox)
604  : local_algorithm_(local_algorithm), inbox_(inbox) {}
605 
606  template <typename InboxTag, typename Data>
607  void receive_data(const typename InboxTag::temporal_id& id, const Data& data,
608  const bool enable_if_disabled = false) {
609  // The variable `enable_if_disabled` might be useful in the future but is
610  // not needed now. However, it is required by the interface to be compliant
611  // with the Algorithm invocations.
612  (void)enable_if_disabled;
613  tuples::get<InboxTag>(inbox_)[id].emplace(data);
614  }
615 
616  template <typename Action, typename... Args>
617  void simple_action(std::tuple<Args...> args) noexcept {
618  local_algorithm_.template simple_action<Action>(std::move(args));
619  }
620 
621  template <typename Action>
622  void simple_action() noexcept {
623  local_algorithm_.template simple_action<Action>();
624  }
625 
626  template <typename Action, typename... Args>
627  void threaded_action(std::tuple<Args...> args) noexcept {
628  local_algorithm_.template threaded_action<Action>(std::move(args));
629  }
630 
631  template <typename Action>
632  void threaded_action() noexcept {
633  local_algorithm_.template threaded_action<Action>();
634  }
635 
636  void set_terminate(bool t) noexcept { local_algorithm_.set_terminate(t); }
637 
638  // Actions may call this, but since tests step through actions manually it has
639  // no effect.
640  void perform_algorithm() noexcept {}
641 
642  MockDistributedObject<Component>* ckLocal() { return &local_algorithm_; }
643 
644  private:
645  MockDistributedObject<Component>& local_algorithm_;
646  Inbox& inbox_;
647 };
648 
649 // A mock class for the Charm++ generated CProxy_AlgorithmArray (we use an array
650 // for everything, so no need to mock groups, nodegroups, singletons).
651 template <typename Component, typename Index, typename InboxTagList>
652 class MockProxy {
653  public:
654  using Inboxes =
656  tuples::tagged_tuple_from_typelist<InboxTagList>>;
657  using TupleOfMockDistributedObjects =
659 
660  MockProxy() : inboxes_(nullptr) {}
661 
662  void set_data(TupleOfMockDistributedObjects* local_algorithms,
663  Inboxes* inboxes) {
664  local_algorithms_ = local_algorithms;
665  inboxes_ = inboxes;
666  }
667 
668  MockArrayElementProxy<Component, InboxTagList> operator[](
669  const Index& index) {
670  ASSERT(local_algorithms_->count(index) == 1,
671  "Should have exactly one local algorithm with key '"
672  << index << "' but found " << local_algorithms_->count(index)
673  << ". The known keys are " << keys_of(*local_algorithms_)
674  << ". Did you forget to add a local algorithm when constructing "
675  "the MockRuntimeSystem?");
676  return MockArrayElementProxy<Component, InboxTagList>(
677  local_algorithms_->at(index), inboxes_->operator[](index));
678  }
679 
680  MockDistributedObject<Component>* ckLocalBranch() noexcept {
681  ASSERT(
682  local_algorithms_->size() == 1,
683  "Can only have one algorithm when getting the ckLocalBranch, but have "
684  << local_algorithms_->size());
685  // We always retrieve the 0th local branch because we are assuming running
686  // on a single core.
687  return std::addressof(local_algorithms_->at(0));
688  }
689 
690  template <typename Action, typename... Args>
691  void simple_action(std::tuple<Args...> args) noexcept {
692  std::for_each(
693  local_algorithms_->begin(), local_algorithms_->end(),
694  [&args](auto& index_and_local_algorithm) noexcept {
695  index_and_local_algorithm.second.template simple_action<Action>(args);
696  });
697  }
698 
699  template <typename Action>
700  void simple_action() noexcept {
701  std::for_each(
702  local_algorithms_->begin(),
703  local_algorithms_->end(), [](auto& index_and_local_algorithm) noexcept {
704  index_and_local_algorithm.second.template simple_action<Action>();
705  });
706  }
707 
708  template <typename Action, typename... Args>
709  void threaded_action(std::tuple<Args...> args) noexcept {
710  if (local_algorithms_->size() != 1) {
711  ERROR("NodeGroups must have exactly one element during testing, but have "
712  << local_algorithms_->size());
713  }
714  local_algorithms_->begin()->second.template threaded_action<Action>(
715  std::move(args));
716  }
717 
718  template <typename Action>
719  void threaded_action() noexcept {
720  if (local_algorithms_->size() != 1) {
721  ERROR("NodeGroups must have exactly one element during testing, but have "
722  << local_algorithms_->size());
723  }
724  local_algorithms_->begin()->second.template threaded_action<Action>();
725  }
726 
727  // clang-tidy: no non-const references
728  void pup(PUP::er& /*p*/) noexcept { // NOLINT
729  ERROR(
730  "Should not try to serialize the mock proxy. If you encountered this "
731  "error you are using the mocking framework in a way that it was not "
732  "intended to be used. It may be possible to extend it to more use "
733  "cases but it is recommended you file an issue to discuss before "
734  "modifying the mocking framework.");
735  }
736 
737  private:
738  TupleOfMockDistributedObjects* local_algorithms_;
739  Inboxes* inboxes_;
740 };
741 } // namespace ActionTesting_detail
742 
743 /// A mock class for the CMake-generated `Parallel::Algorithms::Array`
745  template <typename Component, typename Index>
746  using cproxy = ActionTesting_detail::MockProxy<
747  Component, Index,
749 };
750 } // namespace ActionTesting
751 
752 /// \cond HIDDEN_SYMBOLS
753 namespace Parallel {
754 template <>
755 struct get_array_index<ActionTesting::MockArrayChare> {
756  template <typename Component>
757  using f = typename Component::array_index;
758 };
759 } // namespace Parallel
760 /// \endcond
761 
762 /*!
763  * \ingroup TestingFrameworkGroup
764  * \brief Structures used for mocking the parallel components framework in order
765  * to test actions.
766  */
767 namespace ActionTesting {
768 /// \ingroup TestingFrameworkGroup
769 /// A class that mocks the infrastructure needed to run actions. It
770 /// simulates message passing using the inbox infrastructure and
771 /// handles most of the arguments to the apply and is_ready action
772 /// methods.
773 template <typename Metavariables>
775  public:
776  // No moving, since MockProxy holds a pointer to us.
777  MockRuntimeSystem(const MockRuntimeSystem&) = delete;
779  MockRuntimeSystem& operator=(const MockRuntimeSystem&) = delete;
780  MockRuntimeSystem& operator=(MockRuntimeSystem&&) = delete;
781  ~MockRuntimeSystem() = default;
782 
783  template <typename Component>
784  struct InboxesTag {
785  using type = std::unordered_map<
786  typename Component::array_index,
787  tuples::tagged_tuple_from_typelist<
789  };
790 
791  template <typename Component>
793  using type = std::unordered_map<typename Component::array_index,
795  };
796 
798  using CacheTuple =
799  tuples::tagged_tuple_from_typelist<typename GlobalCache::tag_list>;
800  using TupleOfMockDistributedObjects = tuples::tagged_tuple_from_typelist<
801  tmpl::transform<typename Metavariables::component_list,
802  tmpl::bind<MockDistributedObjectsTag, tmpl::_1>>>;
803  using Inboxes = tuples::tagged_tuple_from_typelist<
804  tmpl::transform<typename Metavariables::component_list,
805  tmpl::bind<InboxesTag, tmpl::_1>>>;
806 
807  /// Construct from the tuple of ConstGlobalCache objects.
808  explicit MockRuntimeSystem(CacheTuple cache_contents,
809  TupleOfMockDistributedObjects local_algorithms)
810  : cache_(std::move(cache_contents)),
811  local_algorithms_(std::move(local_algorithms)) {
812  tmpl::for_each<typename Metavariables::component_list>([this](
813  auto component) {
814  using Component = tmpl::type_from<decltype(component)>;
815  Parallel::get_parallel_component<Component>(cache_).set_data(
817  &tuples::get<InboxesTag<Component>>(inboxes_));
818 
819  for (auto& local_alg_pair : this->template algorithms<Component>()) {
820  const auto& index = local_alg_pair.first;
821  auto& local_alg = local_alg_pair.second;
822  local_alg.set_index(index);
823  local_alg.set_cache(&cache_);
824  local_alg.set_inboxes(
825  &(tuples::get<InboxesTag<Component>>(inboxes_)[index]));
826  }
827  });
828  }
829 
830  // @{
831  /// Invoke the simple action `Action` on the `Component` labeled by
832  /// `array_index` immediately.
833  template <typename Component, typename Action, typename Arg0,
834  typename... Args>
835  void simple_action(const typename Component::array_index& array_index,
836  Arg0&& arg0, Args&&... args) noexcept {
837  algorithms<Component>()
838  .at(array_index)
839  .template simple_action<Action>(
840  std::make_tuple(std::forward<Arg0>(arg0),
841  std::forward<Args>(args)...),
842  true);
843  }
844 
845  template <typename Component, typename Action>
847  const typename Component::array_index& array_index) noexcept {
848  algorithms<Component>()
849  .at(array_index)
850  .template simple_action<Action>(true);
851  }
852  // @}
853 
854  /// Return true if there are no queued simple actions on the
855  /// `Component` labeled by `array_index`.
856  template <typename Component>
858  const typename Component::array_index& array_index) noexcept {
859  return algorithms<Component>()
860  .at(array_index)
861  .is_simple_action_queue_empty();
862  }
863 
864  /// Invoke the next queued simple action on the `Component` labeled by
865  /// `array_index`.
866  template <typename Component>
868  const typename Component::array_index& array_index) noexcept {
869  algorithms<Component>().at(array_index).invoke_queued_simple_action();
870  }
871 
872  /// Return true if there are no queued threaded actions on the
873  /// `Component` labeled by `array_index`.
874  template <typename Component>
876  const typename Component::array_index& array_index) noexcept {
877  return algorithms<Component>()
878  .at(array_index)
879  .is_threaded_action_queue_empty();
880  }
881 
882  /// Invoke the next queued threaded action on the `Component` labeled by
883  /// `array_index`.
884  template <typename Component>
886  const typename Component::array_index& array_index) noexcept {
887  algorithms<Component>().at(array_index).invoke_queued_threaded_action();
888  }
889 
890  /// Instead of the next call to `next_action` applying the next action in
891  /// the action list, force the next action to be `Action`
892  template <typename Component, typename Action>
894  const typename Component::array_index& array_index) noexcept {
895  static_assert(
896  tmpl::list_contains_v<typename Component::action_list, Action>,
897  "Cannot force a next action that is not in the action list of the "
898  "parallel component. See the first template parameter of "
899  "'force_next_action_to_be' for the component and the second template "
900  "parameter for the action.");
901  algorithms<Component>()
902  .at(array_index)
903  .force_next_action_to_be(
904  tmpl::index_of<typename Component::action_list, Action>::value);
905  }
906 
907  /// Obtain the index into the action list of the next action.
908  template <typename Component>
910  const typename Component::array_index& array_index) noexcept {
911  return algorithms<Component>().at(array_index).get_next_action_index();
912  }
913 
914  /// Invoke the next action in the ActionList on the parallel component
915  /// `Component` on the component labeled by `array_index`.
916  template <typename Component>
918  const typename Component::array_index& array_index) noexcept {
919  algorithms<Component>()
920  .at(array_index)
921  .next_action(
923  Component>::actions_list>::value>{});
924  }
925 
926  /// Call is_ready on the next action in the action list as if on the portion
927  /// of Component labeled by array_index.
928  template <typename Component>
929  bool is_ready(const typename Component::array_index& array_index) noexcept {
930  return algorithms<Component>()
931  .at(array_index)
932  .is_ready(
934  Component>::actions_list>::value>{});
935  }
936 
937  /// Access the inboxes for a given component.
938  template <typename Component>
940  typename Component::array_index,
941  tuples::tagged_tuple_from_typelist<
943  inboxes() noexcept {
944  return tuples::get<InboxesTag<Component>>(inboxes_);
945  }
946 
947  /// Find the set of array indices on Component where the specified
948  /// inbox is not empty.
949  template <typename Component, typename InboxTag>
951  nonempty_inboxes() noexcept {
953  for (const auto& element_box : inboxes<Component>()) {
954  if (not tuples::get<InboxTag>(element_box.second).empty()) {
955  result.insert(element_box.first);
956  }
957  }
958  return result;
959  }
960 
961  /// Access the mocked algorithms for a component, indexed by array index.
962  template <typename Component>
963  auto& algorithms() noexcept {
964  return tuples::get<MockDistributedObjectsTag<Component>>(local_algorithms_);
965  }
966 
967  const GlobalCache& cache() noexcept { return cache_; }
968 
969  private:
970  GlobalCache cache_;
971  Inboxes inboxes_;
972  TupleOfMockDistributedObjects local_algorithms_;
973 };
974 
975 /// Returns a vector of all the indices of the Components
976 /// in the ComponentList that have queued actions.
977 template <typename ComponentList, typename MockRuntimeSystem,
978  typename ArrayIndex>
981  const ArrayIndex& array_index) noexcept {
982  std::vector<size_t> result{};
983  size_t i = 0;
984  tmpl::for_each<ComponentList>([&](auto tag) noexcept {
985  using Tag = typename decltype(tag)::type;
986  if (not runner->template is_simple_action_queue_empty<Tag>(array_index)) {
987  result.push_back(i);
988  }
989  ++i;
990  });
991  return result;
992 }
993 
994 /// \cond
995 namespace detail {
996 // Helper function used in invoke_queued_simple_action.
997 template <typename ComponentList, typename MockRuntimeSystem,
998  typename ArrayIndex, size_t... Is>
999 void invoke_queued_action(const gsl::not_null<MockRuntimeSystem*> runner,
1000  const size_t component_to_invoke,
1001  const ArrayIndex& array_index,
1002  std::index_sequence<Is...> /*meta*/) noexcept {
1003  const auto helper =
1004  [ component_to_invoke, &runner, &array_index ](auto I) noexcept {
1005  if (I.value == component_to_invoke) {
1006  runner->template invoke_queued_simple_action<
1007  tmpl::at_c<ComponentList, I.value>>(array_index);
1008  }
1009  };
1011 }
1012 } // namespace detail
1013 /// \endcond
1014 
1015 /// Invokes the next queued action on a random Component.
1016 /// `index_map` is the thing returned by
1017 /// `indices_of_components_with_queued_actions`
1018 template <typename ComponentList, typename MockRuntimeSystem,
1019  typename Generator, typename ArrayIndex>
1021  const gsl::not_null<Generator*> generator,
1022  const std::vector<size_t>& index_map,
1023  const ArrayIndex& array_index) noexcept {
1024  std::uniform_int_distribution<size_t> ran(0, index_map.size() - 1);
1025  const size_t component_to_invoke = index_map.at(ran(*generator));
1026  detail::invoke_queued_action<ComponentList>(
1027  runner, component_to_invoke, array_index,
1029 }
1030 } // namespace ActionTesting
Definition: ActionTesting.hpp:86
void threaded_action(Proxy &&proxy) noexcept
Invoke a threaded action on proxy, where the proxy must be a nodegroup.
Definition: Invoke.hpp:131
tmpl::remove_duplicates< tmpl::join< tmpl::transform< ActionsList, Parallel_detail::get_inbox_tags_from_action< tmpl::_1 > >> > get_inbox_tags
Given a list of Actions, get a list of the unique inbox tags.
Definition: ParallelComponentHelpers.hpp:37
Defines helper functions for the standard library.
Definition: Strahlkorper.hpp:14
CmiNodeLock create_lock() noexcept
Create a converse CmiNodeLock.
Definition: NodeLock.hpp:16
Defines class tuples::TaggedTuple.
#define SPECTRE_JUST_ALWAYS_INLINE
Always inline a function, but do not mark it inline
Definition: ForceInline.hpp:24
#define ERROR(m)
prints an error message to the standard error stream and aborts the program.
Definition: Error.hpp:35
void receive_data(Proxy &&proxy, typename ReceiveTag::temporal_id temporal_id, ReceiveDataType &&receive_data, const bool enable_if_disabled) noexcept
Send the data args... to the algorithm running on proxy, and tag the message with the identifier temp...
Definition: Invoke.hpp:51
std::vector< size_t > indices_of_components_with_queued_actions(const gsl::not_null< MockRuntimeSystem *> runner, const ArrayIndex &array_index) noexcept
Returns a vector of all the indices of the Components in the ComponentList that have queued actions...
Definition: ActionTesting.hpp:979
Overloader< Fs... > make_overloader(Fs... fs)
Create Overloader<Fs...>, see Overloader for details.
Definition: Overloader.hpp:109
size_t get_next_action_index(const typename Component::array_index &array_index) noexcept
Obtain the index into the action list of the next action.
Definition: ActionTesting.hpp:909
#define EXPAND_PACK_LEFT_TO_RIGHT(...)
Expand a parameter pack evaluating the terms from left to right.
Definition: TMPL.hpp:561
Structures used for mocking the parallel components framework in order to test actions.
Definition: ActionTesting.hpp:57
#define UNLIKELY(x)
Definition: Gsl.hpp:72
constexpr Tag::type & get(Variables< TagList > &v) noexcept
Return Tag::type pointing into the contiguous array.
Definition: Variables.hpp:649
auto & algorithms() noexcept
Access the mocked algorithms for a component, indexed by array index.
Definition: ActionTesting.hpp:963
constexpr const T & as_const(const T &t) noexcept
Returns a const reference to its argument.
Definition: ConstantExpressions.hpp:408
#define ASSERT(a, m)
Assert that an expression should be true.
Definition: Assert.hpp:49
Contains functions that forward to Charm++ parallel functions.
Definition: Abort.hpp:13
bool is_ready(const typename Component::array_index &array_index) noexcept
Call is_ready on the next action in the action list as if on the portion of Component labeled by arra...
Definition: ActionTesting.hpp:929
Defines the type alias Requires.
constexpr auto apply(F &&f, const DataBox< BoxTags > &box, Args &&... args)
Apply the function f with argument Tags TagsList from DataBox box
Definition: DataBox.hpp:1595
Definition: Determinant.hpp:11
void invoke_queued_simple_action(const typename Component::array_index &array_index) noexcept
Invoke the next queued simple action on the Component labeled by array_index.
Definition: ActionTesting.hpp:867
void invoke_queued_threaded_action(const typename Component::array_index &array_index) noexcept
Invoke the next queued threaded action on the Component labeled by array_index.
Definition: ActionTesting.hpp:885
std::string keys_of(const std::unordered_map< K, V, H > &m)
Construct a string containing the keys of a std::unordered_map.
Definition: StdHelpers.hpp:198
Defines classes and functions used for manipulating DataBox&#39;s.
MockRuntimeSystem(CacheTuple cache_contents, TupleOfMockDistributedObjects local_algorithms)
Construct from the tuple of ConstGlobalCache objects.
Definition: ActionTesting.hpp:808
A mock class for the CMake-generated Parallel::Algorithms::Array
Definition: ActionTesting.hpp:744
typename detail::make_boost_variant_over_impl< tmpl::remove_duplicates< Sequence > >::type make_boost_variant_over
Create a boost::variant with all all the types inside the typelist Sequence.
Definition: BoostHelpers.hpp:51
Defines helper functions for working with boost.
Definition: InterpolationTargetWedgeSectionTorus.hpp:24
Define simple functions for constant expressions.
Defines macro to always inline a function.
void force_next_action_to_be(const typename Component::array_index &array_index) noexcept
Instead of the next call to next_action applying the next action in the action list, force the next action to be Action
Definition: ActionTesting.hpp:893
std::unordered_map< typename Component::array_index, tuples::tagged_tuple_from_typelist< Parallel::get_inbox_tags< typename Component::action_list > > > & inboxes() noexcept
Access the inboxes for a given component.
Definition: ActionTesting.hpp:943
void next_action(const typename Component::array_index &array_index) noexcept
Invoke the next action in the ActionList on the parallel component Component on the component labeled...
Definition: ActionTesting.hpp:917
Defines macro ASSERT.
Contains a pretty_type library to write types in a "pretty" format.
Definition: ActionTesting.hpp:784
Wraps the template metaprogramming library used (brigand)
An integer multi-index.
Definition: Index.hpp:28
Defines functions and classes from the GSL.
gsl::not_null< T * > make_not_null(T *ptr) noexcept
Construct a not_null from a pointer. Often this will be done as an implicit conversion, but it may be necessary to perform the conversion explicitly when type deduction is desired.
Definition: Gsl.hpp:863
void simple_action(Proxy &&proxy) noexcept
Invoke a simple action on proxy
Definition: Invoke.hpp:112
typename Requires_detail::requires_impl< B >::template_error_type_failed_to_meet_requirements_on_template_parameters Requires
Express requirements on the template parameters of a function or class, replaces std::enable_if_t ...
Definition: Requires.hpp:67
typename DataBox_detail::compute_dbox_type< get_items< TagList >, get_compute_items< TagList > >::type compute_databox_type
Returns the type of the DataBox that would be constructed from the TagList of tags.
Definition: DataBox.hpp:1827
void simple_action(const typename Component::array_index &array_index) noexcept
Invoke the simple action Action on the Component labeled by array_index immediately.
Definition: ActionTesting.hpp:846
bool is_threaded_action_queue_empty(const typename Component::array_index &array_index) noexcept
Return true if there are no queued threaded actions on the Component labeled by array_index.
Definition: ActionTesting.hpp:875
std::string type_of_current_state(const boost::variant< Ts... > &variant) noexcept
Get the type name of the current state of the boost::variant.
Definition: BoostHelpers.hpp:93
Defines class template ConstGlobalCache.
Defines macro ERROR.
void invoke_random_queued_action(const gsl::not_null< MockRuntimeSystem *> runner, const gsl::not_null< Generator *> generator, const std::vector< size_t > &index_map, const ArrayIndex &array_index) noexcept
Invokes the next queued action on a random Component. index_map is the thing returned by indices_of_c...
Definition: ActionTesting.hpp:1020
Defines type traits, some of which are future STL type_traits header.
A class that mocks the infrastructure needed to run actions. It simulates message passing using the i...
Definition: ActionTesting.hpp:774
bool is_simple_action_queue_empty(const typename Component::array_index &array_index) noexcept
Return true if there are no queued simple actions on the Component labeled by array_index.
Definition: ActionTesting.hpp:857
void simple_action(const typename Component::array_index &array_index, Arg0 &&arg0, Args &&... args) noexcept
Invoke the simple action Action on the Component labeled by array_index immediately.
Definition: ActionTesting.hpp:835
Require a pointer to not be a nullptr
Definition: ConservativeFromPrimitive.hpp:12
constexpr T & at(std::array< T, N > &arr, Size index)
Retrieve a entry from a container, with checks in Debug mode that the index being retrieved is valid...
Definition: Gsl.hpp:124
std::unordered_set< typename Component::array_index > nonempty_inboxes() noexcept
Find the set of array indices on Component where the specified inbox is not empty.
Definition: ActionTesting.hpp:951