mirror of
https://github.com/python/cpython.git
synced 2025-08-22 09:45:06 +00:00
[3.9] bpo-44852: Support ignoring specific DeprecationWarnings wholesale in regrtest (GH-27634) (GH-27785)
(cherry picked from commit a0a6d39295
)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
43bab0537c
commit
c7c4cbc58e
4 changed files with 269 additions and 0 deletions
|
@ -3241,3 +3241,31 @@ def infinite_recursion(max_depth=75):
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
sys.setrecursionlimit(original_depth)
|
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()
|
||||||
|
|
199
Lib/test/support/warnings_helper.py
Normal file
199
Lib/test/support/warnings_helper.py
Normal file
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
|
||||||
|
@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, [])
|
||||||
|
|
||||||
|
|
||||||
|
@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])
|
||||||
|
|
||||||
|
|
||||||
|
@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,
|
||||||
|
)
|
|
@ -11,6 +11,8 @@ import tempfile
|
||||||
import textwrap
|
import textwrap
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
import warnings
|
||||||
|
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import script_helper
|
from test.support import script_helper
|
||||||
from test.support import socket_helper
|
from test.support import socket_helper
|
||||||
|
@ -19,6 +21,33 @@ TESTFN = support.TESTFN
|
||||||
|
|
||||||
|
|
||||||
class TestSupport(unittest.TestCase):
|
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):
|
def test_import_module(self):
|
||||||
support.import_module("ftplib")
|
support.import_module("ftplib")
|
||||||
|
@ -676,6 +705,17 @@ class TestSupport(unittest.TestCase):
|
||||||
# SuppressCrashReport
|
# 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():
|
def test_main():
|
||||||
tests = [TestSupport]
|
tests = [TestSupport]
|
||||||
support.run_unittest(*tests)
|
support.run_unittest(*tests)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add ability to wholesale silence DeprecationWarnings while running the
|
||||||
|
regression test suite.
|
Loading…
Add table
Add a link
Reference in a new issue