From e8a85adf11ae4dcec4980edbd51790a2b8c51dcc Mon Sep 17 00:00:00 2001 From: Vasileios Karakasis Date: Fri, 23 Oct 2020 00:53:17 +0200 Subject: [PATCH 1/6] Color deprecation warnings Also: - Move all the warning related parts into a separate file. --- reframe/core/config.py | 5 +- reframe/core/deferrable.py | 2 - reframe/core/exceptions.py | 28 --------- reframe/core/fields.py | 2 +- reframe/core/meta.py | 2 +- reframe/core/pipeline.py | 3 +- reframe/core/systems.py | 2 +- reframe/core/warnings.py | 58 +++++++++++++++++++ reframe/frontend/cli.py | 3 +- .../checks_unlisted/deprecated_test.py | 3 +- unittests/test_config.py | 3 +- unittests/test_fields.py | 2 +- unittests/test_loader.py | 2 +- 13 files changed, 73 insertions(+), 42 deletions(-) create mode 100644 reframe/core/warnings.py diff --git a/reframe/core/config.py b/reframe/core/config.py index 3991142457..80cc4a7b73 100644 --- a/reframe/core/config.py +++ b/reframe/core/config.py @@ -19,10 +19,9 @@ import reframe.utility as util import reframe.utility.osext as osext import reframe.utility.typecheck as types -from reframe.core.exceptions import (ConfigError, - ReframeDeprecationWarning, - ReframeFatalError) +from reframe.core.exceptions import ConfigError, ReframeFatalError from reframe.core.logging import getlogger +from reframe.core.warnings import ReframeDeprecationWarning from reframe.utility import ScopedDict diff --git a/reframe/core/deferrable.py b/reframe/core/deferrable.py index e3274bda64..dcc937fcad 100644 --- a/reframe/core/deferrable.py +++ b/reframe/core/deferrable.py @@ -6,8 +6,6 @@ import builtins import functools -from reframe.core.exceptions import user_deprecation_warning - def deferrable(func): '''Function decorator for converting a function to a deferred diff --git a/reframe/core/exceptions.py b/reframe/core/exceptions.py index ba5485a30f..c20d801564 100644 --- a/reframe/core/exceptions.py +++ b/reframe/core/exceptions.py @@ -270,13 +270,6 @@ class DependencyError(ReframeError): '''Raised when a dependency problem is encountered.''' -class ReframeDeprecationWarning(DeprecationWarning): - '''Warning raised for deprecated features of the framework.''' - - -warnings.filterwarnings('default', category=ReframeDeprecationWarning) - - def user_frame(tb): if not inspect.istraceback(tb): raise ValueError('could not retrieve frame: argument not a traceback') @@ -326,24 +319,3 @@ def format_user_frame(frame): exc_str = ''.join(traceback.format_exception(exc_type, exc_value, tb)) return 'unexpected error: %s\n%s' % (exc_value, exc_str) - - -def user_deprecation_warning(message): - '''Raise a deprecation warning at the user stack frame that eventually - calls this function. - - As "user stack frame" is considered a stack frame that is outside the - :py:mod:`reframe` base module. - ''' - - # Unroll the stack and issue the warning from the first stack frame that is - # outside the framework. - stack_level = 1 - for s in inspect.stack(): - module = inspect.getmodule(s.frame) - if module is None or not module.__name__.startswith('reframe'): - break - - stack_level += 1 - - warnings.warn(message, ReframeDeprecationWarning, stacklevel=stack_level) diff --git a/reframe/core/fields.py b/reframe/core/fields.py index 07e459ad2f..af53237de6 100644 --- a/reframe/core/fields.py +++ b/reframe/core/fields.py @@ -13,7 +13,7 @@ import re import reframe.utility.typecheck as types -from reframe.core.exceptions import user_deprecation_warning +from reframe.core.warnings import user_deprecation_warning from reframe.utility import ScopedDict diff --git a/reframe/core/meta.py b/reframe/core/meta.py index 45a67045df..538b2393be 100644 --- a/reframe/core/meta.py +++ b/reframe/core/meta.py @@ -7,7 +7,7 @@ # Met-class for creating regression tests. # -from reframe.core.exceptions import user_deprecation_warning +from reframe.core.warnings import user_deprecation_warning class RegressionTestMeta(type): diff --git a/reframe/core/pipeline.py b/reframe/core/pipeline.py index 6ec215e368..0b6292646f 100644 --- a/reframe/core/pipeline.py +++ b/reframe/core/pipeline.py @@ -34,9 +34,10 @@ from reframe.core.deferrable import _DeferredExpression from reframe.core.exceptions import (BuildError, DependencyError, PipelineError, SanityError, - PerformanceError, user_deprecation_warning) + PerformanceError) from reframe.core.meta import RegressionTestMeta from reframe.core.schedulers import Job +from reframe.core.warnings import user_deprecation_warning # Dependency kinds diff --git a/reframe/core/systems.py b/reframe/core/systems.py index 21f33fe31f..17fa370eca 100644 --- a/reframe/core/systems.py +++ b/reframe/core/systems.py @@ -155,7 +155,7 @@ def launcher(self): Please use :attr:`launcher_type` instead. ''' - from reframe.core.exceptions import user_deprecation_warning + from reframe.core.warnings import user_deprecation_warning user_deprecation_warning("the 'launcher' attribute is deprecated; " "please use 'launcher_type' instead") diff --git a/reframe/core/warnings.py b/reframe/core/warnings.py new file mode 100644 index 0000000000..23b685f883 --- /dev/null +++ b/reframe/core/warnings.py @@ -0,0 +1,58 @@ +import inspect +import warnings + + +class ReframeDeprecationWarning(DeprecationWarning): + '''Warning raised for deprecated features of the framework.''' + + +warnings.filterwarnings('default', category=ReframeDeprecationWarning) + + +_format_warning_orig = warnings.formatwarning + + +def _format_warning(message, category, filename, lineno, line=None): + import reframe.core.runtime as rt + import reframe.utility.color as color + + if category != ReframeDeprecationWarning: + return _format_warning_orig + + if line is None: + # Read in the line from the file + with open(filename) as fp: + try: + line = fp.readlines()[lineno-1] + except IndexError: + line = '' + + message = f'{filename}:{lineno}: WARNING: {message}\n{line}\n' + if rt.runtime().get_option('general/0/colorize'): + message = color.colorize(message, color.YELLOW) + + return message + + +warnings.formatwarning = _format_warning + + +def user_deprecation_warning(message): + '''Raise a deprecation warning at the user stack frame that eventually + calls this function. + + As "user stack frame" is considered a stack frame that is outside the + :py:mod:`reframe` base module. + ''' + + # Unroll the stack and issue the warning from the first stack frame that is + # outside the framework. + stack_level = 1 + for s in inspect.stack(): + module = inspect.getmodule(s.frame) + if module is None or not module.__name__.startswith('reframe'): + break + + stack_level += 1 + + warnings.warn(message, ReframeDeprecationWarning, stacklevel=stack_level) diff --git a/reframe/frontend/cli.py b/reframe/frontend/cli.py index 64a148667e..49907596c1 100644 --- a/reframe/frontend/cli.py +++ b/reframe/frontend/cli.py @@ -24,8 +24,9 @@ import reframe.utility.osext as osext from reframe.core.exceptions import ( EnvironError, ConfigError, ReframeError, - ReframeDeprecationWarning, ReframeFatalError, format_exception + ReframeFatalError, format_exception ) +from reframe.core.warnings import ReframeDeprecationWarning from reframe.frontend.executors import Runner, generate_testcases from reframe.frontend.executors.policies import (SerialExecutionPolicy, AsynchronousExecutionPolicy) diff --git a/unittests/resources/checks_unlisted/deprecated_test.py b/unittests/resources/checks_unlisted/deprecated_test.py index 6a95963377..398bff51b1 100644 --- a/unittests/resources/checks_unlisted/deprecated_test.py +++ b/unittests/resources/checks_unlisted/deprecated_test.py @@ -1,6 +1,7 @@ import reframe as rfm import reframe.utility.sanity as sn -from reframe.core.exceptions import user_deprecation_warning + +from reframe.core.warnings import user_deprecation_warning @rfm.simple_test diff --git a/unittests/test_config.py b/unittests/test_config.py index c59c5aacb2..bee766b910 100644 --- a/unittests/test_config.py +++ b/unittests/test_config.py @@ -8,8 +8,9 @@ import pytest import reframe.core.config as config -from reframe.core.exceptions import (ConfigError, ReframeDeprecationWarning) +from reframe.core.exceptions import ConfigError from reframe.core.systems import System +from reframe.core.warnings import ReframeDeprecationWarning def test_load_config_fallback(monkeypatch): diff --git a/unittests/test_fields.py b/unittests/test_fields.py index c79e864f73..5763af784c 100644 --- a/unittests/test_fields.py +++ b/unittests/test_fields.py @@ -8,7 +8,7 @@ import pytest import reframe.core.fields as fields -from reframe.core.exceptions import ReframeDeprecationWarning +from reframe.core.warnings import ReframeDeprecationWarning from reframe.utility import ScopedDict diff --git a/unittests/test_loader.py b/unittests/test_loader.py index f8af8631d7..873e4da77d 100644 --- a/unittests/test_loader.py +++ b/unittests/test_loader.py @@ -8,9 +8,9 @@ import reframe as rfm from reframe.core.exceptions import (ConfigError, NameConflictError, - ReframeDeprecationWarning, RegressionTestLoadError) from reframe.core.systems import System +from reframe.core.warnings import ReframeDeprecationWarning from reframe.frontend.loader import RegressionCheckLoader From 30dfc633a72fe74727706a09a10867eb3b14b49d Mon Sep 17 00:00:00 2001 From: Vasileios Karakasis Date: Fri, 23 Oct 2020 09:29:27 +0200 Subject: [PATCH 2/6] Update CSCS CI script --- ci-scripts/ci-runner.bash | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci-scripts/ci-runner.bash b/ci-scripts/ci-runner.bash index 378b2a2d23..74685b1fa7 100644 --- a/ci-scripts/ci-runner.bash +++ b/ci-scripts/ci-runner.bash @@ -142,7 +142,7 @@ if [ $CI_GENERIC -eq 1 ]; then # Run unit tests for the public release echo "[INFO] Running unit tests with generic settings" checked_exec ./test_reframe.py --workers=auto --forked \ - -W=error::reframe.core.exceptions.ReframeDeprecationWarning -ra + -W=error::reframe.core.warnings.ReframeDeprecationWarning -ra checked_exec ! ./bin/reframe.py --system=generic -l 2>&1 | \ grep -- '--- Logging error ---' elif [ $CI_TUTORIAL -eq 1 ]; then @@ -174,7 +174,7 @@ else echo "[INFO] Running unit tests with ${backend}" TMPDIR=$tempdir checked_exec ./test_reframe.py --workers=auto --forked \ --rfm-user-config=config/cscs-ci.py \ - -W=error::reframe.core.exceptions.ReframeDeprecationWarning \ + -W=error::reframe.core.warnings.ReframeDeprecationWarning \ --rfm-user-system=dom:${backend} -ra done export PATH=$PATH_save @@ -182,7 +182,7 @@ else echo "[INFO] Running unit tests" TMPDIR=$tempdir checked_exec ./test_reframe.py --workers=auto --forked \ --rfm-user-config=config/cscs-ci.py \ - -W=error::reframe.core.exceptions.ReframeDeprecationWarning -ra + -W=error::reframe.core.warnings.ReframeDeprecationWarning -ra fi if [ $CI_EXITCODE -eq 0 ]; then From c3f47fdf01bd0462a9553c0d5f7627a8b342bee2 Mon Sep 17 00:00:00 2001 From: Vasileios Karakasis Date: Fri, 23 Oct 2020 20:14:00 +0200 Subject: [PATCH 3/6] Add unit tests + fix --- reframe/core/warnings.py | 2 +- unittests/test_warnings.py | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 unittests/test_warnings.py diff --git a/reframe/core/warnings.py b/reframe/core/warnings.py index 23b685f883..4bb76a64e8 100644 --- a/reframe/core/warnings.py +++ b/reframe/core/warnings.py @@ -17,7 +17,7 @@ def _format_warning(message, category, filename, lineno, line=None): import reframe.utility.color as color if category != ReframeDeprecationWarning: - return _format_warning_orig + return _format_warning_orig(message, category, filename, lineno, line) if line is None: # Read in the line from the file diff --git a/unittests/test_warnings.py b/unittests/test_warnings.py new file mode 100644 index 0000000000..dc84e96cf5 --- /dev/null +++ b/unittests/test_warnings.py @@ -0,0 +1,51 @@ +import pytest +import warnings + +import reframe.core.runtime as rt +import reframe.core.warnings as warn +import reframe.utility.color as color +import unittests.fixtures as fixtures + + +@pytest.fixture(params=['colors', 'nocolors']) +def with_colors(request): + with rt.temp_runtime(fixtures.BUILTIN_CONFIG_FILE, 'generic', + {'general/colorize': request.param == 'colors'}): + yield request.param == 'colors' + + +def test_deprecation_warning(): + with pytest.warns(warn.ReframeDeprecationWarning): + warn.user_deprecation_warning('deprecated') + + +def test_deprecation_warning_formatting(with_colors): + message = warnings.formatwarning( + 'deprecated', warn.ReframeDeprecationWarning, 'file', 10, 'a = 1' + ) + expected = 'file:10: WARNING: deprecated\na = 1\n' + if with_colors: + expected = color.colorize(expected, color.YELLOW) + + assert message == expected + + +def test_deprecation_warning_formatting_noline(tmp_path, with_colors): + srcfile = tmp_path / 'file' + srcfile.touch() + + message = warnings.formatwarning( + 'deprecated', warn.ReframeDeprecationWarning, srcfile, 10 + ) + expected = f'{srcfile}:10: WARNING: deprecated\n\n' + if with_colors: + expected = color.colorize(expected, color.YELLOW) + + assert message == expected + + +def test_random_warning_formatting(): + message = warnings.formatwarning( + 'deprecated', UserWarning, 'file', 10, 'a = 1' + ) + assert message == f'file:10: UserWarning: deprecated\n a = 1\n' From 78b934522f7135753a4dabaa1ad6b16d1287ec78 Mon Sep 17 00:00:00 2001 From: Vasileios Karakasis Date: Fri, 23 Oct 2020 22:02:26 +0200 Subject: [PATCH 4/6] Fix crash of user_deprecation_warning() without a runtime --- reframe/core/warnings.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/reframe/core/warnings.py b/reframe/core/warnings.py index 4bb76a64e8..4cd898e818 100644 --- a/reframe/core/warnings.py +++ b/reframe/core/warnings.py @@ -1,6 +1,9 @@ +import contextlib import inspect import warnings +from reframe.core.exceptions import ReframeFatalError + class ReframeDeprecationWarning(DeprecationWarning): '''Warning raised for deprecated features of the framework.''' @@ -28,8 +31,12 @@ def _format_warning(message, category, filename, lineno, line=None): line = '' message = f'{filename}:{lineno}: WARNING: {message}\n{line}\n' - if rt.runtime().get_option('general/0/colorize'): - message = color.colorize(message, color.YELLOW) + + # Ignore coloring if runtime has not been initialized; this can happen + # when generating the documentation of deprecated APIs + with contextlib.suppress(ReframeFatalError): + if rt.runtime().get_option('general/0/colorize'): + message = color.colorize(message, color.YELLOW) return message From 6bd1ec949e46b19398dd8f57fd4a57c8d3c4071c Mon Sep 17 00:00:00 2001 From: Vasileios Karakasis Date: Sat, 24 Oct 2020 00:41:50 +0200 Subject: [PATCH 5/6] Use relative paths for filenames in deprecation warnings --- reframe/core/warnings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/reframe/core/warnings.py b/reframe/core/warnings.py index 4cd898e818..8713441d23 100644 --- a/reframe/core/warnings.py +++ b/reframe/core/warnings.py @@ -1,5 +1,6 @@ import contextlib import inspect +import os import warnings from reframe.core.exceptions import ReframeFatalError @@ -30,6 +31,8 @@ def _format_warning(message, category, filename, lineno, line=None): except IndexError: line = '' + # Use a relative path + filename = os.path.relpath(filename) message = f'{filename}:{lineno}: WARNING: {message}\n{line}\n' # Ignore coloring if runtime has not been initialized; this can happen From ed8eaac938cc49ea1d87c8bb500c8570bebbe294 Mon Sep 17 00:00:00 2001 From: Vasileios Karakasis Date: Sat, 24 Oct 2020 10:29:16 +0200 Subject: [PATCH 6/6] Revert "Use relative paths for filenames in deprecation warnings" This reverts commit 6bd1ec949e46b19398dd8f57fd4a57c8d3c4071c. --- reframe/core/warnings.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/reframe/core/warnings.py b/reframe/core/warnings.py index 8713441d23..4cd898e818 100644 --- a/reframe/core/warnings.py +++ b/reframe/core/warnings.py @@ -1,6 +1,5 @@ import contextlib import inspect -import os import warnings from reframe.core.exceptions import ReframeFatalError @@ -31,8 +30,6 @@ def _format_warning(message, category, filename, lineno, line=None): except IndexError: line = '' - # Use a relative path - filename = os.path.relpath(filename) message = f'{filename}:{lineno}: WARNING: {message}\n{line}\n' # Ignore coloring if runtime has not been initialized; this can happen