Skip to content

Commit 07064bb

Browse files
authored
Merge branch 'master' into bugfix/documentation_check_info
2 parents 0a265f6 + 8b7a651 commit 07064bb

File tree

4 files changed

+188
-3
lines changed

4 files changed

+188
-3
lines changed

docs/running.rst

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,3 +629,78 @@ Here is an example output of ReFrame using asynchronous execution policy:
629629
630630
The asynchronous execution policy may provide significant overall performance benefits for run-only regression tests.
631631
For compile-only and normal tests that require a compilation, the execution time will be bound by the total compilation time of the test.
632+
633+
634+
Manipulating modules
635+
--------------------
636+
637+
.. versionadded:: 2.11
638+
639+
ReFrame allows you to change the modules loaded by a regression test on-the-fly without having to edit the regression test file.
640+
This feature is extremely useful when you need to quickly test a newer version of a module, but it also allows you to completely decouple the module names used in your regression tests from the real module names in a system, thus making your test even more portable.
641+
This is achieved by defining *module mappings*.
642+
643+
There are two ways to pass module mappings to ReFrame.
644+
The first is to use the ``--map-module`` command-line option, which accepts a module mapping.
645+
For example, the following line maps the module ``test_module`` to the module ``real_module``:
646+
647+
.. code-block:: none
648+
649+
--map-module='test_module: real_module'
650+
651+
In this case, whenever ReFrame is asked to load ``test_module``, it will load ``real_module``.
652+
Any string without spaces may be accepted in place of ``test_module`` and ``real_module``.
653+
You can also define multiple module mappings at once by repeating the ``--map-module``.
654+
If more than one mapping is specified for the same module, then the last mapping will take precedence.
655+
It is also possible to map a single module to more than one target.
656+
This can be done by listing the target modules separated by spaces in the order that they should be loaded.
657+
In the following example, ReFrame will load ``real_module0`` and ``real_module1`` whenever the ``test_module`` is encountered:
658+
659+
.. code-block:: none
660+
661+
--map-module 'test_module: real_module0 real_module1'
662+
663+
The second way of defining mappings is by listing them on a file, which you can then pass to ReFrame through the command-line option ``--module-mappings``.
664+
Each line on the file corresponds to the definition of a mapping for a single module.
665+
The syntax of the individual mappings in the file is the same as with the option ``--map-module`` and the same rules apply regarding repeated definitions.
666+
Text starting with ``#`` is considered a comment and is ignored until the end of line is encountered.
667+
Empty lines are ignored.
668+
The following block shows an example of module mapping file:
669+
670+
.. code-block:: none
671+
672+
module-1: module-1a # an inline comment
673+
module-2: module-2a module-2b module-2c
674+
675+
# This is a full line comment
676+
module-4: module-4a module-4b
677+
678+
If both ``--map-module`` and ``--module-mappings`` are passed, ReFrame will first create a mapping from the definitions on the file and it will then process the definitions passed with the ``--map-module`` options.
679+
As usual, later definitions will override the former.
680+
681+
A final note on module mappings.
682+
Module mappings can be arbitrarily deep as long as they do not form a cycle.
683+
In this case, ReFrame will issue an error (denoting the offending cyclic dependency).
684+
For example, suppose having the following mapping file:
685+
686+
.. code-block:: none
687+
688+
cudatoolkit: foo
689+
foo: bar
690+
bar: foobar
691+
foobar: cudatoolkit
692+
693+
If you now try to run a test that loads the module `cudatoolkit`, the following error will be yielded:
694+
695+
.. code-block:: none
696+
697+
------------------------------------------------------------------------------
698+
FAILURE INFO for example7_check
699+
* System partition: daint:gpu
700+
* Environment: PrgEnv-gnu
701+
* Stage directory: None
702+
* Job type: batch job (id=-1)
703+
* Maintainers: ['you-can-type-your-email-here']
704+
* Failing phase: setup
705+
* Reason: caught framework exception: module cyclic dependency: cudatoolkit->foo->bar->foobar->cudatoolkit
706+
------------------------------------------------------------------------------

reframe/core/modules.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import abc
66
import os
77
import re
8+
from collections import OrderedDict
89

910
import reframe.core.fields as fields
1011
import reframe.utility.os as os_ext
@@ -203,6 +204,35 @@ def is_module_loaded(self, name):
203204
def _is_module_loaded(self, name):
204205
return self._backend.is_module_loaded(Module(name))
205206

207+
def load_mapping(self, mapping):
208+
"""Updates the internal module mapping with a single mapping"""
209+
key, *rest = mapping.split(':')
210+
if len(rest) != 1:
211+
raise ConfigError('invalid mapping syntax: %s' % mapping)
212+
213+
key = key.strip()
214+
values = rest[0].split()
215+
if not key:
216+
raise ConfigError('no key found in mapping: %s' % mapping)
217+
218+
if not values:
219+
raise ConfigError('no mapping defined for module: %s' % key)
220+
221+
self.module_map[key] = list(OrderedDict.fromkeys(values))
222+
223+
def load_mapping_from_file(self, filename):
224+
"""Update the internal module mapping from mappings in a file."""
225+
with open(filename) as fp:
226+
for lineno, line in enumerate(fp, start=1):
227+
line = line.strip().split('#')[0]
228+
if not line:
229+
continue
230+
231+
try:
232+
self.load_mapping(line)
233+
except ConfigError as e:
234+
raise ConfigError('%s:%s' % (filename, lineno)) from e
235+
206236
@property
207237
def name(self):
208238
"""Return the name of this module system."""

reframe/frontend/cli.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
import reframe.utility.os as os_ext
88
from reframe.core.exceptions import (EnvironError, ReframeError,
99
ReframeFatalError, format_exception)
10-
from reframe.core.modules import get_modules_system
11-
from reframe.core.modules import init_modules_system
10+
from reframe.core.modules import get_modules_system, init_modules_system
1211
from reframe.frontend.argparse import ArgumentParser
1312
from reframe.frontend.executors import Runner
1413
from reframe.frontend.executors.policies import (SerialExecutionPolicy,
@@ -123,7 +122,7 @@ def main():
123122
help='Run checks on the selected list of nodes')
124123
run_options.add_argument(
125124
'--exclude-nodes', action='store', metavar='NODELIST',
126-
help='Exclude the list of nodes from runnning checks')
125+
help='Exclude the list of nodes from running checks')
127126
run_options.add_argument(
128127
'--job-option', action='append', metavar='OPT',
129128
dest='job_options', default=[],
@@ -159,6 +158,14 @@ def main():
159158
'-m', '--module', action='append', default=[],
160159
metavar='MOD', dest='user_modules',
161160
help='Load module MOD before running the regression')
161+
misc_options.add_argument(
162+
'-M', '--map-module', action='append', metavar='MAPPING',
163+
dest='module_mappings', default=[],
164+
help='Apply a single module mapping')
165+
misc_options.add_argument(
166+
'--module-mappings', action='store', metavar='FILE',
167+
dest='module_map_file',
168+
help='Apply module mappings defined in FILE')
162169
misc_options.add_argument(
163170
'--nocolor', action='store_false', dest='colorize', default=True,
164171
help='Disable coloring of output')
@@ -229,6 +236,19 @@ def main():
229236
# Init modules system
230237
init_modules_system(system.modules_system)
231238

239+
try:
240+
if options.module_map_file:
241+
get_modules_system().load_mapping_from_file(
242+
options.module_map_file)
243+
244+
if options.module_mappings:
245+
for m in options.module_mappings:
246+
get_modules_system().load_mapping(m)
247+
248+
except (ReframeError, OSError) as e:
249+
printer.error('could not load module mappings: %s' % e)
250+
sys.exit(1)
251+
232252
if options.mode:
233253
try:
234254
mode_args = site_config.modes[options.mode]

unittests/test_modules.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import unittest
3+
from tempfile import NamedTemporaryFile
34

45
import reframe.core.modules as modules
56
from reframe.core.environments import EnvironmentSnapshot
@@ -199,6 +200,11 @@ class TestModuleMapping(unittest.TestCase):
199200
def setUp(self):
200201
self.modules_activity = ModulesSystemEmulator()
201202
self.modules_system = modules.ModulesSystem(self.modules_activity)
203+
self.mapping_file = NamedTemporaryFile('wt', delete=False)
204+
205+
def tearDown(self):
206+
self.mapping_file.close()
207+
os.remove(self.mapping_file.name)
202208

203209
def test_mapping_simple(self):
204210
#
@@ -401,3 +407,57 @@ def test_mapping_deep_cycle(self):
401407
}
402408
self.assertRaisesRegex(EnvironError, 'm0->m1->m3->m4->m2->m1',
403409
self.modules_system.load_module, 'm0')
410+
411+
def test_mapping_from_file_simple(self):
412+
with self.mapping_file:
413+
self.mapping_file.write('m1:m2 m3 m3\n'
414+
'm2 : m4 \n'
415+
' #m5: m6\n'
416+
'\n'
417+
' m2: m7 m8\n'
418+
'm9: m10 # Inline comment')
419+
420+
reference_map = {
421+
'm1': ['m2', 'm3'],
422+
'm2': ['m7', 'm8'],
423+
'm9': ['m10']
424+
}
425+
self.modules_system.load_mapping_from_file(self.mapping_file.name)
426+
self.assertEqual(reference_map, self.modules_system.module_map)
427+
428+
def test_maping_from_file_missing_key_separator(self):
429+
with self.mapping_file:
430+
self.mapping_file.write('m1 m2')
431+
432+
self.assertRaises(ConfigError,
433+
self.modules_system.load_mapping_from_file,
434+
self.mapping_file.name)
435+
436+
def test_maping_from_file_empty_value(self):
437+
with self.mapping_file:
438+
self.mapping_file.write('m1: # m2')
439+
440+
self.assertRaises(ConfigError,
441+
self.modules_system.load_mapping_from_file,
442+
self.mapping_file.name)
443+
444+
def test_maping_from_file_multiple_key_separators(self):
445+
with self.mapping_file:
446+
self.mapping_file.write('m1 : m2 : m3')
447+
448+
self.assertRaises(ConfigError,
449+
self.modules_system.load_mapping_from_file,
450+
self.mapping_file.name)
451+
452+
def test_maping_from_file_empty_key(self):
453+
with self.mapping_file:
454+
self.mapping_file.write(' : m2')
455+
456+
self.assertRaises(ConfigError,
457+
self.modules_system.load_mapping_from_file,
458+
self.mapping_file.name)
459+
460+
def test_maping_from_file_missing_file(self):
461+
self.assertRaises(OSError,
462+
self.modules_system.load_mapping_from_file,
463+
'foo')

0 commit comments

Comments
 (0)