SpECTRE
v2024.09.29
|
Structures used for mocking the parallel components framework in order to test actions. More...
Classes | |
struct | GlobalCoreId |
Wraps a size_t representing the global core number. More... | |
struct | InitializeDataBox< tmpl::list< SimpleTags... >, ComputeTagsList > |
struct | LocalCoreId |
Wraps a size_t representing the local core number. This is so the user can write things like emplace_array_component(NodeId{3},LocalCoreId{2},...) instead of emplace_array_component(3,2,...) . More... | |
struct | MockArrayChare |
A mock class for the CMake-generated Parallel::Algorithms::Array More... | |
class | MockDistributedObject |
MockDistributedObject mocks the DistributedObject class. It should not be considered as part of the user interface. More... | |
struct | MockGroupChare |
A mock class for the CMake-generated Parallel::Algorithms::Group More... | |
struct | MockNodeGroupChare |
A mock class for the CMake-generated Parallel::Algorithms::NodeGroup More... | |
class | MockRuntimeSystem |
A class that mocks the infrastructure needed to run actions. It simulates message passing using the inbox infrastructure and handles most of the arguments to the apply method. This mocks the Charm++ runtime system as well as the layer built on top of it as part of SpECTRE. More... | |
struct | MockSingletonChare |
A mock class for the CMake-generated Parallel::Algorithms::Singleton More... | |
struct | NodeId |
Wraps a size_t representing the node number. This is so the user can write things like emplace_array_component(NodeId{3},...) instead of emplace_array_component(3,...) . More... | |
Functions | |
bool | operator== (const NodeId &lhs, const NodeId &rhs) |
bool | operator!= (const NodeId &lhs, const NodeId &rhs) |
bool | operator== (const LocalCoreId &lhs, const LocalCoreId &rhs) |
bool | operator!= (const LocalCoreId &lhs, const LocalCoreId &rhs) |
bool | operator== (const GlobalCoreId &lhs, const GlobalCoreId &rhs) |
bool | operator!= (const GlobalCoreId &lhs, const GlobalCoreId &rhs) |
template<typename Metavariables > | |
void | set_phase (const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const Parallel::Phase &phase) |
Set the phase of all parallel components to phase | |
template<typename Component , typename... Options> | |
void | emplace_component (const gsl::not_null< MockRuntimeSystem< typename Component::metavariables > * > runner, const typename Component::array_index &array_index, Options &&... opts) |
Emplaces a distributed object with index array_index into the parallel component Component . The options opts are forwarded to be used in a call to detail::ForwardAllOptionsToDataBox::apply . | |
template<typename Component , typename... Options> | |
void | emplace_array_component (const gsl::not_null< MockRuntimeSystem< typename Component::metavariables > * > runner, const NodeId node_id, const LocalCoreId local_core_id, const typename Component::array_index &array_index, Options &&... opts) |
Emplaces a distributed array object with index array_index into the parallel component Component . The options opts are forwarded to be used in a call to detail::ForwardAllOptionsToDataBox::apply . | |
template<typename Component , typename... Options> | |
void | emplace_singleton_component (const gsl::not_null< MockRuntimeSystem< typename Component::metavariables > * > runner, const NodeId node_id, const LocalCoreId local_core_id, Options &&... opts) |
Emplaces a distributed singleton object into the parallel component Component . The options opts are forwarded to be used in a call to detail::ForwardAllOptionsToDataBox::apply . | |
template<typename Component , typename... Options> | |
void | emplace_group_component (const gsl::not_null< MockRuntimeSystem< typename Component::metavariables > * > runner, Options &&... opts) |
Emplaces a distributed group object into the parallel component Component . The options opts are forwarded to be used in a call to detail::ForwardAllOptionsToDataBox::apply . | |
template<typename Component , typename... Options> | |
void | emplace_nodegroup_component (const gsl::not_null< MockRuntimeSystem< typename Component::metavariables > * > runner, Options &&... opts) |
Emplaces a distributed nodegroup object into the parallel component Component . The options opts are forwarded to be used in a call to detail::ForwardAllOptionsToDataBox::apply . | |
template<typename Component , typename... Options, typename Metavars = typename Component::metavariables> | |
void | emplace_component_and_initialize (const gsl::not_null< MockRuntimeSystem< typename Component::metavariables > * > runner, const typename Component::array_index &array_index, const typename detail::get_initialization< Component >::InitialValues &initial_values, Options &&... opts) |
Emplaces a distributed object with index array_index into the parallel component Component . The options opts are forwarded to be used in a call to detail::ForwardAllOptionsToDataBox::apply Additionally, the simple tags in the DataBox are initialized from the values set in initial_values . | |
template<typename Component , typename... Options, typename Metavars = typename Component::metavariables> | |
void | emplace_array_component_and_initialize (const gsl::not_null< MockRuntimeSystem< typename Component::metavariables > * > runner, const NodeId node_id, const LocalCoreId local_core_id, const typename Component::array_index &array_index, const typename detail::get_initialization< Component >::InitialValues &initial_values, Options &&... opts) |
Emplaces a distributed array object with index array_index into the parallel component Component . The options opts are forwarded to be used in a call to detail::ForwardAllOptionsToDataBox::apply Additionally, the simple tags in the DataBox are initialized from the values set in initial_values . | |
template<typename Component , typename... Options, typename Metavars = typename Component::metavariables> | |
void | emplace_singleton_component_and_initialize (const gsl::not_null< MockRuntimeSystem< typename Component::metavariables > * > runner, const NodeId node_id, const LocalCoreId local_core_id, const typename detail::get_initialization< Component >::InitialValues &initial_values, Options &&... opts) |
Emplaces a distributed singleton object into the parallel component Component . The options opts are forwarded to be used in a call to detail::ForwardAllOptionsToDataBox::apply Additionally, the simple tags in the DataBox are initialized from the values set in initial_values . | |
template<typename Component , typename... Options, typename Metavars = typename Component::metavariables> | |
void | emplace_group_component_and_initialize (const gsl::not_null< MockRuntimeSystem< typename Component::metavariables > * > runner, const typename detail::get_initialization< Component >::InitialValues &initial_values, Options &&... opts) |
Emplaces a distributed group object into the parallel component Component . The options opts are forwarded to be used in a call to detail::ForwardAllOptionsToDataBox::apply Additionally, the simple tags in the DataBox are initialized from the values set in initial_values . | |
template<typename Component , typename... Options, typename Metavars = typename Component::metavariables> | |
void | emplace_nodegroup_component_and_initialize (const gsl::not_null< MockRuntimeSystem< typename Component::metavariables > * > runner, const typename detail::get_initialization< Component >::InitialValues &initial_values, Options &&... opts) |
Emplaces a distributed nodegroup object into the parallel component Component . The options opts are forwarded to be used in a call to detail::ForwardAllOptionsToDataBox::apply Additionally, the simple tags in the DataBox are initialized from the values set in initial_values . | |
template<typename Component , typename Metavariables > | |
size_t | get_next_action_index (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Get the index in the action list of the next action. | |
template<typename Component , typename Tag , typename Metavariables > | |
const auto & | get_databox_tag (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Returns the Tag from the DataBox of the parallel component Component with array index array_index . If the component's current DataBox type does not contain Tag then an error is emitted. | |
template<typename Component , typename Tag , typename Metavariables > | |
bool | box_contains (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Returns true if the current DataBox of Component with index array_index contains the tag Tag . If the tag is not contained, returns false . | |
template<typename Component , typename Tag , typename Metavariables > | |
bool | tag_is_retrievable (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Returns true if the tag Tag can be retrieved from the current DataBox of Component with index array_index . | |
template<typename Component , typename Metavariables > | |
void | next_action (const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index) |
Invoke the next action in the ActionList on the parallel component Component on the component labeled by array_index , failing if it was not ready. | |
template<typename Component , typename Metavariables > | |
bool | next_action_if_ready (const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index) |
Invoke the next action in the ActionList on the parallel component Component on the component labeled by array_index , returning whether it was ready. | |
template<typename Component , typename Action , typename Metavariables , typename... Args> | |
void | simple_action (const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index, Args &&... args) |
Runs the simple action Action on the array_index th element of the parallel component Component . | |
template<typename Component , typename Action , typename Metavariables , typename... Args> | |
void | queue_simple_action (const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index, Args &&... args) |
Queues the simple action Action on the array_index th element of the parallel component Component . | |
template<typename Component , typename Action , typename Metavariables , typename... Args> | |
void | threaded_action (const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index, Args &&... args) |
Runs the simple action Action on the array_index th element of the parallel component Component . | |
template<typename Component , typename Metavariables > | |
void | invoke_queued_simple_action (const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index) |
Runs the next queued simple action on the array_index th element of the parallel component Component . | |
template<typename Component , typename Metavariables > | |
bool | is_simple_action_queue_empty (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Returns true if there are no simple actions in the queue. | |
template<typename Component , typename Metavariables > | |
size_t | number_of_queued_simple_actions (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Returns the number of simple actions in the queue. | |
template<typename Component , typename Metavariables > | |
void | invoke_queued_threaded_action (const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index) |
Runs the next queued threaded action on the array_index th element of the parallel component Component . | |
template<typename Component , typename Metavariables > | |
bool | is_threaded_action_queue_empty (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Returns true if there are no threaded actions in the queue. | |
template<typename Component , typename Metavariables > | |
size_t | number_of_queued_threaded_actions (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Returns the number of threaded actions in the queue. | |
template<typename Component , typename Metavariables > | |
bool | get_terminate (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Returns whether or not the Component with index array_index has been terminated. | |
template<typename Component , typename Metavariables , typename ArrayIndex > | |
Parallel::GlobalCache< Metavariables > & | cache (MockRuntimeSystem< Metavariables > &runner, const ArrayIndex &array_index) |
Returns the GlobalCache of Component with index array_index . | |
template<typename ComponentList , typename MockRuntimeSystem , typename ArrayIndex > | |
std::vector< size_t > | indices_of_components_with_queued_simple_actions (const gsl::not_null< MockRuntimeSystem * > runner, const ArrayIndex &array_index) |
Returns a vector of all the indices of the Components in the ComponentList that have queued simple actions, for a particular array_index. | |
template<typename ComponentList , typename MockRuntimeSystem > | |
auto | array_indices_with_queued_simple_actions (const gsl::not_null< MockRuntimeSystem * > runner) -> detail::array_indices_for_each_component< ComponentList > |
Returns a vector of array_indices for each Component. The vector is filled with only those array_indices for which there are queued simple actions. | |
template<typename ComponentList > | |
size_t | number_of_elements_with_queued_simple_actions (const detail::array_indices_for_each_component< ComponentList > &array_indices) |
Given the output of array_indices_with_queued_simple_actions , returns the total number of array indices that have queued simple actions. | |
template<typename ComponentList , typename MockRuntimeSystem , typename Generator > | |
void | invoke_random_queued_simple_action (const gsl::not_null< MockRuntimeSystem * > runner, const gsl::not_null< Generator * > generator, const detail::array_indices_for_each_component< ComponentList > &array_indices) |
Invokes the next queued action on a random Component on a random array_index of that component. array_indices is the thing returned by array_indices_with_queued_simple_actions | |
template<typename Component , typename Metavariables > | |
const auto & | get_databox (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Retrieves the DataBox from the parallel component Component with index array_index . | |
template<typename Component , typename Metavariables > | |
auto & | get_databox (const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index) |
Retrieves the DataBox from the parallel component Component with index array_index . | |
template<typename Component , typename InboxTag , typename Metavariables > | |
const auto & | get_inbox_tag (const MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) |
Returns the InboxTag from the parallel component Component with array index array_index . | |
template<typename Component , typename InboxTag , typename Metavariables > | |
auto & | get_inbox_tag (const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index) |
Returns the InboxTag from the parallel component Component with array index array_index . | |
Structures used for mocking the parallel components framework in order to test actions.
The ActionTesting framework is designed to mock the parallel components so that actions and sequences of actions can be tested in a controlled environment that behaves effectively identical to the actual parallel environment.
The action testing framework (ATF) works essentially identically to the parallel infrastructure. A metavariables must be supplied which must at least list the components used (using component_list = tmpl::list<>
). As a simple example, let's look at the test for the Parallel::Actions::TerminatePhase
action.
The Metavariables
is given by:
The component list in this case just contains a single component that is described below.
The component is templated on the metavariables, which, while not always necessary, eliminates some compilation issues that may arise otherwise. The component for the TerminatePhase
test is given by:
Just like with the standard parallel code, a metavariables
type alias must be present. The chare type should be ActionTesting::MockArrayChare
, ActionTesting::MockGroupChare
, ActionTesting::MockNodeGroupChare
, or ActionTesting::MockSingletonChare
. Currently many groups, nodegroups, and singletons are treated during action testing as a one-element array component, but that usage is deprecated and will eventually be removed. The index_type
must be whatever the actions will use to index the array. In the case of a singleton int
is recommended. Finally, the phase_dependent_action_list
must be specified just as in the cases for parallel executables. In this case only the Testing
phase is used and only has the TerminatePhase
action listed.
The SPECTRE_TEST_CASE
is
The type alias component = Component<Metavariables>
is just used to reduce the amount of typing needed. The ATF provides a ActionTesting::MockRuntimeSystem
class, which is the code that takes the place of the parallel runtime system (RTS) (e.g. Charm++) that manages the actions and components. The constructor of ActionTesting::MockRuntimeSystem
takes a std::vector<size_t>
whose length is the number of (mocked) nodes and whose values are the number of (mocked) cores on each node; the RTS runs only on a single core, but it keeps track of which components are on which (mocked) nodes and cores so that one can test some of the functionality of multiple cores/nodes. Components are added to the RTS using the functions ActionTesting::emplace_array_component()
, ActionTesting::emplace_singleton_component()
, ActionTesting::emplace_group_component()
, and ActionTesting::emplace_nodegroup_component()
. Currently there is a deprecated ActionTesting::emplace_component()
function that is the same as ActionTesting::emplace_array_component()
and can be used for the deprecated usage of treating singletons, groups, and nodegroups as single-element arrays. The emplace functions take the runner by not_null
. The array emplace function take the array index of the component being inserted (0
in the above example, but this is arbitrary), and the array and singleton emplace functions take the (mocked) node and core on which the component lives. The emplace function for groups places its object on all (mocked) cores and the array index is the same as the global core index; the emplace function for nodegroups places its object on a single (mocked) core on each (mocked) node, and the array index is the same as the node index. All emplace functions optionally take a parameter pack of additional arguments that are forwarded to the constructor of the component. These additional arguments are how input options (set in the simple_tags_from_options
type alias of the parallel component) get passed to parallel components.
With the ATF, everything is controlled explicitly, providing the ability to perform full introspection into the state of the RTS at virtually any point during the execution. This means that the phase is not automatically advanced like it is in a parallel executable, but instead must be advanced by calling the ActionTesting::set_phase
function. While testing the TerminatePhase
action we are only interesting in checking whether the algorithm has set the terminate
flag. This is done using the ActionTesting::get_terminate()
function and is done for each distributed object (i.e. per component). The next action in the action list for the current phase is invoked by using the ActionTesting::next_action()
function, which takes as arguments the runner by not_null
and the array index of the distributed object to invoke the next action on.
Having covered the very basics of ATF let us look at the test for the Actions::Goto
action. We will introduce the functionality InitializeDataBox
,force_next_action_to_be
, get_next_action_index
, and get_databox_tag
. The Goto<the_label_class>
action changes the next action in the algorithm to be the Actions::Label<the_label_class>
. For this test the Metavariables is:
You can see that this time there are two test phases, Testing
and Execute
.
The component for this case is quite a bit more complicated so we will go through it in detail. It is given by
In addition to the Initialization
and Exit
phases, there are two additional phases, Testing
and Execute
. Just as before there are metavariables
, chare_type
, and array_index
type aliases. The repeat_until_phase_action_list
is the list of iterable actions that are called during the Execute
phase. The Initialization
phase action list now has the action ActionTesting::InitializeDataBox
, which takes simple tags and compute tags that will be added to the DataBox in the Initialization phase. How the values for the simple tags are set will be made clear below when we discuss the ActionTesting::emplace_component_and_initialize()
functions. The action lists for the Testing
and Execute
phases are fairly simple and not the focus of this discussion.
Having discussed the metavariables and component, let us now look at the test case.
Just as for the TerminatePhase
test we have some type aliases to reduce the amount of typing needed and to make the test easier to read. The runner is again created with the default constructor. However, the component is now inserted using the functions ActionTesting::emplace_array_component_and_initialize()
, ActionTesting::emplace_singleton_component_and_initialize()
, ActionTesting::emplace_group_component_and_initialize()
, and ActionTesting::emplace_nodegroup_component_and_initialize()
. There is a deprecated function ActionTesting::emplace_component_and_initialize()
that is used when one treats groups, nodegroups, and singletons as arrays. The third argument to the ActionTesting::emplace_component_and_initialize()
functions is a tuples::TaggedTuple
of the simple tags, which is to be populated with the initial values for the component. Note that there is no call to ActionTesting::set_phase (&runner, Parallel::Phase::Initialization)
: this and the required action invocation is handled internally by the ActionTesting::emplace_component_and_initialize()
functions.
Once the phase is set the next action to be executed is set to be Actions::Label<Label1>
by calling the MockRuntimeSystem::force_next_action_to_be()
member function of the runner. The argument to the function is the array index of the component for which to set the next action. After the Label<Label1>
action is invoked we check that the next action is the fourth (index 3
because we use zero-based indexing) by calling the MockRuntimeSystem::get_next_action_index()
member function. For clarity, the indices of the actions in the Phase::Testing
phase are:
Actions::Goto<Label1>
Actions::Label<Label2>
Actions::Label<Label1>
Actions::Goto<Label2>
Since the exact DataBox type of any component at some point in the action list may not be known, the ActionTesting::get_databox_tag()
function is provided. An example usage of this function can be seen in the last line of the test snippet above. The component and tag are passed as template parameters while the runner and array index are passed as arguments. It is also possible to retrieve DataBox directly using the ActionTesting::get_databox()
function if the DataBox type is known:
There is also a gsl::not_null
overload of ActionTesting::get_databox()
that can be used to mutate tags in the DataBox. It is also possible to check if an item can be retrieved from the DataBox using the ActionTesting::tag_is_retrievable()
function as follows:
A simple action can be invoked on a distributed object or component using the ActionTesting::simple_action()
function:
A threaded action can be invoked on a distributed object or component using the ActionTesting::threaded_action()
function:
Sometimes an individual action calls other actions but we still want to be able to test the action and its effects in isolation. To this end the ATF supports replacing calls to actions with calls to other actions. For example, instead of calling simple_action_a
we want to call simple_action_a_mock
which just verifies the data received is correct and sets a flag in the DataBox that it was invoked. This can be done by setting the type aliases replace_these_simple_actions
and with_these_simple_actions
in the parallel component definition as follows:
The length of the two type lists must be the same and the Nth action in replace_these_simple_actions
gets replaced by the Nth action in with_these_simple_actions
. Note that simple_action_a_mock
will get invoked in any context that simple_action_a
is called to be invoked. The same feature also exists for threaded actions:
Furthermore, simple actions invoked from an action are not run immediately. Instead, they are queued so that order randomization and introspection may occur. The simplest form of introspection is checking whether the simple action queue is empty:
The ActionTesting::invoke_queued_simple_action()
invokes the next queued simple action:
Note that the same functionality exists for threaded actions. The functions are ActionTesting::is_threaded_action_queue_empty()
, and ActionTesting::invoke_queued_threaded_action()
.
The ATF, in general, does not support reduction actions at the moment. Meaning that calls to Parallel::contribute_to_reduction
and Parallel::threaded_action<observers::ThreadedActions::WriteReductionData>
are not supported. However, the ATF does support calls using WriteReductionDataRow
without having to actually write any data to disk (this avoids unnecessary IO when all we really care about is if the values are correct).
If you want to test code that has a call to Parallel::threaded_action< observers::ThreadedActions::WriteReductionDataRow>
, you'll need to add the TestHelpers::observers::MockObserverWriter
component, found in Helpers/IO/Observers/MockWriteReductionDataRow.hpp, to your test metavars (see next section for what mocking a component means). Initialize the component as follows:
Then, when you want to check the data that was "written", you can do something along the lines of
The data is stored in MockH5File
and MockDat
objects, which have similar interfaces to h5::H5File
and h5::Dat
, respectively.
An action can invoke an action on another parallel component. In this case we need to be able to tell the mocking framework to replace the component the action is trying to invoke the other action on and instead use a different component that we have set up to mock the original component. For example, the action below invokes an action on ComponentB
Let us assume we cannot use the component ComponentB
in our test and that we need to mock it. We do so using the type alias component_being_mocked
in the mock component:
When creating the runner ComponentBMock
is emplaced:
Any checks and function calls into the ATF also use ComponentBMock
. For example:
Actions sometimes need tags/items to be placed into the Parallel::GlobalCache
. Once the list of tags for the global cache has been assembled, the associated objects need to be inserted. This is done using the constructor of the ActionTesting::MockRuntimeSystem
. For example, consider the tags:
These are added into the global cache by, for example, the metavariables:
A constructor of ActionTesting::MockRuntimeSystem
takes a tuples::TaggedTuple
of the const global cache tags. If you know the order of the tags in the tuples::TaggedTuple
you can use the constructor without explicitly specifying the type of the tuples::TaggedTuple
as follows:
If you do not know the order of the tags but know all the tags that are present you can use another constructor where you explicitly specify the tuples::TaggedTuple
type:
Similarly to const global cache tags, sometimes Actions will want to change the data of a tag in the Parallel::GlobalCache
. Consider this tag:
To indicate that this tag in the global cache may be changed, it is added by, for example, the metavariables:
Then, exactly like the const global cache tags, the constructor of ActionTesting::MockRuntimeSystem
takes a tuples::TaggedTuple
of the mutable global cache tags as its second argument. The mutable global cache tags are empty by default so you need not specify a tuples::TaggedTuple
if you don't have any mutable global cache tags. Here is how you would specify zero const global cache tags and one mutable global cache tag:
To mutate a tag in the mutable global cache, use the Parallel::mutate
function just like you would in a charm-aware situation.
The inbox tags can also be retrieved from a component by using the ActionTesting::get_inbox_tag
function. Both a const version:
and a non-const version exist:
The non-const version can be used like in the above example to clear or otherwise manipulate the inbox tags.