Skip to content

Commit 4cb9c4d

Browse files
author
Vasileios Karakasis
authored
Merge branch 'master' into doc/fixtures-tutorial
2 parents a2e18c1 + 6a082ec commit 4cb9c4d

File tree

8 files changed

+80
-137
lines changed

8 files changed

+80
-137
lines changed

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,15 @@
1414

1515
# ReFrame in a Nutshell
1616

17-
ReFrame is a framework for writing regression tests for HPC systems.
18-
The goal of this framework is to abstract away the complexity of the interactions with the system, separating the logic of a regression test from the low-level details, which pertain to the system configuration and setup.
19-
This allows users to write easily portable regression tests, focusing only on the functionality.
17+
ReFrame is a powerful framework for writing system regression tests and benchmarks, specifically targeted to HPC systems.
18+
The goal of the framework is to abstract away the complexity of the interactions with the system, separating the logic of a test from the low-level details, which pertain to the system configuration and setup.
19+
This allows users to write portable tests in a declarative way that describes only the test's functionality.
2020

21-
Regression tests in ReFrame are simple Python classes that specify the basic parameters of the test.
22-
The framework will load the test and will send it down a well-defined pipeline that will take care of its execution.
21+
Tests in ReFrame are simple Python classes that specify the basic variables and parameters of the test.
22+
ReFrame offers an intuitive and very powerful syntax that allows users to create test libraries, test factories, as well as complete test workflows using other tests as fixtures.
23+
ReFrame will load the tests and send them down a well-defined pipeline that will execute them in parallel.
2324
The stages of this pipeline take care of all the system interaction details, such as programming environment switching, compilation, job submission, job status query, sanity checking and performance assessment.
2425

25-
Writing system regression tests in a high-level modern programming language, like Python, poses a great advantage in organizing and maintaining the tests.
26-
Users can create their own test hierarchies, create test factories for generating multiple tests at the same time and also customize them in a simple and expressive way.
27-
2826
Please visit the project's documentation [page](https://reframe-hpc.readthedocs.io/) for all the details!
2927

3028

docs/index.rst

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,19 @@
22
Welcome to ReFrame
33
==================
44

5-
ReFrame is a high-level framework for writing regression tests for HPC systems.
6-
The goal of the framework is to abstract away the complexity of the interactions with the system, separating the logic of a regression test from the low-level details, which pertain to the system configuration and setup.
7-
This allows users to write easily portable regression tests, focusing only on the functionality.
5+
ReFrame is a powerful framework for writing system regression tests and benchmarks, specifically targeted to HPC systems.
6+
The goal of the framework is to abstract away the complexity of the interactions with the system, separating the logic of a test from the low-level details, which pertain to the system configuration and setup.
7+
This allows users to write portable tests in a declarative way that describes only the test's functionality.
88

9-
Regression tests in ReFrame are simple Python classes that specify the basic parameters of the test.
10-
The framework will load the test and will send it down a well-defined pipeline that will take care of its execution.
9+
Tests in ReFrame are simple Python classes that specify the basic variables and parameters of the test.
10+
ReFrame offers an intuitive and very powerful syntax that allows users to create test libraries, test factories, as well as complete test workflows using other tests as fixtures.
11+
ReFrame will load the tests and send them down a well-defined pipeline that will execute them in parallel.
1112
The stages of this pipeline take care of all the system interaction details, such as programming environment switching, compilation, job submission, job status query, sanity checking and performance assessment.
1213

1314
ReFrame also offers a high-level and flexible abstraction for writing sanity and performance checks for your regression tests, without having to care about the details of parsing output files, searching for patterns and testing against reference values for different systems.
1415

15-
Writing system regression tests in a high-level modern programming language, like Python, poses a great advantage in organizing and maintaining the tests.
16-
Users can create their own test hierarchies or test factories for generating multiple tests at the same time and they can also customize them in a simple and expressive way.
17-
1816
Finally, ReFrame offers a powerful and efficient runtime for running and managing the execution of tests, as well as integration with common logging facilities, where ReFrame can send live data from currently running performance tests.
1917

20-
2118
Use Cases
2219
=========
2320

reframe/core/fixtures.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -527,16 +527,10 @@ class FixtureSpace(namespaces.Namespace):
527527
into the target instance under the ``_rfm_fixture_registry`` attribute.
528528
'''
529529

530-
@property
531-
def local_namespace_name(self):
532-
return '_rfm_local_fixture_space'
533-
534-
@property
535-
def namespace_name(self):
536-
return '_rfm_fixture_space'
537-
538-
def __init__(self, target_cls=None, target_namespace=None):
539-
super().__init__(target_cls, target_namespace)
530+
def __init__(self, target_cls=None, illegal_names=None):
531+
super().__init__(target_cls, illegal_names,
532+
ns_name='_rfm_fixture_space',
533+
ns_local_name='_rfm_local_fixture_space')
540534

541535
# Store all fixture variant combinations to allow random access.
542536
self.__variant_combinations = tuple(
@@ -563,7 +557,7 @@ def join(self, other, cls):
563557

564558
def extend(self, cls):
565559
'''Extend the inherited fixture space with the local fixture space.'''
566-
local_fixture_space = getattr(cls, self.local_namespace_name)
560+
local_fixture_space = getattr(cls, self.local_namespace_name, False)
567561
while local_fixture_space:
568562
name, fixture = local_fixture_space.popitem()
569563
self.fixtures[name] = fixture

reframe/core/meta.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -393,18 +393,18 @@ def __init__(cls, name, bases, namespace, **kwargs):
393393
for base in (b for b in bases if hasattr(b, '_rfm_dir')):
394394
cls._rfm_dir.update(base._rfm_dir)
395395

396-
used_attribute_names = set(cls._rfm_dir)
397-
398-
# Build the var space and extend the target namespace
399-
variables.VarSpace(cls, used_attribute_names)
400-
used_attribute_names.update(cls._rfm_var_space.vars)
401-
402-
# Build the parameter space
403-
parameters.ParamSpace(cls, used_attribute_names)
404-
used_attribute_names.update(cls._rfm_param_space.params)
396+
used_attribute_names = set(cls._rfm_dir).union(
397+
{h.__name__ for h in cls._rfm_hook_registry}
398+
)
405399

406-
# Build the fixture space
407-
fixtures.FixtureSpace(cls, used_attribute_names)
400+
# Build the different global class namespaces
401+
namespace_types = (variables.VarSpace,
402+
parameters.ParamSpace,
403+
fixtures.FixtureSpace)
404+
for ns_type in namespace_types:
405+
ns = ns_type(cls, used_attribute_names)
406+
setattr(cls, ns.namespace_name, ns)
407+
used_attribute_names.update(ns.data())
408408

409409
# Update used names set with the local __dict__
410410
cls._rfm_dir.update(cls.__dict__)

reframe/core/namespaces.py

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,23 @@ def _raise_namespace_clash(self, name):
6868
def clear(self):
6969
self._namespace = {}
7070

71+
def data(self):
72+
'''Give access to the underlying namespace'''
73+
return self._namespace
74+
7175

7276
class Namespace(LocalNamespace, metaclass=abc.ABCMeta):
7377
'''Namespace of a regression test.
7478
75-
The final namespace may be built by inheriting namespaces from
76-
the base classes, and extended with the information stored in the local
77-
namespace of the target class. In this context, the target class is
78-
simply the regression test class where the namespace is to be built.
79-
80-
To allow for this inheritance and extension of the namespace, this
81-
class must define the names under which the local and final namespaces
82-
are inserted in the target classes.
79+
The final namespace may be built by inheriting namespaces from the base
80+
classes, and extending this one with the information stored in the local
81+
namespace of the target class. In this context, the target class is simply
82+
the regression test class where the namespace is to be built.
8383
84-
If a target class is provided, the constructor will attach the Namespace
85-
instance into the target class with the class attribute name as defined
86-
in ``namespace_name``.
84+
If a target class is provided, the constructor will build a Namespace
85+
instance by inheriting the namespaces found in the base classes, and
86+
extending this with the information from the local namespace of the
87+
target class.
8788
8889
Eventually, the items from a Namespace are injected as attributes of
8990
the target class instance by the :func:`inject` method, which must be
@@ -95,32 +96,15 @@ class may use more that one Namespace, which raises the need for name
9596
target class. Then, after the Namespace is built, if ``illegal_names`` is
9697
provided, a sanity check is performed, ensuring that no name clashing
9798
will occur during the target class instantiation process.
98-
'''
9999
100-
@property
101-
@abc.abstractmethod
102-
def local_namespace_name(self):
103-
'''Name of the local namespace in the target class.
104-
105-
Name under which the local namespace is stored in the
106-
:class:`reframe.core.pipeline.RegressionTest` class.
107-
'''
108-
109-
@property
110-
@abc.abstractmethod
111-
def namespace_name(self):
112-
'''Name of the namespace in the target class.
113-
114-
Name under which the namespace is stored in the
115-
:class:`reframe.core.pipeline.RegressionTest` class.
116-
'''
100+
'''
117101

118-
def __init__(self, target_cls=None, illegal_names=None):
102+
def __init__(self, target_cls=None, illegal_names=None,
103+
*, ns_name, ns_local_name):
119104
super().__init__()
105+
self._ns_name = ns_name
106+
self._ns_local_name = ns_local_name
120107
if target_cls:
121-
# Assert the Namespace can be built for the target_cls
122-
self.assert_target_cls(target_cls)
123-
124108
# Inherit Namespaces from the base clases
125109
self.inherit(target_cls)
126110

@@ -130,23 +114,21 @@ def __init__(self, target_cls=None, illegal_names=None):
130114
# Sanity checkings on the resulting Namespace
131115
self.sanity(target_cls, illegal_names)
132116

133-
# Attach the Namespace to the target class
134-
setattr(target_cls, self.namespace_name, self)
135-
136-
def assert_target_cls(self, cls):
137-
'''Assert the target class has a valid local namespace.'''
117+
@property
118+
def namespace_name(self):
119+
return self._ns_name
138120

139-
assert hasattr(cls, self.local_namespace_name)
140-
assert isinstance(getattr(cls, self.local_namespace_name),
141-
LocalNamespace)
121+
@property
122+
def local_namespace_name(self):
123+
return self._ns_local_name
142124

143125
def inherit(self, cls):
144126
'''Inherit the Namespaces from the bases.'''
145127

146-
for base in filter(lambda x: hasattr(x, self.namespace_name),
147-
cls.__bases__):
148-
assert isinstance(getattr(base, self.namespace_name), type(self))
149-
self.join(getattr(base, self.namespace_name), cls)
128+
for base in cls.__bases__:
129+
other = getattr(base, self.namespace_name, None)
130+
if isinstance(other, type(self)):
131+
self.join(other, cls)
150132

151133
@abc.abstractmethod
152134
def join(self, other, cls):
@@ -156,7 +138,7 @@ def join(self, other, cls):
156138
def extend(self, cls):
157139
'''Extend the namespace with the local namespace.'''
158140

159-
def sanity(self, cls, illegal_names=None):
141+
def sanity(self, cls, illegal_names):
160142
'''Sanity checks post-creation of the namespace.
161143
162144
By default, we make illegal to have any item in the namespace

reframe/core/parameters.py

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -61,43 +61,24 @@ def filter_params(x):
6161

6262

6363
class ParamSpace(namespaces.Namespace):
64-
''' Regression test parameter space
65-
66-
Host class for the parameter space of a regresion test. The parameter
67-
space is stored as a dictionary (self.params), where the keys are the
68-
parameter names and the values are tuples with all the available values
69-
for each parameter. The __init__ method in this class takes an optional
70-
argument (target_class), which is the regression test class where the
71-
parameter space is to e inserted as the ``_rfm_param_space`` class
72-
attribute. If no target class is provided, the parameter space is
73-
initialized as empty. After the parameter space is set, a parameter space
74-
iterator is created under self.__unique_iter, which acts as an internal
75-
control variable that tracks the usage of this parameter space. This
76-
iterator walks through all possible parameter combinations and cannot be
77-
restored after reaching exhaustion. The length of this iterator matches
78-
the value returned by the member function __len__.
79-
80-
:param target_cls: the class where the full parameter space is to be built.
81-
:param target_namespace: a reference namespace to ensure that no name
82-
clashes occur (see :class:`reframe.core.namespaces.Namespace`).
83-
84-
.. note::
85-
The __init__ method is aware of the implementation details of the
86-
regression test metaclass. This is required to retrieve the parameter
87-
spaces from the base classes, and also the local parameter space from
88-
the target class.
64+
'''Regression test parameter space
65+
66+
The parameter space is stored as a dictionary (self.params), where the
67+
keys are the parameter names and the values are tuples with all the
68+
available values for each parameter. The __init__ method in this class
69+
takes the optional argument ``target_cls``, which is the regression test
70+
class that the parameter space is being built for. If no target class is
71+
provided, the parameter space is initialized as empty.
72+
73+
All the parameter combinations are stored under ``__param_combinations``.
74+
This enables random-access to any of the available parameter combinations
75+
through the ``__getitem__`` method.
8976
'''
9077

91-
@property
92-
def local_namespace_name(self):
93-
return '_rfm_local_param_space'
94-
95-
@property
96-
def namespace_name(self):
97-
return '_rfm_param_space'
98-
99-
def __init__(self, target_cls=None, target_namespace=None):
100-
super().__init__(target_cls, target_namespace)
78+
def __init__(self, target_cls=None, illegal_names=None):
79+
super().__init__(target_cls, illegal_names,
80+
ns_name='_rfm_param_space',
81+
ns_local_name='_rfm_local_param_space')
10182

10283
# Store all param combinations to allow random access.
10384
self.__param_combinations = tuple(
@@ -142,7 +123,7 @@ def join(self, other, cls):
142123
def extend(self, cls):
143124
'''Extend the parameter space with the local parameter space.'''
144125

145-
local_param_space = getattr(cls, self.local_namespace_name)
126+
local_param_space = getattr(cls, self.local_namespace_name, dict())
146127
for name, p in local_param_space.items():
147128
try:
148129
filt_vals = p.filter_params(self.params.get(name, ()))

reframe/core/variables.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -425,30 +425,21 @@ def __ceil__(self):
425425
class VarSpace(namespaces.Namespace):
426426
'''Variable space of a regression test.
427427
428-
Store the variables of a regression test. This variable space is stored
429-
in the regression test class under the class attribute ``_rfm_var_space``.
430428
A target class can be provided to the
431429
:func:`__init__` method, which is the regression test where the
432430
VarSpace is to be built. During this call to
433431
:func:`__init__`, the VarSpace inherits all the VarSpace from the base
434432
classes of the target class. After this, the VarSpace is extended with
435-
the information from the local variable space, which is stored under the
436-
target class' attribute ``_rfm_local_var_space``. If no target class is
433+
the information from the local variable space. If no target class is
437434
provided, the VarSpace is simply initialized as empty.
438435
'''
439436

440-
@property
441-
def local_namespace_name(self):
442-
return '_rfm_local_var_space'
443-
444-
@property
445-
def namespace_name(self):
446-
return '_rfm_var_space'
447-
448437
def __init__(self, target_cls=None, illegal_names=None):
449438
# Set to register the variables already injected in the class
450439
self._injected_vars = set()
451-
super().__init__(target_cls, illegal_names)
440+
super().__init__(target_cls, illegal_names,
441+
ns_name='_rfm_var_space',
442+
ns_local_name='_rfm_local_var_space')
452443

453444
def join(self, other, cls):
454445
'''Join an existing VarSpace into the current one.
@@ -482,7 +473,7 @@ def extend(self, cls):
482473
of these actions on the same var for the same local var space
483474
is disallowed.
484475
'''
485-
local_varspace = getattr(cls, self.local_namespace_name)
476+
local_varspace = getattr(cls, self.local_namespace_name, False)
486477
while local_varspace:
487478
key, var = local_varspace.popitem()
488479
if isinstance(var, TestVar):
@@ -513,7 +504,7 @@ def extend(self, cls):
513504
for key in _assigned_vars:
514505
delattr(cls, key)
515506

516-
def sanity(self, cls, illegal_names=None):
507+
def sanity(self, cls, illegal_names):
517508
'''Sanity checks post-creation of the var namespace.
518509
519510
By default, we make illegal to have any item in the namespace

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = ReFrame-HPC
33
version = attr: reframe.VERSION
44
author = CSCS Swiss National Supercomputing Center
5-
description = ReFrame is a framework for writing regression tests for HPC systems
5+
description = ReFrame is a powerful framework for writing system regression tests and benchmarks, specifically targeted to HPC systems
66
url = https://github.com/eth-cscs/reframe
77
license = BSD 3-Clause
88
long_description = file: README.md

0 commit comments

Comments
 (0)