# Copyright 2016-2024 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
# ReFrame Project Developers. See the top-level LICENSE file for details.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Regression test class builtins
#
import functools
import reframe.core.parameters as parameters
import reframe.core.variables as variables
import reframe.core.fixtures as fixtures
import reframe.core.hooks as hooks
import reframe.utility as utils
from reframe.core.deferrable import deferrable, _DeferredPerformanceExpression
__all__ = ['deferrable', 'deprecate', 'final', 'fixture', 'loggable',
'loggable_as', 'parameter', 'performance_function', 'required',
'require_deps', 'run_before', 'run_after', 'sanity_function',
'variable']
parameter = parameters.TestParam
variable = variables.TestVar
required = variables.Undefined
deprecate = variables.TestVar.create_deprecated
fixture = fixtures.TestFixture
def final(fn):
'''Indicate that a function is final and cannot be overridden.'''
fn._rfm_final = True
return fn
# Hook-related builtins
[docs]
def run_before(stage, *, always_last=False):
'''Attach the decorated function before a certain pipeline stage.
The function will run just before the specified pipeline stage and it
cannot accept any arguments except ``self``. This decorator can be
stacked, in which case the function will be attached to multiple pipeline
stages. See above for the valid ``stage`` argument values.
:param stage: The pipeline stage where this function will be attached to.
See :ref:`pipeline-hooks` for the list of valid stage values.
:param always_last: Run this hook at the end of the stage's hook chain
instead of the beginning. If multiple tests set this flag for a hook
in the same stage, then all ``always_last`` hooks will be executed in
MRO order at the end of stage's hook chain. See :ref:`pipeline-hooks`
for an example execution.
.. versionchanged:: 4.4
The ``always_last`` argument was added.
.. versionchanged:: 4.5
Multiple tests can set ``always_last`` in the same stage.
'''
return hooks.attach_to('pre_' + stage, always_last)
[docs]
def run_after(stage, *, always_last=False):
'''Attach the decorated function after a certain pipeline stage.
This is analogous to :func:`run_before`, except that the
hook will execute right after the stage it was attached to. This decorator
also supports ``'init'`` as a valid ``stage`` argument, where in this
case, the hook will execute right after the test is initialized (i.e.
after the :func:`__init__` method is called) and before entering the
test's pipeline. In essence, a post-init hook is equivalent to defining
additional :func:`__init__` functions in the test. The following code
.. code-block:: python
class MyTest(rfm.RegressionTest):
@run_after('init')
def foo(self):
self.x = 1
is equivalent to
.. code-block:: python
class MyTest(rfm.RegressionTest):
def __init__(self):
self.x = 1
.. versionchanged:: 3.5.2
Add support for post-init hooks.
'''
return hooks.attach_to('post_' + stage, always_last)
require_deps = hooks.require_deps
# Sanity and performance function builtins
[docs]
def sanity_function(fn):
'''Decorate a test member function to mark it as a sanity check.
This decorator will convert the given function into a
:func:`~RegressionMixin.deferrable` and mark it to be executed during the
test's sanity stage. When this decorator is used, manually assigning a
value to :attr:`~RegressionTest.sanity_patterns` in the test is not
allowed.
Decorated functions may be overridden by derived classes, and derived
classes may also decorate a different method as the test's sanity
function. Decorating multiple member functions in the same class is not
allowed. However, a :class:`RegressionTest` may inherit from multiple
:class:`RegressionMixin` classes with their own sanity functions. In this
case, the derived class will follow Python's `MRO
<https://docs.python.org/3/library/stdtypes.html#class.__mro__>`_ to find
a suitable sanity function.
.. versionadded:: 3.7.0
'''
_def_fn = deferrable(fn)
setattr(_def_fn, '_rfm_sanity_fn', True)
return _def_fn
[docs]
def loggable_as(name):
'''Mark a property as loggable.
:param name: An alternative name that will be used for logging
this property. If :obj:`None`, the name of the decorated
property will be used.
:raises ValueError: if the decorated function is not a property.
.. versionadded:: 3.10.2
:meta private:
'''
def _loggable(fn):
if not hasattr(fn, 'fget'):
raise ValueError('decorated function does not '
'look like a property')
# Mark property as loggable
#
# NOTE: Attributes cannot be set on property objects, so we
# set the attribute on one of its functions
prop_name = fn.fget.__name__
fn.fget._rfm_loggable = (prop_name, name)
return fn
return _loggable
loggable = loggable_as(None)
loggable.__doc__ = '''Equivalent to :func:`loggable_as(None) <loggable_as>`.'''