Skip to content

Commit e9875de

Browse files
committed
Respect execution order of overriden hooks
1 parent a4e8469 commit e9875de

File tree

3 files changed

+56
-23
lines changed

3 files changed

+56
-23
lines changed

reframe/core/hooks.py

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,21 @@
99
import reframe.utility as util
1010

1111

12+
def is_hook(func):
13+
return hasattr(func, '_rfm_attach')
14+
15+
16+
def is_dep_hook(func):
17+
return hasattr(func, '_rfm_resolve_deps')
18+
19+
1220
def attach_to(phase):
1321
'''Backend function to attach a hook to a given phase.
1422
1523
:meta private:
1624
'''
1725
def deco(func):
18-
if hasattr(func, '_rfm_attach'):
26+
if is_hook(func):
1927
func._rfm_attach.append(phase)
2028
else:
2129
func._rfm_attach = [phase]
@@ -118,7 +126,7 @@ class Hook:
118126

119127
def __init__(self, fn):
120128
self.__fn = fn
121-
if not hasattr(fn, '_rfm_attach'):
129+
if not is_hook(fn):
122130
raise ValueError(f'{fn.__name__} is not a hook')
123131

124132
@property
@@ -152,7 +160,7 @@ class HookRegistry:
152160
'''Global hook registry.'''
153161

154162
def __init__(self, hooks=None):
155-
self.__hooks = util.OrderedSet()
163+
self.__hooks = []
156164
if hooks is not None:
157165
self.update(hooks)
158166

@@ -172,30 +180,36 @@ def add(self, v):
172180
of the pipeline where they must be attached. Dependencies will be
173181
resolved first in the post-setup phase if not assigned elsewhere.
174182
'''
175-
176-
if hasattr(v, '_rfm_attach'):
183+
if is_hook(v):
177184
# Always override hooks with the same name
178185
h = Hook(v)
179-
self.__hooks.discard(h)
180-
self.__hooks.add(h)
181-
elif hasattr(v, '_rfm_resolve_deps'):
186+
try:
187+
pos = self.__hooks.index(h)
188+
except ValueError:
189+
self.__hooks.append(h)
190+
else:
191+
self.__hooks[pos] = h
192+
elif is_dep_hook(v):
182193
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.
194+
self.__hooks.append(Hook(v))
187195

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

192200
assert isinstance(other, HookRegistry)
193-
denied_hooks = denied_hooks or set()
201+
forbidden_names = forbidden_names or {}
194202
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)
203+
if (h.__name__ in forbidden_names and
204+
not is_hook(forbidden_names[h.__name__])):
205+
continue
206+
207+
try:
208+
pos = self.__hooks.index(h)
209+
except ValueError:
210+
self.__hooks.append(h)
211+
else:
212+
self.__hooks[pos] = h
199213

200214
def __repr__(self):
201215
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)