Skip to content

Commit f72e094

Browse files
author
Vasileios Karakasis
authored
Merge branch 'master' into containers/doc
2 parents 2dcaa16 + 14515c9 commit f72e094

File tree

3 files changed

+125
-4
lines changed

3 files changed

+125
-4
lines changed

docs/tutorial.rst

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,45 @@ In the following example ``var`` will be set to ``2`` after the setup phase is e
418418
def inc(self):
419419
self.var += 1
420420
421+
Another important feature of the hooks syntax, is that hooks are inherited by derived tests, unless you override the function and re-hook it explicitly.
422+
In the following example, the :func:`setflags()` will be executed before the compilation phase of the :class:`DerivedTest`:
423+
424+
.. code:: python
425+
426+
class BaseTest(rfm.RegressionTest):
427+
def __init__(self):
428+
...
429+
self.build_system = 'Make'
430+
431+
@rfm.run_before('compile')
432+
def setflags(self):
433+
if self.current_environ.name == 'X':
434+
self.build_system.cppflags = ['-Ifoo']
435+
436+
437+
@rfm.simple_test
438+
class DerivedTest(BaseTest):
439+
def __init__(self):
440+
super().__init__()
441+
...
442+
443+
444+
If you override a hooked function in a derived class, the base class' hook will not be executed, unless you explicitly call it with ``super()``.
445+
In the following example, we completely disable the :func:`setflags()` hook of the base class:
446+
447+
448+
.. code:: python
449+
450+
@rfm.simple_test
451+
class DerivedTest(BaseTest):
452+
@rfm.run_before('compile')
453+
def setflags(self):
454+
pass
455+
456+
457+
Notice that in order to redefine a hook, you need not only redefine the method in the derived class, but you should hook it at the same pipeline phase.
458+
Otherwise, the base class hook will be executed.
459+
421460

422461
.. note::
423462
You may still configure your test per programming environment and per system partition by overriding the :func:`setup <reframe.core.pipeline.RegressionTest.setup>` method, as in ReFrame versions prior to 2.20, but this is now discouraged since it is more error prone, as you have to memorize the signature of the pipeline methods that you override and also remember to call ``super()``.
@@ -664,7 +703,7 @@ Here is the final example code that combines all the tests discussed before:
664703
This test abstracts away the common functionality found in almost all of our tutorial tests (executable options, sanity checking, etc.) to a base class, from which all the concrete regression tests derive.
665704
Each test then redefines only the parts that are specific to it.
666705
Notice also that only the actual tests, i.e., the derived classes, are made visible to the framework through the ``@simple_test`` decorator.
667-
Decorating the base class has now meaning, because it does not correspond to an actual test.
706+
Decorating the base class has no meaning, because it does not correspond to an actual test.
668707

669708
The total line count of this refactored example is less than half of that of the individual tutorial tests.
670709
Another interesting thing to note here is the base class accepting additional additional parameters to its constructor, so that the concrete subclasses can initialize it based on their needs.

reframe/core/pipeline.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,21 @@ def hooks(obj, kind):
5151
# Just any name that does not exist
5252
hook_name = 'xxx'
5353

54-
return obj._rfm_pipeline_hooks.get(hook_name, [])
54+
func_names = set()
55+
ret = []
56+
for cls in type(obj).mro():
57+
try:
58+
funcs = cls._rfm_pipeline_hooks.get(hook_name, [])
59+
if any(fn.__name__ in func_names for fn in funcs):
60+
# hook has been overriden
61+
continue
62+
63+
func_names |= {fn.__name__ for fn in funcs}
64+
ret += funcs
65+
except AttributeError:
66+
pass
67+
68+
return ret
5569

5670
'''Run the hooks before and after func.'''
5771
@functools.wraps(func)
@@ -1070,7 +1084,7 @@ def compile(self):
10701084
with os_ext.change_dir(self._stagedir):
10711085
try:
10721086
self._build_job.prepare(build_commands, environs,
1073-
login=True, trap_errors=True)
1087+
trap_errors=True)
10741088
except OSError as e:
10751089
raise PipelineError('failed to prepare build job') from e
10761090

@@ -1149,7 +1163,7 @@ def run(self):
11491163

11501164
with os_ext.change_dir(self._stagedir):
11511165
try:
1152-
self._job.prepare(commands, environs, login=True)
1166+
self._job.prepare(commands, environs)
11531167
except OSError as e:
11541168
raise PipelineError('failed to prepare job') from e
11551169

unittests/test_pipeline.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,74 @@ def x(self):
493493
_run(test, self.partition, self.prgenv)
494494
assert test.var == 3
495495

496+
def test_inherited_hooks(self):
497+
import unittests.resources.checks.hellocheck as mod
498+
499+
class BaseTest(mod.HelloTest):
500+
def __init__(self):
501+
super().__init__()
502+
self._prefix = 'unittests/resources/checks'
503+
self.name = type(self).__name__
504+
self.executable = os.path.join('.', self.name)
505+
self.var = 0
506+
507+
@rfm.run_after('setup')
508+
def x(self):
509+
self.var += 1
510+
511+
class C(rfm.RegressionTest):
512+
@rfm.run_before('run')
513+
def y(self):
514+
self.foo = 1
515+
516+
class DerivedTest(BaseTest, C):
517+
@rfm.run_after('setup')
518+
def z(self):
519+
self.var += 1
520+
521+
class MyTest(DerivedTest):
522+
pass
523+
524+
test = MyTest()
525+
_run(test, self.partition, self.prgenv)
526+
assert test.var == 2
527+
assert test.foo == 1
528+
529+
def test_overriden_hooks(self):
530+
import unittests.resources.checks.hellocheck as mod
531+
532+
class BaseTest(mod.HelloTest):
533+
def __init__(self):
534+
super().__init__()
535+
self._prefix = 'unittests/resources/checks'
536+
self.name = type(self).__name__
537+
self.executable = os.path.join('.', self.name)
538+
self.var = 0
539+
self.foo = 0
540+
541+
@rfm.run_after('setup')
542+
def x(self):
543+
self.var += 1
544+
545+
@rfm.run_before('setup')
546+
def y(self):
547+
self.foo += 1
548+
549+
class DerivedTest(BaseTest):
550+
@rfm.run_after('setup')
551+
def x(self):
552+
self.var += 5
553+
554+
class MyTest(DerivedTest):
555+
@rfm.run_before('setup')
556+
def y(self):
557+
self.foo += 10
558+
559+
test = MyTest()
560+
_run(test, self.partition, self.prgenv)
561+
assert test.var == 5
562+
assert test.foo == 10
563+
496564
def test_require_deps(self):
497565
import unittests.resources.checks.hellocheck as mod
498566
import reframe.frontend.dependency as dependency

0 commit comments

Comments
 (0)