Skip to content

Commit a80f4fc

Browse files
author
Vasileios Karakasis
authored
Merge branch 'master' into bugfix/json-tuples-as-keys
2 parents a57f160 + 6c5f3fc commit a80f4fc

File tree

4 files changed

+50
-21
lines changed

4 files changed

+50
-21
lines changed

reframe/core/meta.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ def __prepare__(metacls, name, bases, **kwargs):
176176
namespace['variable'] = variables.TestVar
177177
namespace['required'] = variables.Undefined
178178

179+
# Utility decorators
179180
def bind(fn, name=None):
180181
'''Directive to bind a free function to a class.
181182
@@ -186,7 +187,14 @@ def bind(fn, name=None):
186187
namespace[inst.__name__] = inst
187188
return inst
188189

190+
def final(fn):
191+
'''Indicate that a function is final and cannot be overridden.'''
192+
193+
fn._rfm_final = True
194+
return fn
195+
189196
namespace['bind'] = bind
197+
namespace['final'] = final
190198

191199
# Hook-related functionality
192200
def run_before(stage):
@@ -205,8 +213,6 @@ def run_before(stage):
205213

206214
return hooks.attach_to('pre_' + stage)
207215

208-
namespace['run_before'] = run_before
209-
210216
def run_after(stage):
211217
'''Decorator for attaching a test method to a pipeline stage.
212218
@@ -228,6 +234,7 @@ def run_after(stage):
228234

229235
return hooks.attach_to('post_' + stage)
230236

237+
namespace['run_before'] = run_before
231238
namespace['run_after'] = run_after
232239
namespace['require_deps'] = hooks.require_deps
233240

@@ -259,7 +266,8 @@ class was created or even at the instance level (e.g. doing
259266

260267
directives = [
261268
'parameter', 'variable', 'bind', 'run_before', 'run_after',
262-
'require_deps', 'required', 'deferrable', 'sanity_function'
269+
'require_deps', 'required', 'deferrable', 'sanity_function',
270+
'final'
263271
]
264272
for b in directives:
265273
namespace.pop(b, None)
@@ -326,13 +334,12 @@ def __init__(cls, name, bases, namespace, **kwargs):
326334
if hasattr(v, '_rfm_final')}
327335

328336
# Add the final functions from its parents
329-
cls._final_methods.update(*(b._final_methods for b in bases
330-
if hasattr(b, '_final_methods')))
337+
bases_w_final = [b for b in bases if hasattr(b, '_final_methods')]
338+
cls._final_methods.update(*(b._final_methods for b in bases_w_final))
331339

332-
if getattr(cls, '_rfm_special_test', None):
340+
if getattr(cls, '_rfm_override_final', None):
333341
return
334342

335-
bases_w_final = [b for b in bases if hasattr(b, '_final_methods')]
336343
for v in namespace.values():
337344
for b in bases_w_final:
338345
if callable(v) and v.__name__ in b._final_methods:

reframe/core/pipeline.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@
9292

9393
def final(fn):
9494
fn._rfm_final = True
95+
user_deprecation_warning(
96+
'using the @rfm.final decorator from the rfm module is '
97+
'deprecated; please use the built-in decorator @final instead.',
98+
from_version='3.7.0'
99+
)
95100

96101
@functools.wraps(fn)
97102
def _wrapped(*args, **kwargs):
@@ -802,7 +807,7 @@ def __getattribute__(self, name):
802807
@classmethod
803808
def __init_subclass__(cls, *, special=False, pin_prefix=False, **kwargs):
804809
super().__init_subclass__(**kwargs)
805-
cls._rfm_special_test = special
810+
cls._rfm_override_final = special
806811

807812
# Insert the prefix to pin the test to if the test lives in a test
808813
# library with resources in it.
@@ -1934,7 +1939,7 @@ def run(self):
19341939
self._copy_to_stagedir(os.path.join(self._prefix,
19351940
self.sourcesdir))
19361941

1937-
super().run.__wrapped__(self)
1942+
super().run()
19381943

19391944

19401945
class CompileOnlyRegressionTest(RegressionTest, special=True):

unittests/test_loader.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import reframe as rfm
1010
from reframe.core.exceptions import NameConflictError, ReframeSyntaxError
11+
from reframe.core.warnings import ReframeDeprecationWarning
1112
from reframe.frontend.loader import RegressionCheckLoader
1213

1314

@@ -137,17 +138,12 @@ def __init__(self):
137138
def setup(self, partition, environ, **job_opts):
138139
super().setup(partition, environ, **job_opts)
139140

140-
@rfm.simple_test
141-
class TestFinal(rfm.RegressionTest):
142-
def __init__(self):
143-
pass
144-
145-
@rfm.final
146-
def my_new_final(self):
147-
pass
148-
149-
with pytest.raises(ReframeSyntaxError):
141+
with pytest.warns(ReframeDeprecationWarning):
150142
@rfm.simple_test
151-
class TestFinalDerived(TestFinal):
152-
def my_new_final(self, a, b):
143+
class TestFinal(rfm.RegressionTest):
144+
def __init__(self):
145+
pass
146+
147+
@rfm.final
148+
def my_new_final(self):
153149
pass

unittests/test_meta.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class MyTest(MyMeta):
5252
deferrable(ext)
5353
sanity_function(ext)
5454
v = required
55+
final(ext)
5556

5657
def __init__(self):
5758
assert not hasattr(self, 'parameter')
@@ -63,6 +64,7 @@ def __init__(self):
6364
assert not hasattr(self, 'deferrable')
6465
assert not hasattr(self, 'sanity_function')
6566
assert not hasattr(self, 'required')
67+
assert not hasattr(self, 'final')
6668

6769
MyTest()
6870

@@ -201,3 +203,22 @@ def hook_b(self):
201203
assert not Bar.hook_in_stage('hook_b', 'pre_compile')
202204
assert Bar.hook_in_stage('hook_c', 'post_run_wait')
203205
assert Bar.hook_in_stage('hook_a', 'pre_sanity')
206+
207+
208+
def test_final(MyMeta):
209+
class Base(MyMeta):
210+
@final
211+
def foo(self):
212+
pass
213+
214+
with pytest.raises(ReframeSyntaxError):
215+
class Derived(Base):
216+
def foo(self):
217+
'''Override attempt.'''
218+
219+
class AllowFinalOverride(Base):
220+
'''Use flag to bypass the final override check.'''
221+
_rfm_override_final = True
222+
223+
def foo(self):
224+
'''Overriding foo is now allowed.'''

0 commit comments

Comments
 (0)