Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Extend the syntax of valid_systems and valid_prog_environs #2479

Merged
merged 20 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 30 additions & 3 deletions docs/config_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -428,16 +428,29 @@ System Partition Configuration
.. versionadded:: 3.5.0


.. js:attribute:: .systems[].partitions[].features

:required: No
:default: ``[]``

User defined features of the partition.
These are accessible through the :attr:`~reframe.core.systems.SystemPartition.features` attribute of the :attr:`~reframe.core.pipeline.RegressionTest.current_partition` and can also be selected through the extended syntax of :attr:`~reframe.core.pipeline.RegressionTest.valid_systems`.
The values of this list must be alphanumeric strings starting with a non-digit character and may also contain a ``-``.

.. versionadded:: 3.11.0


.. js:attribute:: .systems[].partitions[].extras

:required: No
:default: ``{}``

User defined attributes of the partition. This will be accessible through the :attr:`~reframe.core.systems.SystemPartition.extras` attribute of the :attr:`~reframe.core.pipeline.RegressionTest.current_partition`.
User defined attributes of the partition.
These are accessible through the :attr:`~reframe.core.systems.SystemPartition.extras` attribute of the :attr:`~reframe.core.pipeline.RegressionTest.current_partition` and can also be selected through the extended syntax of :attr:`~reframe.core.pipeline.RegressionTest.valid_systems`.
The attributes of this object must be alphanumeric strings starting with a non-digit character and their values can be of any type.

.. versionadded:: 3.5.0


.. _container-platform-configuration:


Expand Down Expand Up @@ -598,12 +611,26 @@ They are associated with `system partitions <#system-partition-configuration>`__
Variables are set after the environment modules are loaded.


.. js:attribute:: .environments[].features

:required: No
:default: ``[]``

User defined features of the environment.
These are accessible through the :attr:`~reframe.core.environments.Environment.features` attribute of the :attr:`~reframe.core.pipeline.RegressionTest.current_environ` and can also be selected through the extended syntax of :attr:`~reframe.core.pipeline.RegressionTest.valid_prog_environs`.
The values of this list must be alphanumeric strings starting with a non-digit character and may also contain a ``-``.

.. versionadded:: 3.11.0


.. js:attribute:: .environments[].extras

:required: No
:default: ``{}``

User defined attributes of the environment. This will be accessible through the :attr:`~reframe.core.environments.Environment.extras` attribute of the :attr:`~reframe.core.pipeline.RegressionTest.current_environ`.
User defined attributes of the environment.
These are accessible through the :attr:`~reframe.core.environments.Environment.extras` attribute of the :attr:`~reframe.core.pipeline.RegressionTest.current_environ` and can also be selected through the extended syntax of :attr:`~reframe.core.pipeline.RegressionTest.valid_prog_environs`.
The attributes of this object must be alphanumeric strings starting with a non-digit character and their values can be of any type.

.. versionadded:: 3.9.1

Expand Down
10 changes: 8 additions & 2 deletions reframe/core/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ def skip(self, test):
'''Add a test to the skip set.'''
self._skip_tests.add(test)

def instantiate_all(self):
'''Instantiate all the registered tests.'''
def instantiate_all(self, reset_sysenv=0):
'''Instantiate all the registered tests.

:param reset_sysenv: Reset valid_systems and valid_prog_environs after
instantiating the tests. Bit 0 resets the valid_systems, bit 1
resets the valid_prog_environs.
'''

# We first instantiate the leaf tests and then walk up their
# dependencies to instantiate all the fixtures. Fixtures can only
Expand All @@ -79,6 +84,7 @@ def instantiate_all(self):

for args, kwargs in variants:
try:
kwargs['reset_sysenv'] = reset_sysenv
leaf_tests.append(test(*args, **kwargs))
except SkipTestError as e:
getlogger().warning(
Expand Down
22 changes: 18 additions & 4 deletions reframe/core/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ class Environment(jsonext.JSONSerializable):
Users may not create :class:`Environment` objects directly.
'''

def __init__(self, name, modules=None, variables=None, extras=None):
def __init__(self, name, modules=None, variables=None,
extras=None, features=None):
modules = modules or []
variables = variables or []
self._name = name
self._modules = normalize_module_list(modules)
self._module_names = [m['name'] for m in self._modules]
self._variables = collections.OrderedDict(variables)
self._extras = extras or {}
self._features = features or []

@property
def name(self):
Expand Down Expand Up @@ -92,7 +94,7 @@ def variables(self):

@property
def extras(self):
'''User defined properties defined in the configuration.
'''User defined properties specified in the configuration.

.. versionadded:: 3.9.1

Expand All @@ -101,6 +103,16 @@ def extras(self):

return self._extras

@property
def features(self):
'''Used defined features specified in the configuration.

.. versionadded:: 3.11.0

:type: :class:`List[str]`
'''
return self._features

def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
Expand All @@ -116,7 +128,8 @@ def __repr__(self):
return (f'{type(self).__name__}('
f'name={self._name!r}, '
f'modules={self._modules!r}, '
f'variables={list(self._variables.items())!r})')
f'variables={list(self._variables.items())!r}, '
f'extras={self._extras!r}, features={self._features!r})')


class _EnvironmentSnapshot(Environment):
Expand Down Expand Up @@ -174,6 +187,7 @@ def __init__(self,
modules=None,
variables=None,
extras=None,
features=None,
cc='cc',
cxx='CC',
ftn='ftn',
Expand All @@ -184,7 +198,7 @@ def __init__(self,
fflags=None,
ldflags=None,
**kwargs):
super().__init__(name, modules, variables, extras)
super().__init__(name, modules, variables, extras, features)
self._cc = cc
self._cxx = cxx
self._ftn = ftn
Expand Down
106 changes: 40 additions & 66 deletions reframe/core/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def __init__(self):
# Store the system name for name-mangling purposes
self._sys_name = runtime.runtime().system.name

def add(self, fixture, variant_num, parent_name, partitions, prog_envs):
def add(self, fixture, variant_num, parent_test):
'''Register a fixture.

This method mangles the fixture name, ensuring that different fixture
Expand Down Expand Up @@ -161,11 +161,7 @@ def add(self, fixture, variant_num, parent_name, partitions, prog_envs):

:param fixture: An instance of :class:`TestFixture`.
:param variant_num: The variant index for the given ``fixture``.
:param parent_name: The full name of the parent test. This argument
is used to mangle the fixture name for those with a ``'test'``
scope, such that the fixture is private to its parent test.
:param partitions: The system partitions supported by the parent test.
:param prog_envs: The valid programming environments from the parent.
:param parent_test: The parent test.
'''

cls = fixture.cls
Expand All @@ -187,61 +183,76 @@ def add(self, fixture, variant_num, parent_name, partitions, prog_envs):
fname += vname

# Select only the valid partitions
valid_partitions = self._filter_valid_partitions(partitions)
try:
valid_sysenv = runtime.valid_sysenv_comb(
parent_test.valid_systems,
parent_test.valid_prog_environs
)
except AttributeError as e:
msg = e.args[0] + f' in test {parent_test.display_name!r}'
raise ReframeSyntaxError(msg) from None

# Return if not any valid partition
if not valid_partitions:
# Return if there are no valid system/environment combinations
if not valid_sysenv:
return []

# Register the fixture
if scope == 'session':
# The name is mangled with the system name
# Select a valid environment supported by a partition
for part in valid_partitions:
valid_envs = self._filter_valid_environs(part, prog_envs)
if valid_envs:
# Pick the first valid system/environment combination
pname, ename = None, None
for part, environs in valid_sysenv.items():
pname = part.fullname
for env in environs:
ename = env.name
break
else:

if ename is None:
# No valid environments found
return []

# Register the fixture
fixt_data = FixtureData(variant_num, [valid_envs[0]], [part],
fixt_data = FixtureData(variant_num, [ename], [pname],
variables, scope, self._sys_name)
name = f'{cls.__name__}_{fixt_data.mashup()}'
self._registry[cls][name] = fixt_data
reg_names.append(name)
elif scope == 'partition':
for part in valid_partitions:
for part, environs in valid_sysenv.items():
# The mangled name contains the full partition name

# Select an environment supported by the partition
valid_envs = self._filter_valid_environs(part, prog_envs)
if not valid_envs:
pname = part.fullname
try:
ename = environs[0].name
except IndexError:
continue

# Register the fixture
fixt_data = FixtureData(variant_num, [valid_envs[0]], [part],
variables, scope, part)
fixt_data = FixtureData(variant_num, [ename], [pname],
variables, scope, pname)
name = f'{cls.__name__}_{fixt_data.mashup()}'
self._registry[cls][name] = fixt_data
reg_names.append(name)
elif scope == 'environment':
for part in valid_partitions:
for env in self._filter_valid_environs(part, prog_envs):
for part, environs in valid_sysenv.items():
for env in environs:
# The mangled name contains the full part and env names
# Register the fixture
fixt_data = FixtureData(variant_num, [env], [part],
variables, scope, f'{part}+{env}')
pname, ename = part.fullname, env.name
fixt_data = FixtureData(variant_num, [ename], [pname],
variables, scope,
f'{pname}+{ename}')
name = f'{cls.__name__}_{fixt_data.mashup()}'
self._registry[cls][name] = fixt_data
reg_names.append(name)
elif scope == 'test':
# The mangled name contains the parent test name.

# Register the fixture
fixt_data = FixtureData(variant_num, list(prog_envs),
list(valid_partitions),
variables, scope, parent_name)
fixt_data = FixtureData(variant_num,
list(parent_test.valid_prog_environs),
list(parent_test.valid_systems),
variables, scope, parent_test.unique_name)
name = f'{cls.__name__}_{fixt_data.mashup()}'
self._registry[cls][name] = fixt_data
reg_names.append(name)
Expand Down Expand Up @@ -986,18 +997,13 @@ def inject(self, obj, cls=None, fixtures_index=None):
# Create the fixture registry
obj._rfm_fixture_registry = FixtureRegistry()

# Prepare the partitions and prog_envs
part, prog_envs = self._expand_partitions_envs(obj)

# Register the fixtures
for name, fixture in self.fixtures.items():
dep_names = []
for variant in fixture_variants[name]:
# Register all the variants and track the fixture names
dep_names += obj._rfm_fixture_registry.add(fixture,
variant,
obj.unique_name,
part, prog_envs)
variant, obj)

# Add dependencies
if fixture.scope == 'session':
Expand All @@ -1011,38 +1017,6 @@ def inject(self, obj, cls=None, fixtures_index=None):
for dep_name in dep_names:
obj.depends_on(dep_name, dep_kind)

def _expand_partitions_envs(self, obj):
'''Process the partitions and programming environs of the parent.'''

try:
part = tuple(obj.valid_systems)
except AttributeError:
raise ReframeSyntaxError(
f"'valid_systems' is undefined in test {obj.unique_name!r}"
)
else:
rt = runtime.runtime()
if '*' in part or rt.system.name in part:
part = tuple(p.fullname for p in rt.system.partitions)

try:
prog_envs = tuple(obj.valid_prog_environs)
except AttributeError:
raise ReframeSyntaxError(
f"'valid_prog_environs' is undefined "
f"in test {obj.unique_name!r}"
)
else:
if '*' in prog_envs:
all_pes = set()
for p in runtime.runtime().system.partitions:
for e in p.environs:
all_pes.add(e.name)

prog_envs = tuple(all_pes)

return part, prog_envs

def __iter__(self):
'''Walk through all index combinations for all fixtures.'''
yield from self.__variant_combinations
Expand Down
6 changes: 6 additions & 0 deletions reframe/core/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ def __call__(cls, *args, **kwargs):
# Intercept the requested variant number (if any) and map it to the
# respective points in the parameter and fixture spaces.
variant_num = kwargs.pop('variant_num', None)
reset_sysenv = kwargs.pop('reset_sysenv', 0)
param_index, fixt_index = cls._map_variant_num(variant_num)
fixt_name = kwargs.pop('fixt_name', None)
fixt_data = kwargs.pop('fixt_data', None)
Expand Down Expand Up @@ -437,6 +438,11 @@ def __call__(cls, *args, **kwargs):
setattr(obj, fname, fixtures.FixtureProxy(finfo))

obj.__init__(*args, **kwargs)
if reset_sysenv & 1:
obj.valid_systems = ['*']

if reset_sysenv & 2:
obj.valid_prog_environs = ['*']

# Register the fixtures
# Fixtures must be injected after the object's initialisation because
Expand Down
Loading