Skip to content

Commit ff28278

Browse files
author
Vasileios Karakasis
authored
Merge pull request #2470 from vkarak/refactor/move-builtins
[refactor] Move builtins in a separate module file and improve `make_test()`
2 parents 2db7e2f + 3a5f03c commit ff28278

File tree

9 files changed

+998
-752
lines changed

9 files changed

+998
-752
lines changed

docs/regression_test_api.rst

Lines changed: 58 additions & 554 deletions
Large diffs are not rendered by default.

reframe/core/builtins.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# Copyright 2016-2022 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
2+
# ReFrame Project Developers. See the top-level LICENSE file for details.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
#
7+
# Regression test class builtins
8+
#
9+
10+
import functools
11+
import reframe.core.parameters as parameters
12+
import reframe.core.variables as variables
13+
import reframe.core.fixtures as fixtures
14+
import reframe.core.hooks as hooks
15+
import reframe.utility as utils
16+
from reframe.core.deferrable import deferrable, _DeferredPerformanceExpression
17+
18+
19+
__all__ = ['deferrable', 'deprecate', 'final', 'fixture', 'loggable',
20+
'loggable_as', 'parameter', 'performance_function', 'required',
21+
'require_deps', 'run_before', 'run_after', 'sanity_function',
22+
'variable']
23+
24+
parameter = parameters.TestParam
25+
variable = variables.TestVar
26+
required = variables.Undefined
27+
deprecate = variables.TestVar.create_deprecated
28+
fixture = fixtures.TestFixture
29+
30+
31+
def final(fn):
32+
'''Indicate that a function is final and cannot be overridden.'''
33+
34+
fn._rfm_final = True
35+
return fn
36+
37+
38+
# Hook-related builtins
39+
40+
def run_before(stage):
41+
'''Attach the decorated function before a certain pipeline stage.
42+
43+
The function will run just before the specified pipeline stage and it
44+
cannot accept any arguments except ``self``. This decorator can be
45+
stacked, in which case the function will be attached to multiple pipeline
46+
stages. See above for the valid ``stage`` argument values.
47+
48+
:param stage: The pipeline stage where this function will be attached to.
49+
See :ref:`pipeline-hooks` for the list of valid stage values.
50+
'''
51+
return hooks.attach_to('pre_' + stage)
52+
53+
54+
def run_after(stage):
55+
'''Attach the decorated function after a certain pipeline stage.
56+
57+
This is analogous to :func:`~RegressionMixin.run_before`, except that the
58+
hook will execute right after the stage it was attached to. This decorator
59+
also supports ``'init'`` as a valid ``stage`` argument, where in this
60+
case, the hook will execute right after the test is initialized (i.e.
61+
after the :func:`__init__` method is called) and before entering the
62+
test's pipeline. In essence, a post-init hook is equivalent to defining
63+
additional :func:`__init__` functions in the test. The following code
64+
65+
.. code-block:: python
66+
67+
class MyTest(rfm.RegressionTest):
68+
@run_after('init')
69+
def foo(self):
70+
self.x = 1
71+
72+
is equivalent to
73+
74+
.. code-block:: python
75+
76+
class MyTest(rfm.RegressionTest):
77+
def __init__(self):
78+
self.x = 1
79+
80+
.. versionchanged:: 3.5.2
81+
Add support for post-init hooks.
82+
83+
'''
84+
return hooks.attach_to('post_' + stage)
85+
86+
87+
require_deps = hooks.require_deps
88+
89+
90+
# Sanity and performance function builtins
91+
92+
def sanity_function(fn):
93+
'''Decorate a test member function to mark it as a sanity check.
94+
95+
This decorator will convert the given function into a
96+
:func:`~RegressionMixin.deferrable` and mark it to be executed during the
97+
test's sanity stage. When this decorator is used, manually assigning a
98+
value to :attr:`~RegressionTest.sanity_patterns` in the test is not
99+
allowed.
100+
101+
Decorated functions may be overridden by derived classes, and derived
102+
classes may also decorate a different method as the test's sanity
103+
function. Decorating multiple member functions in the same class is not
104+
allowed. However, a :class:`RegressionTest` may inherit from multiple
105+
:class:`RegressionMixin` classes with their own sanity functions. In this
106+
case, the derived class will follow Python's `MRO
107+
<https://docs.python.org/3/library/stdtypes.html#class.__mro__>`_ to find
108+
a suitable sanity function.
109+
110+
.. versionadded:: 3.7.0
111+
'''
112+
113+
_def_fn = deferrable(fn)
114+
setattr(_def_fn, '_rfm_sanity_fn', True)
115+
return _def_fn
116+
117+
118+
def performance_function(unit, *, perf_key=None):
119+
'''Decorate a test member function to mark it as a performance metric
120+
function.
121+
122+
This decorator converts the decorated method into a performance deferrable
123+
function (see ":ref:`deferrable-performance-functions`" for more details)
124+
whose evaluation is deferred to the performance stage of the regression
125+
test. The decorated function must take a single argument without a default
126+
value (i.e. ``self``) and any number of arguments with default values. A
127+
test may decorate multiple member functions as performance functions,
128+
where each of the decorated functions must be provided with the unit of
129+
the performance quantity to be extracted from the test. Any performance
130+
function may be overridden in a derived class and multiple bases may
131+
define their own performance functions. In the event of a name conflict,
132+
the derived class will follow Python's `MRO
133+
<https://docs.python.org/3/library/stdtypes.html#class.__mro__>`_ to
134+
choose the appropriate performance function. However, defining more than
135+
one performance function with the same name in the same class is
136+
disallowed.
137+
138+
The full set of performance functions of a regression test is stored under
139+
:attr:`~reframe.core.pipeline.RegressionTest.perf_variables` as key-value
140+
pairs, where, by default, the key is the name of the decorated member
141+
function, and the value is the deferred performance function itself.
142+
Optionally, the key under which a performance function is stored in
143+
:attr:`~reframe.core.pipeline.RegressionTest.perf_variables` can be
144+
customised by passing the desired key as the ``perf_key`` argument to this
145+
decorator.
146+
147+
:param unit: A string representing the measurement unit of this metric.
148+
149+
.. versionadded:: 3.8.0
150+
151+
'''
152+
153+
if not isinstance(unit, str):
154+
raise TypeError('performance unit must be a string')
155+
156+
if perf_key and not isinstance(perf_key, str):
157+
raise TypeError("'perf_key' must be a string")
158+
159+
def _deco_wrapper(func):
160+
if not utils.is_trivially_callable(func, non_def_args=1):
161+
raise TypeError(
162+
f'performance function {func.__name__!r} has more '
163+
f'than one argument without a default value'
164+
)
165+
166+
@functools.wraps(func)
167+
def _perf_fn(*args, **kwargs):
168+
return _DeferredPerformanceExpression(
169+
func, unit, *args, **kwargs
170+
)
171+
172+
_perf_key = perf_key if perf_key else func.__name__
173+
setattr(_perf_fn, '_rfm_perf_key', _perf_key)
174+
return _perf_fn
175+
176+
return _deco_wrapper
177+
178+
179+
def loggable_as(name):
180+
'''Mark a property as loggable.
181+
182+
:param name: An alternative name that will be used for logging
183+
this property. If :obj:`None`, the name of the decorated
184+
property will be used.
185+
:raises ValueError: if the decorated function is not a property.
186+
187+
.. versionadded:: 3.10.2
188+
189+
:meta private:
190+
191+
'''
192+
def _loggable(fn):
193+
if not hasattr(fn, 'fget'):
194+
raise ValueError('decorated function does not '
195+
'look like a property')
196+
197+
# Mark property as loggable
198+
#
199+
# NOTE: Attributes cannot be set on property objects, so we
200+
# set the attribute on one of its functions
201+
prop_name = fn.fget.__name__
202+
fn.fget._rfm_loggable = (prop_name, name)
203+
return fn
204+
205+
return _loggable
206+
207+
208+
loggable = loggable_as(None)
209+
loggable.__doc__ = '''Equivalent to :func:`loggable_as(None) <loggable_as>`.'''

reframe/core/deferrable.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88

99

1010
def deferrable(func):
11-
'''Function decorator for converting a function to a deferred
12-
expression.'''
11+
'''Convert the decorated function to a deferred expression.
12+
13+
See :ref:`deferrable-functions` for further information on deferrable
14+
functions.
15+
'''
16+
1317
@functools.wraps(func)
1418
def _deferred(*args, **kwargs):
1519
return _DeferredExpression(func, *args, **kwargs)

0 commit comments

Comments
 (0)