[Python-checkins] [3.9] bpo-44852: Support ignoring specific DeprecationWarnings wholesale in regrtest (GH-27634) (GH-27785)
ambv
webhook-mailer at python.org
Mon Aug 16 16:47:30 EDT 2021
https://github.com/python/cpython/commit/c7c4cbc58e18ef5a6f4f377b1ece0a84a54335a7
commit: c7c4cbc58e18ef5a6f4f377b1ece0a84a54335a7
branch: 3.9
author: Łukasz Langa <lukasz at langa.pl>
committer: ambv <lukasz at langa.pl>
date: 2021-08-16T22:47:08+02:00
summary:
[3.9] bpo-44852: Support ignoring specific DeprecationWarnings wholesale in regrtest (GH-27634) (GH-27785)
(cherry picked from commit a0a6d39295a30434b088f4b66439bf5ea21a3e4e)
Co-authored-by: Łukasz Langa <lukasz at langa.pl>
files:
A Lib/test/support/warnings_helper.py
A Misc/NEWS.d/next/Tests/2021-08-06-18-36-04.bpo-44852.sUL8YX.rst
M Lib/test/support/__init__.py
M Lib/test/test_support.py
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index ff7db991589c2b..39dea88076e9e1 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -3241,3 +3241,31 @@ def infinite_recursion(max_depth=75):
yield
finally:
sys.setrecursionlimit(original_depth)
+
+def ignore_deprecations_from(module: str, *, like: str) -> object:
+ token = object()
+ warnings.filterwarnings(
+ "ignore",
+ category=DeprecationWarning,
+ module=module,
+ message=like + fr"(?#support{id(token)})",
+ )
+ return token
+
+def clear_ignored_deprecations(*tokens: object) -> None:
+ if not tokens:
+ raise ValueError("Provide token or tokens returned by ignore_deprecations_from")
+
+ new_filters = []
+ for action, message, category, module, lineno in warnings.filters:
+ if action == "ignore" and category is DeprecationWarning:
+ if isinstance(message, re.Pattern):
+ message = message.pattern
+ if tokens:
+ endswith = tuple(rf"(?#support{id(token)})" for token in tokens)
+ if message.endswith(endswith):
+ continue
+ new_filters.append((action, message, category, module, lineno))
+ if warnings.filters != new_filters:
+ warnings.filters[:] = new_filters
+ warnings._filters_mutated()
diff --git a/Lib/test/support/warnings_helper.py b/Lib/test/support/warnings_helper.py
new file mode 100644
index 00000000000000..a024fbe5beaa02
--- /dev/null
+++ b/Lib/test/support/warnings_helper.py
@@ -0,0 +1,199 @@
+import contextlib
+import functools
+import re
+import sys
+import warnings
+
+
+def check_syntax_warning(testcase, statement, errtext='',
+ *, lineno=1, offset=None):
+ # Test also that a warning is emitted only once.
+ from test.support import check_syntax_error
+ with warnings.catch_warnings(record=True) as warns:
+ warnings.simplefilter('always', SyntaxWarning)
+ compile(statement, '<testcase>', 'exec')
+ testcase.assertEqual(len(warns), 1, warns)
+
+ warn, = warns
+ testcase.assertTrue(issubclass(warn.category, SyntaxWarning),
+ warn.category)
+ if errtext:
+ testcase.assertRegex(str(warn.message), errtext)
+ testcase.assertEqual(warn.filename, '<testcase>')
+ testcase.assertIsNotNone(warn.lineno)
+ if lineno is not None:
+ testcase.assertEqual(warn.lineno, lineno)
+
+ # SyntaxWarning should be converted to SyntaxError when raised,
+ # since the latter contains more information and provides better
+ # error report.
+ with warnings.catch_warnings(record=True) as warns:
+ warnings.simplefilter('error', SyntaxWarning)
+ check_syntax_error(testcase, statement, errtext,
+ lineno=lineno, offset=offset)
+ # No warnings are leaked when a SyntaxError is raised.
+ testcase.assertEqual(warns, [])
+
+
+def ignore_warnings(*, category):
+ """Decorator to suppress deprecation warnings.
+
+ Use of context managers to hide warnings make diffs
+ more noisy and tools like 'git blame' less useful.
+ """
+ def decorator(test):
+ @functools.wraps(test)
+ def wrapper(self, *args, **kwargs):
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore', category=category)
+ return test(self, *args, **kwargs)
+ return wrapper
+ return decorator
+
+
+class WarningsRecorder(object):
+ """Convenience wrapper for the warnings list returned on
+ entry to the warnings.catch_warnings() context manager.
+ """
+ def __init__(self, warnings_list):
+ self._warnings = warnings_list
+ self._last = 0
+
+ def __getattr__(self, attr):
+ if len(self._warnings) > self._last:
+ return getattr(self._warnings[-1], attr)
+ elif attr in warnings.WarningMessage._WARNING_DETAILS:
+ return None
+ raise AttributeError("%r has no attribute %r" % (self, attr))
+
+ @property
+ def warnings(self):
+ return self._warnings[self._last:]
+
+ def reset(self):
+ self._last = len(self._warnings)
+
+
+ at contextlib.contextmanager
+def check_warnings(*filters, **kwargs):
+ """Context manager to silence warnings.
+
+ Accept 2-tuples as positional arguments:
+ ("message regexp", WarningCategory)
+
+ Optional argument:
+ - if 'quiet' is True, it does not fail if a filter catches nothing
+ (default True without argument,
+ default False if some filters are defined)
+
+ Without argument, it defaults to:
+ check_warnings(("", Warning), quiet=True)
+ """
+ quiet = kwargs.get('quiet')
+ if not filters:
+ filters = (("", Warning),)
+ # Preserve backward compatibility
+ if quiet is None:
+ quiet = True
+ return _filterwarnings(filters, quiet)
+
+
+ at contextlib.contextmanager
+def check_no_warnings(testcase, message='', category=Warning, force_gc=False):
+ """Context manager to check that no warnings are emitted.
+
+ This context manager enables a given warning within its scope
+ and checks that no warnings are emitted even with that warning
+ enabled.
+
+ If force_gc is True, a garbage collection is attempted before checking
+ for warnings. This may help to catch warnings emitted when objects
+ are deleted, such as ResourceWarning.
+
+ Other keyword arguments are passed to warnings.filterwarnings().
+ """
+ from test.support import gc_collect
+ with warnings.catch_warnings(record=True) as warns:
+ warnings.filterwarnings('always',
+ message=message,
+ category=category)
+ yield
+ if force_gc:
+ gc_collect()
+ testcase.assertEqual(warns, [])
+
+
+ at contextlib.contextmanager
+def check_no_resource_warning(testcase):
+ """Context manager to check that no ResourceWarning is emitted.
+
+ Usage:
+
+ with check_no_resource_warning(self):
+ f = open(...)
+ ...
+ del f
+
+ You must remove the object which may emit ResourceWarning before
+ the end of the context manager.
+ """
+ with check_no_warnings(testcase, category=ResourceWarning, force_gc=True):
+ yield
+
+
+def _filterwarnings(filters, quiet=False):
+ """Catch the warnings, then check if all the expected
+ warnings have been raised and re-raise unexpected warnings.
+ If 'quiet' is True, only re-raise the unexpected warnings.
+ """
+ # Clear the warning registry of the calling module
+ # in order to re-raise the warnings.
+ frame = sys._getframe(2)
+ registry = frame.f_globals.get('__warningregistry__')
+ if registry:
+ registry.clear()
+ with warnings.catch_warnings(record=True) as w:
+ # Set filter "always" to record all warnings. Because
+ # test_warnings swap the module, we need to look up in
+ # the sys.modules dictionary.
+ sys.modules['warnings'].simplefilter("always")
+ yield WarningsRecorder(w)
+ # Filter the recorded warnings
+ reraise = list(w)
+ missing = []
+ for msg, cat in filters:
+ seen = False
+ for w in reraise[:]:
+ warning = w.message
+ # Filter out the matching messages
+ if (re.match(msg, str(warning), re.I) and
+ issubclass(warning.__class__, cat)):
+ seen = True
+ reraise.remove(w)
+ if not seen and not quiet:
+ # This filter caught nothing
+ missing.append((msg, cat.__name__))
+ if reraise:
+ raise AssertionError("unhandled warning %s" % reraise[0])
+ if missing:
+ raise AssertionError("filter (%r, %s) did not catch any warning" %
+ missing[0])
+
+
+ at contextlib.contextmanager
+def save_restore_warnings_filters():
+ old_filters = warnings.filters[:]
+ try:
+ yield
+ finally:
+ warnings.filters[:] = old_filters
+
+
+def _warn_about_deprecation():
+ warnings.warn(
+ "This is used in test_support test to ensure"
+ " support.ignore_deprecations_from() works as expected."
+ " You should not be seeing this.",
+ DeprecationWarning,
+ stacklevel=0,
+ )
diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
index b5a16f9cb60272..60a7741194f2fc 100644
--- a/Lib/test/test_support.py
+++ b/Lib/test/test_support.py
@@ -11,6 +11,8 @@
import textwrap
import time
import unittest
+import warnings
+
from test import support
from test.support import script_helper
from test.support import socket_helper
@@ -19,6 +21,33 @@
class TestSupport(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ orig_filter_len = len(warnings.filters)
+ cls._warnings_helper_token = support.ignore_deprecations_from(
+ "test.test_support", like=".*used in test_support.*"
+ )
+ cls._test_support_token = support.ignore_deprecations_from(
+ "test.test_support", like=".*You should NOT be seeing this.*"
+ )
+ assert len(warnings.filters) == orig_filter_len + 2
+
+ @classmethod
+ def tearDownClass(cls):
+ orig_filter_len = len(warnings.filters)
+ support.clear_ignored_deprecations(
+ cls._warnings_helper_token,
+ cls._test_support_token,
+ )
+ assert len(warnings.filters) == orig_filter_len - 2
+
+ def test_ignored_deprecations_are_silent(self):
+ """Test support.ignore_deprecations_from() silences warnings"""
+ with warnings.catch_warnings(record=True) as warning_objs:
+ _warn_about_deprecation()
+ warnings.warn("You should NOT be seeing this.", DeprecationWarning)
+ messages = [str(w.message) for w in warning_objs]
+ self.assertEqual(len(messages), 0, messages)
def test_import_module(self):
support.import_module("ftplib")
@@ -676,6 +705,17 @@ def test_print_warning(self):
# SuppressCrashReport
+def _warn_about_deprecation():
+ # In 3.10+ this lives in test.support.warnings_helper
+ warnings.warn(
+ "This is used in test_support test to ensure"
+ " support.ignore_deprecations_from() works as expected."
+ " You should not be seeing this.",
+ DeprecationWarning,
+ stacklevel=0,
+ )
+
+
def test_main():
tests = [TestSupport]
support.run_unittest(*tests)
diff --git a/Misc/NEWS.d/next/Tests/2021-08-06-18-36-04.bpo-44852.sUL8YX.rst b/Misc/NEWS.d/next/Tests/2021-08-06-18-36-04.bpo-44852.sUL8YX.rst
new file mode 100644
index 00000000000000..41b5c2fb51c5c7
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2021-08-06-18-36-04.bpo-44852.sUL8YX.rst
@@ -0,0 +1,2 @@
+Add ability to wholesale silence DeprecationWarnings while running the
+regression test suite.
More information about the Python-checkins
mailing list