You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Directives are functions that can be called directly in the body of a ReFrame regression test class.
45
-
These functions exert control over the test creation, and they allow adding and/or modifying certain attributes of the regression test.
46
-
For example, a test can be parameterized using the :func:`parameter` directive as follows:
43
+
.. versionadded:: 3.4.2
47
44
48
-
.. code:: python
45
+
ReFrame provides built-in functions that facilitate the creation of extensible tests (i.e. a test library).
46
+
These *builtins* are intended to be used directly in the class body of the test, allowing the ReFrame internals to *pre-process* their input before the actual test creation takes place.
47
+
This provides the ReFrame internals with further control over the user's input, making the process of writing regression tests less error-prone thanks to a better error checking.
48
+
In essence, these builtins exert control over the test creation, and they allow adding and/or modifying certain attributes of the regression test.
If a parameter with a matching name is already present in the parameter space of a parent class, the existing parameter values will be combined with those provided by this method following the inheritance behavior set by the arguments ``inherit_params`` and ``filter_params``.
55
+
Instead, if no parameter with a matching name exists in any of the parent parameter spaces, a new regression test parameter is created.
56
+
A regression test can be parametrized as follows:
57
+
58
+
.. code:: python
59
+
60
+
classFoo(rfm.RegressionTest):
61
+
variant = parameter(['A', 'B'])
49
62
50
-
classMyTest(rfm.RegressionTest):
51
-
parameter('variant', ['A', 'B'])
52
-
53
63
def__init__(self):
54
64
ifself.variant =='A':
55
65
do_this()
56
66
else:
57
67
do_other()
58
68
59
-
One of the most powerful features about using directives is that they store their input information at the class level.
60
-
This means if one were to extend or specialize an existing regression test, the test attribute additions and modifications made through directives in the parent class will be automatically inherited by the child test.
61
-
For instance, continuing with the example above, one could override the :func:`__init__` method in the :class:`MyTest` regression test as follows:
62
-
63
-
.. code:: python
69
+
One of the most powerful features about these built-in functions is that they store their input information at the class level.
70
+
This means if one were to extend or specialize an existing regression test, the test attribute additions and modifications made through built-in functions in the parent class will be automatically inherited by the child test.
71
+
For instance, continuing with the example above, one could override the :func:`__init__` method in the :class:`MyTest` regression test as follows:
64
72
65
-
classMyModifiedTest(MyTest):
73
+
.. code:: python
66
74
75
+
classBar(Foo):
67
76
def__init__(self):
68
77
ifself.variant =='A':
69
78
override_this()
70
79
else:
71
80
override_other()
72
81
82
+
Note that this built-in parameter function provides an alternative method to parameterize a test to :func:`reframe.core.decorators.parameterized_test`, and the use of both approaches in the same test is currently disallowed.
83
+
The two main advantages of the built-in :func:`parameter` over the decorated approach reside in the parameter inheritance across classes and the handling of large parameter sets.
84
+
As shown in the example above, the parameters declared with the built-in :func:`parameter` are automatically carried over into derived tests through class inheritance, whereas tests using the decorated approach would have to redefine the parameters on every test.
85
+
Similarly, parameters declared through the built-in :func:`parameter` are regarded as fully independent from each other and ReFrame will automatically generate as many tests as available parameter combinations. This is a major advantage over the decorated approach, where one would have to manually expand the parameter combinations.
86
+
This is illustrated in the example below, consisting of a case with two parameters, each having two possible values.
87
+
88
+
.. code:: python
89
+
90
+
# Parameterized test with two parameters (p0 = ['a', 'b'] and p1 = ['x', 'y'])
# This is easier to write with the parameter built-in.
97
+
@rfm.simple_test
98
+
classBar(rfm.RegressionTest):
99
+
p0 = parameter(['a', 'b'])
100
+
p1 = parameter(['x', 'y'])
101
+
102
+
def__init__(self):
103
+
do_something(self.p0, self.p1)
104
+
105
+
106
+
:param values: A list containing the parameter values.
107
+
If no values are passed when creating a new parameter, the parameter is considered as *declared* but not *defined* (i.e. an abstract parameter).
108
+
Instead, for an existing parameter, this depends on the parameter's inheritance behaviour and on whether any values where provided in any of the parent parameter spaces.
109
+
:param inherit_params: If :obj:`False`, no parameter values that may have been defined in any of the parent parameter spaces will be inherited.
110
+
:param filter_params: Function to filter/modify the inherited parameter values that may have been provided in any of the parent parameter spaces.
111
+
This function must accept a single argument, which will be passed as an iterable containing the inherited parameter values.
112
+
This only has an effect if used with ``inherit_params=True``.
Declaring a test variable through the :func:`variable` built-in allows for a more robust test implementation than if the variables were just defined as regular test attributes (e.g. ``self.a = 10``).
119
+
Using variables declared through the :func:`variable` built-in guarantees that these regression test variables will not be redeclared by any child class, while also ensuring that any values that may be assigned to such variables comply with its original declaration.
120
+
In essence, by using test variables, the user removes any potential test errors that might be caused by accidentally overriding a class attribute. See the example below.
121
+
122
+
123
+
.. code:: python
124
+
125
+
classFoo(rfm.RegressionTest):
126
+
my_var = variable(int, value=8)
127
+
not_a_var =4
128
+
129
+
def__init__(self):
130
+
print(self.my_var) # prints 8.
131
+
# self.my_var = 'override' # Error: my_var must be an int!
132
+
self.not_a_var ='override'# However, this would work. Dangerous!
133
+
self.my_var =10# tests may also assign values the standard way
134
+
135
+
The argument ``value`` in the :func:`variable` built-in sets the default value for the variable.
136
+
As mentioned above, a variable may not be declared more than once, but its default value can be updated by simply assigning it a new value directly in the class body.
137
+
138
+
.. code:: python
139
+
140
+
classBar(Foo):
141
+
my_var =4
142
+
# my_var = 'override' # Error again!
143
+
144
+
def__init__(self):
145
+
print(self.my_var) # prints 4.
146
+
147
+
Here, the class :class:`Bar` inherits the variables from :class:`Foo` and can see that ``my_var`` has already been declared in the parent class. Therefore, the value of ``my_var`` is updated ensuring that the new value complies to the original variable declaration.
148
+
149
+
These examples above assumed that a default value can be provided to the variables in the bases tests, but that might not always be the case.
150
+
For example, when writing a test library, one might want to leave some variables undefined and force the user to set these when using the test.
151
+
As shown in the example below, imposing such requirement is as simple as not passing any ``value`` to the :func:`variable` built-in, which marks the given variable as *required*.
Similarly to a variable with a value already assigned to it, the value of a required variable may be set either directly in the class body, on the :func:`__init__` method, or in any other hook before it is referenced.
183
+
Otherwise an error will be raised indicating that a required variable has not been set.
184
+
Conversely, a variable with a default value already assigned to it can be made required by assigning it the ``required`` keyword.
If a parameter with a matching name is already present in the parameter space of a parent class, the existing parameter values will be combined with those provided by this method following the inheritance behaviour set by the arguments ``inherit_params`` and ``filter_params``.
78
-
Instead, if no parameter with a matching name exists in any of the parent parameter spaces, a new regression test parameter is created.
188
+
classMyRequiredTest(HelloTest):
189
+
what = required
79
190
80
-
:param name: The parameter name.
81
-
:param values: A list containing the parameter values.
82
-
If no values are passed when creating a new parameter, the parameter is considered as *declared* but not *defined* (i.e. an abstract parameter).
83
-
Instead, for an existing parameter, this depends on the parameter's inheritance behaviour and on whether any values where provided in any of the parent parameter spaces.
84
-
:param inherit_params: If :obj:`False`, no parameter values that may have been defined in any of the parent parameter spaces will be inherited.
85
-
:param filter_params: Function to filter/modify the inherited parameter values that may have been provided in any of the parent parameter spaces.
86
-
This function must accept a single argument, which will be passed as an iterable containing the inherited parameter values.
87
-
This only has an effect if used with ``inherit_params=True``.
88
191
192
+
Running the above test will cause the :func:`__init__` method from :class:`EchoBaseTest` to throw an error indicating that the variable ``what`` has not been set.
89
193
194
+
:param types: the supported types for the variable.
195
+
:param value: the default value assigned to the variable. If no value is provided, the variable is set as ``required``.
196
+
:param field: the field validator to be used for this variable.
197
+
If no field argument is provided, it defaults to
198
+
:class:`reframe.core.fields.TypedField`.
199
+
Note that the field validator provided by this argument must derive from
0 commit comments