Skip to content

Commit ff2dddf

Browse files
authored
Merge branch 'master' into bugfix/reruns-exitcode
2 parents d2de1fd + ada1dfc commit ff2dddf

File tree

15 files changed

+112
-37
lines changed

15 files changed

+112
-37
lines changed

docs/manpage.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1043,7 +1043,7 @@ The way the tests are generated and how they interact with the test filtering op
10431043

10441044
Parameterize a test on an existing variable.
10451045

1046-
This option will create a new test with a parameter named ``$VAR`` with the values given in the comma-separated list ``VAL0,VAL1,...``.
1046+
The test will behave as if the variable ``VAR`` was a parameter taking the values ``VAL0,VAL1,...``.
10471047
The values will be converted based on the type of the target variable ``VAR``.
10481048
The ``TEST.`` prefix will only parameterize the variable ``VAR`` of test ``TEST``.
10491049

reframe/core/pipeline.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1646,7 +1646,9 @@ def _resolve_fixtures(self):
16461646
# registered under the same fixture class. So the loop below must
16471647
# also inspect the fixture data the instance was registered with.
16481648
for fixt_name, fixt_data in registry[f.cls].items():
1649-
if f.scope != fixt_data.scope:
1649+
if fixt_data.variables != f.variables:
1650+
continue
1651+
elif f.scope != fixt_data.scope:
16501652
continue
16511653
elif fixt_data.variant_num not in target_variants:
16521654
continue

reframe/core/schedulers/slurm.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ def filternodes(self, job, nodes):
351351
option_parser.add_argument('-w', '--nodelist')
352352
option_parser.add_argument('-C', '--constraint')
353353
option_parser.add_argument('-x', '--exclude')
354+
self.log(f'Filtering by Slurm options: {" ".join(options)}')
354355
parsed_args, _ = option_parser.parse_known_args(options)
355356
reservation = parsed_args.reservation
356357
partitions = parsed_args.partition
@@ -363,7 +364,7 @@ def filternodes(self, job, nodes):
363364
else:
364365
nodes = {node for node in nodes if not node.in_state('RESERVED')}
365366

366-
self.log(f'[F] Filtering nodes by reservation={reservation}: '
367+
self.log(f'Filtering nodes by reservation={reservation}: '
367368
f'available nodes now: {len(nodes)}')
368369

369370
if partitions:
@@ -377,27 +378,27 @@ def filternodes(self, job, nodes):
377378
)
378379
partitions = {default_partition} if default_partition else set()
379380
self.log(
380-
f'[F] No partition specified; using {default_partition!r}'
381+
f'No partition specified; using {default_partition!r}'
381382
)
382383

383384
nodes = {n for n in nodes if n.partitions >= partitions}
384-
self.log(f'[F] Filtering nodes by partition(s) {partitions}: '
385+
self.log(f'Filtering nodes by partition(s) {partitions}: '
385386
f'available nodes now: {len(nodes)}')
386387
if constraints:
387388
nodes = {n for n in nodes if n.satisfies(constraints)}
388-
self.log(f'[F] Filtering nodes by constraint(s) {constraints}: '
389+
self.log(f'Filtering nodes by constraint(s) {constraints}: '
389390
f'available nodes now: {len(nodes)}')
390391

391392
if nodelist:
392393
nodelist = nodelist.strip()
393394
nodes &= self._get_nodes_by_name(nodelist)
394-
self.log(f'[F] Filtering nodes by nodelist: {nodelist}: '
395+
self.log(f'Filtering nodes by nodelist: {nodelist}: '
395396
f'available nodes now: {len(nodes)}')
396397

397398
if exclude_nodes:
398399
exclude_nodes = exclude_nodes.strip()
399400
nodes -= self._get_nodes_by_name(exclude_nodes)
400-
self.log(f'[F] Excluding node(s): {exclude_nodes}: '
401+
self.log(f'Excluding node(s): {exclude_nodes}: '
401402
f'available nodes now: {len(nodes)}')
402403

403404
return nodes
@@ -711,17 +712,29 @@ def is_down(self):
711712
return not self.is_avail()
712713

713714
def satisfies(self, slurm_constraint):
715+
def _replacemany(s, replacements):
716+
for src, dst in replacements:
717+
s = s.replace(src, dst)
718+
719+
return s
720+
714721
# Convert the Slurm constraint to a Python expression and evaluate it,
715722
# but restrict our syntax to accept only AND or OR constraints and
716-
# their combinations
717-
if not re.match(r'^[\w\d\(\)\|\&]*$', slurm_constraint):
723+
# their combinations; to properly treat `-` in constraints we need to
724+
# convert them to valid Python identifiers before evaluating the
725+
# constraint.
726+
if not re.match(r'^[\-\w\d\(\)\|\&]*$', slurm_constraint):
718727
return False
719728

720-
names = {grp[0]
721-
for grp in re.finditer(r'(\w(\w|\d)*)', slurm_constraint)}
722-
expr = slurm_constraint.replace('|', ' or ').replace('&', ' and ')
723-
vars = {n: True for n in self.active_features}
724-
vars.update({n: False for n in names - self.active_features})
729+
names = {
730+
grp[0] for grp in re.finditer(r'[\-\w][\-\w\d]*', slurm_constraint)
731+
}
732+
expr = _replacemany(slurm_constraint,
733+
[('-', '_'), ('|', ' or '), ('&', ' and ')])
734+
vars = {n.replace('-', '_'): True for n in self.active_features}
735+
vars.update({
736+
n.replace('-', '_'): False for n in names - self.active_features
737+
})
725738
try:
726739
return eval(expr, {}, vars)
727740
except BaseException:

reframe/frontend/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1041,7 +1041,8 @@ def restrict_logging():
10411041
with exit_gracefully_on_error('failed to retrieve test case data',
10421042
printer):
10431043
printer.info(jsonext.dumps(reporting.testcase_info(
1044-
options.describe_stored_testcases, namepatt
1044+
options.describe_stored_testcases,
1045+
namepatt, options.filter_expr
10451046
), indent=2))
10461047
sys.exit(0)
10471048

reframe/frontend/loader.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,31 @@ def load_from_file(self, filename, force=False):
193193

194194
try:
195195
dirname = os.path.dirname(filename)
196-
with osext.change_dir(dirname):
197-
with util.temp_sys_path(dirname):
198-
if os.path.exists(os.path.join(dirname, '__init__.py')):
199-
# If the containing directory is a package,
200-
# import it, too.
201-
parent = util.import_module_from_file(dirname).__name__
202-
else:
203-
parent = None
204196

197+
# Load all parent modules of test file
198+
parents = []
199+
while os.path.exists(os.path.join(dirname, '__init__.py')):
200+
parents.append(os.path.join(dirname))
201+
dirname = os.path.split(dirname)[0]
202+
203+
parent_module = None
204+
for pdir in reversed(parents):
205+
with osext.change_dir(pdir):
206+
with util.temp_sys_path(pdir):
207+
package_path = os.path.join(pdir, '__init__.py')
208+
parent_module = util.import_module_from_file(
209+
package_path, parent=parent_module
210+
).__name__
211+
212+
# Now load the actual test file
213+
if not parents:
214+
pdir = dirname
215+
216+
with osext.change_dir(pdir):
217+
with util.temp_sys_path(pdir):
205218
return self.load_from_module(
206-
util.import_module_from_file(filename, force, parent)
219+
util.import_module_from_file(filename, force,
220+
parent_module)
207221
)
208222
except Exception:
209223
exc_info = sys.exc_info()

reframe/frontend/testgenerators.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ def _generate_tests(testcases, gen_fn):
7777
@time_function
7878
def distribute_tests(testcases, node_map):
7979
def _rfm_pin_run_nodes(obj):
80-
nodelist = getattr(obj, '$nid')
80+
nodelist = getattr(obj, '.nid')
8181
if not obj.local:
8282
obj.job.pin_nodes = nodelist
8383

8484
def _rfm_pin_build_nodes(obj):
85-
pin_nodes = getattr(obj, '$nid')
85+
pin_nodes = getattr(obj, '.nid')
8686
if obj.build_job and not obj.local and not obj.build_locally:
8787
obj.build_job.pin_nodes = pin_nodes
8888

@@ -99,9 +99,9 @@ def _rfm_set_valid_systems(obj):
9999
'valid_systems': [partition.fullname],
100100
# We add a partition parameter so as to differentiate the test
101101
# in case another test has the same nodes in another partition
102-
'$part': builtins.parameter([partition.fullname],
102+
'.part': builtins.parameter([partition.fullname],
103103
loggable=False),
104-
'$nid': builtins.parameter(
104+
'.nid': builtins.parameter(
105105
[[n] for n in node_map[partition.fullname]],
106106
fmt=util.nodelist_abbrev, loggable=False
107107
)
@@ -113,7 +113,7 @@ def _rfm_set_valid_systems(obj):
113113
# will not be overwritten by a parent post-init hook
114114
builtins.run_after('init')(_rfm_set_valid_systems),
115115
]
116-
), ['$part', '$nid']
116+
), ['.part', '.nid']
117117

118118
return _generate_tests(testcases, _make_dist_test)
119119

@@ -127,10 +127,10 @@ def _make_repeat_test(testcase):
127127
return make_test(
128128
cls.__name__, (cls,),
129129
{
130-
'$repeat_no': builtins.parameter(range(num_repeats),
130+
'.repeat_no': builtins.parameter(range(num_repeats),
131131
loggable=False)
132132
}
133-
), ['$repeat_no']
133+
), ['.repeat_no']
134134

135135
return _generate_tests(testcases, _make_repeat_test)
136136

@@ -164,7 +164,7 @@ def _make_parameterized_test(testcase):
164164
)
165165
continue
166166

167-
body[f'${var}'] = builtins.parameter(values, loggable=False)
167+
body[f'.{var}'] = builtins.parameter(values, loggable=False)
168168

169169
def _set_vars(self):
170170
for var in body.keys():

reframe/utility/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ def count_digits(n):
866866
'''
867867

868868
num_digits = 1
869-
while n > 10:
869+
while n >= 10:
870870
n /= 10
871871
num_digits += 1
872872

unittests/resources/checks_unlisted/fixtures_complex.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,17 @@ def validate_fixture_resolution(self):
102102
ParamFixture.num_variants
103103
)
104104
])
105+
106+
107+
@rfm.simple_test
108+
class TestC(rfm.RunOnlyRegressionTest):
109+
valid_systems = ['*']
110+
valid_prog_environs = ['*']
111+
executable = 'echo'
112+
f0 = fixture(SimpleFixture, scope='environment', variables={'data': 10})
113+
f1 = fixture(SimpleFixture, scope='environment', variables={'data': 20})
114+
115+
@sanity_function
116+
def validate_vars(self):
117+
return sn.all([sn.assert_eq(self.f0.data, 10),
118+
sn.assert_eq(self.f1.data, 20)])

unittests/resources/checks_unlisted/testlib/nested/__init__.py

Whitespace-only changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2016-2024 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+
import reframe as rfm
7+
import reframe.utility.sanity as sn
8+
from ..utility import dummy_fixture
9+
10+
11+
@rfm.simple_test
12+
class dummy_test(rfm.RunOnlyRegressionTest):
13+
valid_systems = ['*']
14+
valid_prog_environs = ['*']
15+
executable = 'true'
16+
sanity_patterns = sn.assert_true(1)
17+
dummy = fixture(dummy_fixture)

0 commit comments

Comments
 (0)