Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
ccc3350
Add support for directive-based test parameters.
jjotero Nov 5, 2020
f2be8c6
Cleanup parameterized test implementation
jjotero Nov 5, 2020
4231f96
Fix PEP8.
jjotero Nov 5, 2020
408c3bf
Remove unnecessary copy.
jjotero Nov 5, 2020
574eba5
Add attributest to purge the test parameter space
jjotero Nov 5, 2020
f329d83
Make if syntax more compact.
jjotero Nov 5, 2020
ad2022c
Change if order.
jjotero Nov 5, 2020
f9fb896
Change function signature to purge test parameters.
jjotero Nov 6, 2020
77a8202
Merge branch 'master' into feature/extensible-parameterized-test
jjotero Nov 23, 2020
f3c6b78
Fix PEP8.
jjotero Nov 23, 2020
30f260e
Drop rfm_ prefix from the test attributes.
jjotero Nov 30, 2020
528eae4
Address initial PR comments.
jjotero Dec 2, 2020
01f9bb4
Bugfix RegressionTest directives.
jjotero Dec 4, 2020
ae91526
Bugfix the parameter directive.
jjotero Dec 4, 2020
26f2ee0
Fix PEP8 style.
jjotero Dec 4, 2020
c23a549
Rename directives and cleanup.
jjotero Dec 4, 2020
e5bea30
Fix PEP8 complaints.
jjotero Dec 4, 2020
351274d
Add prepare parameter space method.
jjotero Dec 7, 2020
6cdf939
Merge branch 'master' into feature/extensible-parameterized-test
jjotero Dec 7, 2020
55703c4
Simplify the namespace clashing check.
jjotero Dec 7, 2020
98d65c8
Fix PEP8 issues.
jjotero Dec 7, 2020
702d855
Remove unused itertools import.
jjotero Dec 7, 2020
63c2927
Simplify simple_test decorator.
jjotero Dec 9, 2020
5909d94
Move parameter functionality to parameters.py.
jjotero Dec 15, 2020
2f29008
Address more PR comments.
jjotero Dec 15, 2020
2e876e4
Improve docstring.
jjotero Dec 15, 2020
26417ef
Move the assignment of _rfm_params.
jjotero Dec 15, 2020
9afd2a2
Improve simple_test implementation.
jjotero Dec 16, 2020
abc97de
Improve pipeline's docstring.
jjotero Dec 16, 2020
dc80f4f
Simplify is_abstract method.
jjotero Dec 16, 2020
98b81d7
Add unit tests
jjotero Dec 16, 2020
871f4b8
Add code readability improvements.
jjotero Dec 17, 2020
e6b2377
Address PR comments
jjotero Dec 18, 2020
3749fda
Bugfix unit tests
jjotero Dec 18, 2020
0c07a23
Fix PEP8 issues.
jjotero Dec 18, 2020
7e6c652
Create ParamSpace class
jjotero Dec 18, 2020
9c31fe5
Improve parameter space consumption
jjotero Dec 21, 2020
2e19b0b
Address PR comments
jjotero Dec 22, 2020
4df1a82
Merge branch 'master' into feature/extensible-parameterized-test
Dec 22, 2020
a42498f
Test decorated parameterized test
jjotero Dec 22, 2020
f703b40
Merge branch 'feature/extensible-parameterized-test' of github.com:jj…
jjotero Dec 22, 2020
ed2e34e
Make both parameterization approaches incompatible
jjotero Jan 8, 2021
8d31eda
Add unit test
jjotero Jan 8, 2021
24c4d8c
Address PR comments
jjotero Jan 8, 2021
2978816
Public documentation for directives (first draft)
Jan 18, 2021
fbbb5b9
Update param docs
jjotero Jan 19, 2021
f17fea9
Address PR comments
jjotero Jan 20, 2021
de5f359
Merge branch 'master' into feature/extensible-parameterized-test
Jan 20, 2021
76bb9ac
Merge branch 'master' into feature/extensible-parameterized-test
Jan 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,6 @@ ENV/

# TextMATE files
._*

# Vim temp files
*.swp
53 changes: 53 additions & 0 deletions docs/regression_test_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,59 @@ Pipeline Hooks
.. autodecorator:: reframe.core.decorators.require_deps


.. _directives:

Directives
----------

Directives are functions that can be called directly in the body of a ReFrame regression test class.
These functions exert control over the test creation, and they allow adding and/or modifying certain attributes of the regression test.
For example, a test can be parameterized using the :func:`parameter` directive as follows:

.. code:: python

class MyTest(rfm.RegressionTest):
parameter('variant', 'A', 'B')

def __init__(self):
if self.variant == 'A':
do_this()
else:
do_other()

One of the most powerful features about using directives is that they store their input information at the class level.
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.
For instance, continuing with the example above, one could override the :func:`__init__` method in the :class:`MyTest` regression test as follows:

.. code:: python

class MyModifiedTest(MyTest):

def __init__(self):
if self.variant == 'A':
override_this()
else:
override_other()


.. py:function:: reframe.core.pipeline.RegressionTest.parameter(name, *values, inherit_params=False, filter_params=None)

Inserts or modifies a regression test parameter.
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``.
Instead, if no parameter with a matching name exists in any of the parent parameter spaces, a new regression test parameter is created.

:param name: the parameter name.
:param values: the parameter values.
If no values are passed when creating a new parameter, the parameter is considered as *declared* but not *defined* (i.e. an abstract parameter).
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.
:param inherit_params: If :obj:`False`, no parameter values that may have been defined in any of the parent parameter spaces will be inherited.
:param filter_params: Function to filter/modify the inherited parameter values that may have been provided in any of the parent parameter spaces.
This function must accept a single argument, which will be passed as an iterable containing the inherited parameter values.
This only has an effect if used with ``inherit_params=True``.




Environments and Systems
------------------------

Expand Down
28 changes: 23 additions & 5 deletions reframe/core/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,20 @@


def _register_test(cls, args=None):
'''Register the test.

Register the test with _rfm_use_params=True. This additional argument flags
this case to consume the parameter space. Otherwise, the regression test
parameters would simply be initialized to None.
'''
def _instantiate(cls, args):
if isinstance(args, collections.abc.Sequence):
return cls(*args)
return cls(*args, _rfm_use_params=True)
elif isinstance(args, collections.abc.Mapping):
args['_rfm_use_params'] = True
return cls(**args)
elif args is None:
return cls()
return cls(_rfm_use_params=True)

def _instantiate_all():
ret = []
Expand Down Expand Up @@ -72,19 +79,25 @@ def _validate_test(cls):
raise ReframeSyntaxError('the decorated class must be a '
'subclass of RegressionTest')

if (cls.is_abstract()):
raise ValueError(f'decorated test ({cls.__qualname__!r}) is an'
f' abstract test')


def simple_test(cls):
'''Class decorator for registering parameterless tests with ReFrame.
'''Class decorator for registering tests with ReFrame.

The decorated class must derive from
:class:`reframe.core.pipeline.RegressionTest`. This decorator is also
available directly under the :mod:`reframe` module.

.. versionadded:: 2.13
'''

_validate_test(cls)
_register_test(cls)

for _ in cls.param_space:
_register_test(cls)

return cls


Expand All @@ -106,6 +119,11 @@ def parameterized_test(*inst):
'''
def _do_register(cls):
_validate_test(cls)
if not cls.param_space.is_empty():
raise ValueError(
f'{cls.__qualname__!r} is already a parameterized test'
)

for args in inst:
_register_test(cls, args)

Expand Down
58 changes: 58 additions & 0 deletions reframe/core/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,32 @@
# Meta-class for creating regression tests.
#


from reframe.core.exceptions import ReframeSyntaxError
import reframe.core.parameters as parameters


class RegressionTestMeta(type):
@classmethod
def __prepare__(cls, name, bases, **kwargs):
namespace = super().__prepare__(name, bases, **kwargs)

# Regression test parameter space defined at the class level
local_param_space = parameters.LocalParamSpace()
namespace['_rfm_local_param_space'] = local_param_space

# Directive to add a regression test parameter directly in the
# class body as: `parameter('P0', 0,1,2,3)`.
namespace['parameter'] = local_param_space.add_param

return namespace

def __init__(cls, name, bases, namespace, **kwargs):
super().__init__(name, bases, namespace, **kwargs)

# Build the regression test parameter space
cls._rfm_param_space = parameters.ParamSpace(cls)

# Set up the hooks for the pipeline stages based on the _rfm_attach
# attribute; all dependencies will be resolved first in the post-setup
# phase if not assigned elsewhere
Expand Down Expand Up @@ -59,3 +78,42 @@ def __init__(cls, name, bases, namespace, **kwargs):
f"'{b.__qualname__}.{v.__name__}'; "
f"you should use the pipeline hooks instead")
raise ReframeSyntaxError(msg)

def __call__(cls, *args, **kwargs):
'''Intercept reframe-specific constructor arguments.

When registering a regression test using any supported decorator,
this decorator may pass additional arguments to the class constructor
to perform specific reframe-internal actions. This gives extra control
over the class instantiation process, allowing reframe to instantiate
the regression test class differently if this class was registered or
not (e.g. when deep-copying a regression test object). These interal
arguments must be intercepted before the object initialization, since
these would otherwise affect the __init__ method's signature, and these
internal mechanisms must be fully transparent to the user.
'''
obj = cls.__new__(cls, *args, **kwargs)

# Intercept constructor arguments
kwargs.pop('_rfm_use_params', None)

obj.__init__(*args, **kwargs)
return obj

@property
def param_space(cls):
# Make the parameter space available as read-only
return cls._rfm_param_space

def is_abstract(cls):
'''Check if the test is an abstract test.

If the parameter space has undefined parameters, the test is considered
an abstract test. If that is the case, the length of the parameter
space is just 0.

:return: bool indicating wheteher the test is abstract or not

:meta private:
'''
return len(cls.param_space) == 0
Loading