Skip to content

Commit 2d6ae43

Browse files
authored
Merge pull request #2997 from vkarak/bugfix/overriden-hooks-exec-order
[bugfix] Respect execution order of overriden hooks
2 parents 3abb7e7 + 4f67cbe commit 2d6ae43

File tree

3 files changed

+55
-24
lines changed

3 files changed

+55
-24
lines changed

reframe/core/hooks.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
import functools
77
import inspect
88

9-
import reframe.utility as util
9+
10+
def is_hook(func):
11+
return hasattr(func, '_rfm_attach')
12+
13+
14+
def is_dep_hook(func):
15+
return hasattr(func, '_rfm_resolve_deps')
1016

1117

1218
def attach_to(phase):
@@ -15,7 +21,7 @@ def attach_to(phase):
1521
:meta private:
1622
'''
1723
def deco(func):
18-
if hasattr(func, '_rfm_attach'):
24+
if is_hook(func):
1925
func._rfm_attach.append(phase)
2026
else:
2127
func._rfm_attach = [phase]
@@ -118,7 +124,7 @@ class Hook:
118124

119125
def __init__(self, fn):
120126
self.__fn = fn
121-
if not hasattr(fn, '_rfm_attach'):
127+
if not is_hook(fn):
122128
raise ValueError(f'{fn.__name__} is not a hook')
123129

124130
@property
@@ -152,7 +158,7 @@ class HookRegistry:
152158
'''Global hook registry.'''
153159

154160
def __init__(self, hooks=None):
155-
self.__hooks = util.OrderedSet()
161+
self.__hooks = []
156162
if hooks is not None:
157163
self.update(hooks)
158164

@@ -172,30 +178,36 @@ def add(self, v):
172178
of the pipeline where they must be attached. Dependencies will be
173179
resolved first in the post-setup phase if not assigned elsewhere.
174180
'''
175-
176-
if hasattr(v, '_rfm_attach'):
181+
if is_hook(v):
177182
# Always override hooks with the same name
178183
h = Hook(v)
179-
self.__hooks.discard(h)
180-
self.__hooks.add(h)
181-
elif hasattr(v, '_rfm_resolve_deps'):
184+
try:
185+
pos = self.__hooks.index(h)
186+
except ValueError:
187+
self.__hooks.append(h)
188+
else:
189+
self.__hooks[pos] = h
190+
elif is_dep_hook(v):
182191
v._rfm_attach = ['post_setup']
183-
self.__hooks.add(Hook(v))
184-
185-
def update(self, other, *, denied_hooks=None):
186-
'''Update the hook registry with the hooks from another hook registry.
192+
self.__hooks.append(Hook(v))
187193

188-
The optional ``denied_hooks`` argument takes a set of disallowed
189-
hook names, preventing their inclusion into the current hook registry.
190-
'''
194+
def update(self, other, *, forbidden_names=None):
195+
'''Update the hook registry with the hooks from another hook
196+
registry.'''
191197

192198
assert isinstance(other, HookRegistry)
193-
denied_hooks = denied_hooks or set()
199+
forbidden_names = forbidden_names or {}
194200
for h in other:
195-
if h.__name__ not in denied_hooks:
196-
# Hooks in `other` override ours
197-
self.__hooks.discard(h)
198-
self.__hooks.add(h)
201+
if (h.__name__ in forbidden_names and
202+
not is_hook(forbidden_names[h.__name__])):
203+
continue
204+
205+
try:
206+
pos = self.__hooks.index(h)
207+
except ValueError:
208+
self.__hooks.append(h)
209+
else:
210+
self.__hooks[pos] = h
199211

200212
def __repr__(self):
201213
return repr(self.__hooks)

reframe/core/meta.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -348,9 +348,8 @@ def __init__(cls, name, bases, namespace, **kwargs):
348348
# parent classes in reverse MRO order
349349
for c in list(reversed(cls.mro()))[:-1]:
350350
if hasattr(c, '_rfm_local_hook_registry'):
351-
cls._rfm_hook_registry.update(
352-
c._rfm_local_hook_registry, denied_hooks=namespace
353-
)
351+
cls._rfm_hook_registry.update(c._rfm_local_hook_registry,
352+
forbidden_names=namespace)
354353

355354
cls._rfm_hook_registry.update(cls._rfm_local_hook_registry)
356355

unittests/test_pipeline.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,26 @@ def foo(self):
11621162
assert test.pipeline_hooks() == {'post_setup': [MyTest.foo]}
11631163

11641164

1165+
def test_overriden_hook_exec_order():
1166+
@test_util.custom_prefix('unittests/resources/checks')
1167+
class X(rfm.RunOnlyRegressionTest):
1168+
@run_before('run')
1169+
def foo(self):
1170+
pass
1171+
1172+
@run_before('run')
1173+
def bar(self):
1174+
pass
1175+
1176+
class Y(X):
1177+
@run_before('run')
1178+
def foo(self):
1179+
pass
1180+
1181+
test = Y()
1182+
assert test.pipeline_hooks() == {'pre_run': [Y.foo, X.bar]}
1183+
1184+
11651185
def test_disabled_hooks(HelloTest, local_exec_ctx):
11661186
@test_util.custom_prefix('unittests/resources/checks')
11671187
class BaseTest(HelloTest):

0 commit comments

Comments
 (0)