Skip to content
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
9 changes: 9 additions & 0 deletions docs/manpage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,15 @@ Options controlling ReFrame execution
The test stage and output directories will receive a ``_retry<N>`` suffix every time the test is retried.


.. option:: --disable-hook=HOOK

Disable the pipeline hook named ``HOOK`` from all the tests that will run.
This feature is useful when you have implemented test workarounds as pipeline hooks, in which case you can quickly disable them from the command line.
This option may be specified multiple times in order to disable multiple hooks at the same time.

.. versionadded:: 3.2


----------------------------------
Options controlling job submission
----------------------------------
Expand Down
6 changes: 4 additions & 2 deletions reframe/core/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ def __init__(cls, name, bases, namespace, **kwargs):
except AttributeError:
pass

hooks['post_setup'] = fn_with_deps + hooks.get('post_setup', [])
cls._rfm_pipeline_hooks = hooks
if fn_with_deps:
hooks['post_setup'] = fn_with_deps + hooks.get('post_setup', [])

cls._rfm_pipeline_hooks = hooks
cls._rfm_disabled_hooks = set()
cls._final_methods = {v.__name__ for v in namespace.values()
if hasattr(v, '_rfm_final')}

Expand Down
22 changes: 19 additions & 3 deletions reframe/core/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,26 @@ def hooks(obj, kind):
hook_name = 'xxx'

func_names = set()
ret = []
disabled_hooks = set()
func_list = []
for cls in type(obj).mro():
if hasattr(cls, '_rfm_disabled_hooks'):
disabled_hooks |= cls._rfm_disabled_hooks

try:
funcs = cls._rfm_pipeline_hooks.get(hook_name, [])
if any(fn.__name__ in func_names for fn in funcs):
# hook has been overriden
continue

func_names |= {fn.__name__ for fn in funcs}
ret += funcs
func_list += funcs
except AttributeError:
pass

return ret
# Remove the disabled hooks before returning
return [fn for fn in func_list
if fn.__name__ not in disabled_hooks]

'''Run the hooks before and after func.'''
@functools.wraps(func)
Expand Down Expand Up @@ -129,6 +135,16 @@ class RegressionTest(metaclass=RegressionTestMeta):

'''

@classmethod
def disable_hook(cls, hook_name):
'''Disable pipeline hook by name.

:arg hook_name: The function name of the hook to be disabled.

:meta private:
'''
cls._rfm_disabled_hooks.add(hook_name)

#: The name of the test.
#:
#: :type: string that can contain any character except ``/``
Expand Down
87 changes: 58 additions & 29 deletions reframe/frontend/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,49 @@
from reframe.frontend.printer import PrettyPrinter


def format_check(check, detailed):
lines = [' - %s (found in %s)' % (check.name,
inspect.getfile(type(check)))]
flex = 'flexible' if check.num_tasks <= 0 else 'standard'

if detailed:
lines += [
f" description: {check.descr}",
f" systems: {', '.join(check.valid_systems)}",
f" environments: {', '.join(check.valid_prog_environs)}",
f" modules: {', '.join(check.modules)}",
f" task allocation: {flex}",
f" dependencies: "
f"{', '.join([d[0] for d in check.user_deps()])}",
f" tags: {', '.join(check.tags)}",
f" maintainers: {', '.join(check.maintainers)}"
]
def format_check(check, detailed=False):
def fmt_list(x):
if not x:
return '<none>'

return ', '.join(x)

location = inspect.getfile(type(check))
if not detailed:
return f'- {check.name} (found in {location!r})\n'

if check.num_tasks > 0:
node_alloc_scheme = f'standard ({check.num_tasks} task(s))'
elif check.num_tasks == 0:
node_alloc_scheme = 'flexible'
else:
node_alloc_scheme = f'flexible (minimum {-check.num_tasks} task(s))'

check_info = {
'Dependencies': fmt_list([d[0] for d in check.user_deps()]),
'Description': check.descr,
'Environment modules': fmt_list(check.modules),
'Location': location,
'Maintainers': fmt_list(check.maintainers),
'Node allocation': node_alloc_scheme,
'Pipeline hooks': {
k: fmt_list(fn.__name__ for fn in v)
for k, v in type(check)._rfm_pipeline_hooks.items()
},
'Tags': fmt_list(check.tags),
'Valid environments': fmt_list(check.valid_prog_environs),
'Valid systems': fmt_list(check.valid_systems)
}
lines = [f'- {check.name}:']
for prop, val in check_info.items():
lines.append(f' {prop}:')
if isinstance(val, dict):
for k, v in val.items():
lines.append(f' {k}: {v}')
else:
lines.append(f' {val}')

lines.append('')

return '\n'.join(lines)

Expand All @@ -64,10 +90,8 @@ def format_env(envvars):

def list_checks(checks, printer, detailed=False):
printer.info('[List of matched checks]')
for c in checks:
printer.info(format_check(c, detailed))

printer.info('\nFound %d check(s).' % len(checks))
printer.info('\n'.join(format_check(c, detailed) for c in checks))
printer.info(f'Found {len(checks)} check(s)')


def generate_report_filename(filepatt):
Expand Down Expand Up @@ -292,6 +316,10 @@ def main():
dest='flex_alloc_nodes', metavar='{all|STATE|NUM}', default=None,
help='Set strategy for the flexible node allocation (default: "idle").'
)
run_options.add_argument(
'--disable-hook', action='append', metavar='NAME', dest='hooks',
default=[], help='Disable a pipeline hook for this run'
)
env_options.add_argument(
'-M', '--map-module', action='append', metavar='MAPPING',
dest='module_mappings', default=[],
Expand Down Expand Up @@ -357,7 +385,7 @@ def main():
)
misc_options.add_argument(
'--upgrade-config-file', action='store', metavar='OLD[:NEW]',
help='Upgrade old configuration file to new syntax'
help='Upgrade ReFrame 2.x configuration file to ReFrame 3.x syntax'
)
misc_options.add_argument(
'-V', '--version', action='version', version=os_ext.reframe_version()
Expand Down Expand Up @@ -613,6 +641,12 @@ def print_infoline(param, value):

# Generate the test cases, validate dependencies and sort them
checks_matched = list(checks_matched)

# Disable hooks
for c in checks_matched:
for h in options.hooks:
type(c).disable_hook(h)

testcases = generate_testcases(checks_matched,
options.skip_system_check,
options.skip_prgenv_check,
Expand Down Expand Up @@ -675,13 +709,8 @@ def print_infoline(param, value):

# Act on checks
success = True
if options.list:
# List matched checks
list_checks(list(checks_matched), printer)
elif options.list_detailed:
# List matched checks with details
list_checks(list(checks_matched), printer, detailed=True)

if options.list or options.list_detailed:
list_checks(list(checks_matched), printer, options.list_detailed)
elif options.run:
# Setup the execution policy
if options.exec_policy == 'serial':
Expand Down
30 changes: 30 additions & 0 deletions unittests/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,36 @@ def y(self):
assert test.foo == 10


def test_disabled_hooks(local_exec_ctx):
@fixtures.custom_prefix('unittests/resources/checks')
class BaseTest(HelloTest):
def __init__(self):
super().__init__()
self.name = type(self).__name__
self.executable = os.path.join('.', self.name)
self.var = 0
self.foo = 0

@rfm.run_after('setup')
def x(self):
self.var += 1

@rfm.run_before('setup')
def y(self):
self.foo += 1

class MyTest(BaseTest):
@rfm.run_after('setup')
def x(self):
self.var += 5

test = MyTest()
MyTest.disable_hook('y')
_run(test, *local_exec_ctx)
assert test.var == 5
assert test.foo == 0


def test_require_deps(local_exec_ctx):
import reframe.frontend.dependency as dependency
import reframe.frontend.executors as executors
Expand Down