From 8eca2dd938059dacd04061fb1d87c0104af8e521 Mon Sep 17 00:00:00 2001 From: Javier Otero Date: Wed, 7 Jul 2021 12:39:09 +0200 Subject: [PATCH 1/5] Expose final deco from meta --- reframe/core/meta.py | 19 +++++++++++++------ reframe/core/pipeline.py | 5 +++++ unittests/test_loader.py | 15 --------------- unittests/test_meta.py | 14 ++++++++++++++ 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/reframe/core/meta.py b/reframe/core/meta.py index ab51fe9231..eaf10a265b 100644 --- a/reframe/core/meta.py +++ b/reframe/core/meta.py @@ -176,6 +176,7 @@ def __prepare__(metacls, name, bases, **kwargs): namespace['variable'] = variables.TestVar namespace['required'] = variables.Undefined + # Utility decorators def bind(fn, name=None): '''Directive to bind a free function to a class. @@ -186,7 +187,14 @@ def bind(fn, name=None): namespace[inst.__name__] = inst return inst + def final(fn): + '''Indicate that a function is final and cannot be overridden.''' + + fn._rfm_final = True + return fn + namespace['bind'] = bind + namespace['final'] = final # Hook-related functionality def run_before(stage): @@ -205,8 +213,6 @@ def run_before(stage): return hooks.attach_to('pre_' + stage) - namespace['run_before'] = run_before - def run_after(stage): '''Decorator for attaching a test method to a pipeline stage. @@ -228,6 +234,7 @@ def run_after(stage): return hooks.attach_to('post_' + stage) + namespace['run_before'] = run_before namespace['run_after'] = run_after namespace['require_deps'] = hooks.require_deps @@ -259,7 +266,8 @@ class was created or even at the instance level (e.g. doing directives = [ 'parameter', 'variable', 'bind', 'run_before', 'run_after', - 'require_deps', 'required', 'deferrable', 'sanity_function' + 'require_deps', 'required', 'deferrable', 'sanity_function', + 'final' ] for b in directives: namespace.pop(b, None) @@ -326,13 +334,12 @@ def __init__(cls, name, bases, namespace, **kwargs): if hasattr(v, '_rfm_final')} # Add the final functions from its parents - cls._final_methods.update(*(b._final_methods for b in bases - if hasattr(b, '_final_methods'))) + bases_w_final = [b for b in bases if hasattr(b, '_final_methods')] + cls._final_methods.update(*(b._final_methods for b in bases_w_final)) if getattr(cls, '_rfm_special_test', None): return - bases_w_final = [b for b in bases if hasattr(b, '_final_methods')] for v in namespace.values(): for b in bases_w_final: if callable(v) and v.__name__ in b._final_methods: diff --git a/reframe/core/pipeline.py b/reframe/core/pipeline.py index fe92d0a5bb..4a6c25a51c 100644 --- a/reframe/core/pipeline.py +++ b/reframe/core/pipeline.py @@ -92,6 +92,11 @@ def final(fn): fn._rfm_final = True + warn.user_deprecation_warning( + 'using the @rfm.final decorator from the rfm module is ' + 'deprecated; please use the built-in decorator @final instead.', + from_version='3.7.0' + ) @functools.wraps(fn) def _wrapped(*args, **kwargs): diff --git a/unittests/test_loader.py b/unittests/test_loader.py index ae27adf4cc..d925b452f3 100644 --- a/unittests/test_loader.py +++ b/unittests/test_loader.py @@ -136,18 +136,3 @@ def __init__(self): def setup(self, partition, environ, **job_opts): super().setup(partition, environ, **job_opts) - - @rfm.simple_test - class TestFinal(rfm.RegressionTest): - def __init__(self): - pass - - @rfm.final - def my_new_final(self): - pass - - with pytest.raises(ReframeSyntaxError): - @rfm.simple_test - class TestFinalDerived(TestFinal): - def my_new_final(self, a, b): - pass diff --git a/unittests/test_meta.py b/unittests/test_meta.py index 4dfa290061..83a1b64314 100644 --- a/unittests/test_meta.py +++ b/unittests/test_meta.py @@ -52,6 +52,7 @@ class MyTest(MyMeta): deferrable(ext) sanity_function(ext) v = required + final(ext) def __init__(self): assert not hasattr(self, 'parameter') @@ -63,6 +64,7 @@ def __init__(self): assert not hasattr(self, 'deferrable') assert not hasattr(self, 'sanity_function') assert not hasattr(self, 'required') + assert not hasattr(self, 'final') MyTest() @@ -201,3 +203,15 @@ def hook_b(self): assert not Bar.hook_in_stage('hook_b', 'pre_compile') assert Bar.hook_in_stage('hook_c', 'post_run_wait') assert Bar.hook_in_stage('hook_a', 'pre_sanity') + + +def test_final(MyMeta): + class MyBase(MyMeta): + @final + def foo(self): + pass + + with pytest.raises(ReframeSyntaxError): + class MyDerived(MyBase): + def foo(self): + '''Override attempt''' From fb1f8c7a94d74a7f4af5e25a8d02a15c77ebb6cb Mon Sep 17 00:00:00 2001 From: Javier Otero Date: Wed, 7 Jul 2021 13:20:01 +0200 Subject: [PATCH 2/5] Bugfix call to super.run() --- reframe/core/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reframe/core/pipeline.py b/reframe/core/pipeline.py index 4a6c25a51c..f324809dcb 100644 --- a/reframe/core/pipeline.py +++ b/reframe/core/pipeline.py @@ -92,7 +92,7 @@ def final(fn): fn._rfm_final = True - warn.user_deprecation_warning( + user_deprecation_warning( 'using the @rfm.final decorator from the rfm module is ' 'deprecated; please use the built-in decorator @final instead.', from_version='3.7.0' @@ -1939,7 +1939,7 @@ def run(self): self._copy_to_stagedir(os.path.join(self._prefix, self.sourcesdir)) - super().run.__wrapped__(self) + super().run() class CompileOnlyRegressionTest(RegressionTest, special=True): From dfa97368b58eb477948e6013eaea4620af9e3521 Mon Sep 17 00:00:00 2001 From: Javier Otero Date: Wed, 7 Jul 2021 14:01:19 +0200 Subject: [PATCH 3/5] Rename _rfm_special_test to _rfm_override_final --- reframe/core/meta.py | 2 +- reframe/core/pipeline.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reframe/core/meta.py b/reframe/core/meta.py index eaf10a265b..6176a4354c 100644 --- a/reframe/core/meta.py +++ b/reframe/core/meta.py @@ -337,7 +337,7 @@ def __init__(cls, name, bases, namespace, **kwargs): bases_w_final = [b for b in bases if hasattr(b, '_final_methods')] cls._final_methods.update(*(b._final_methods for b in bases_w_final)) - if getattr(cls, '_rfm_special_test', None): + if getattr(cls, '_rfm_override_final', None): return for v in namespace.values(): diff --git a/reframe/core/pipeline.py b/reframe/core/pipeline.py index f324809dcb..ac3aa53a5a 100644 --- a/reframe/core/pipeline.py +++ b/reframe/core/pipeline.py @@ -807,7 +807,7 @@ def __getattribute__(self, name): @classmethod def __init_subclass__(cls, *, special=False, pin_prefix=False, **kwargs): super().__init_subclass__(**kwargs) - cls._rfm_special_test = special + cls._rfm_override_final = special # Insert the prefix to pin the test to if the test lives in a test # library with resources in it. From f837933a323d46df29997b3c46032e13c132668e Mon Sep 17 00:00:00 2001 From: Javier Otero Date: Wed, 7 Jul 2021 14:11:50 +0200 Subject: [PATCH 4/5] Extend meta unit tests --- unittests/test_meta.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/unittests/test_meta.py b/unittests/test_meta.py index 83a1b64314..204489397d 100644 --- a/unittests/test_meta.py +++ b/unittests/test_meta.py @@ -206,12 +206,19 @@ def hook_b(self): def test_final(MyMeta): - class MyBase(MyMeta): + class Base(MyMeta): @final def foo(self): pass with pytest.raises(ReframeSyntaxError): - class MyDerived(MyBase): + class Derived(Base): def foo(self): - '''Override attempt''' + '''Override attempt.''' + + class AllowFinalOverride(Base): + '''Use flag to bypass the final override check.''' + _rfm_override_final = True + + def foo(self): + '''Overriding foo is now allowed.''' From 81eae834ace28f261113cd4c59364d7089ec6eda Mon Sep 17 00:00:00 2001 From: Javier Otero <71280927+jjotero@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:21:53 +0200 Subject: [PATCH 5/5] Test deprecation warning --- unittests/test_loader.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/unittests/test_loader.py b/unittests/test_loader.py index d925b452f3..b390919e00 100644 --- a/unittests/test_loader.py +++ b/unittests/test_loader.py @@ -8,6 +8,7 @@ import reframe as rfm from reframe.core.exceptions import NameConflictError, ReframeSyntaxError +from reframe.core.warnings import ReframeDeprecationWarning from reframe.frontend.loader import RegressionCheckLoader @@ -136,3 +137,13 @@ def __init__(self): def setup(self, partition, environ, **job_opts): super().setup(partition, environ, **job_opts) + + with pytest.warns(ReframeDeprecationWarning): + @rfm.simple_test + class TestFinal(rfm.RegressionTest): + def __init__(self): + pass + + @rfm.final + def my_new_final(self): + pass