[Python-checkins] bpo-40275: Add warnings_helper submodule in test.support (GH-20797)
Hai Shi
webhook-mailer at python.org
Thu Jun 11 11:36:11 EDT 2020
https://github.com/python/cpython/commit/10e6506aa8261aacc89b49e629ae1c927fa5151c
commit: 10e6506aa8261aacc89b49e629ae1c927fa5151c
branch: master
author: Hai Shi <shihai1992 at gmail.com>
committer: GitHub <noreply at github.com>
date: 2020-06-11T17:36:06+02:00
summary:
bpo-40275: Add warnings_helper submodule in test.support (GH-20797)
files:
A Lib/test/support/warnings_helper.py
M Doc/library/test.rst
M Lib/test/support/__init__.py
diff --git a/Doc/library/test.rst b/Doc/library/test.rst
index a18197aed3f4a..843201885ad24 100644
--- a/Doc/library/test.rst
+++ b/Doc/library/test.rst
@@ -497,79 +497,6 @@ The :mod:`test.support` module defines the following functions:
check_impl_detail(cpython=False) # Everywhere except CPython.
-.. function:: check_warnings(\*filters, quiet=True)
-
- A convenience wrapper for :func:`warnings.catch_warnings()` that makes it
- easier to test that a warning was correctly raised. It is approximately
- equivalent to calling ``warnings.catch_warnings(record=True)`` with
- :meth:`warnings.simplefilter` set to ``always`` and with the option to
- automatically validate the results that are recorded.
-
- ``check_warnings`` accepts 2-tuples of the form ``("message regexp",
- WarningCategory)`` as positional arguments. If one or more *filters* are
- provided, or if the optional keyword argument *quiet* is ``False``,
- it checks to make sure the warnings are as expected: each specified filter
- must match at least one of the warnings raised by the enclosed code or the
- test fails, and if any warnings are raised that do not match any of the
- specified filters the test fails. To disable the first of these checks,
- set *quiet* to ``True``.
-
- If no arguments are specified, it defaults to::
-
- check_warnings(("", Warning), quiet=True)
-
- In this case all warnings are caught and no errors are raised.
-
- On entry to the context manager, a :class:`WarningRecorder` instance is
- returned. The underlying warnings list from
- :func:`~warnings.catch_warnings` is available via the recorder object's
- :attr:`warnings` attribute. As a convenience, the attributes of the object
- representing the most recent warning can also be accessed directly through
- the recorder object (see example below). If no warning has been raised,
- then any of the attributes that would otherwise be expected on an object
- representing a warning will return ``None``.
-
- The recorder object also has a :meth:`reset` method, which clears the
- warnings list.
-
- The context manager is designed to be used like this::
-
- with check_warnings(("assertion is always true", SyntaxWarning),
- ("", UserWarning)):
- exec('assert(False, "Hey!")')
- warnings.warn(UserWarning("Hide me!"))
-
- In this case if either warning was not raised, or some other warning was
- raised, :func:`check_warnings` would raise an error.
-
- When a test needs to look more deeply into the warnings, rather than
- just checking whether or not they occurred, code like this can be used::
-
- with check_warnings(quiet=True) as w:
- warnings.warn("foo")
- assert str(w.args[0]) == "foo"
- warnings.warn("bar")
- assert str(w.args[0]) == "bar"
- assert str(w.warnings[0].args[0]) == "foo"
- assert str(w.warnings[1].args[0]) == "bar"
- w.reset()
- assert len(w.warnings) == 0
-
-
- Here all warnings will be caught, and the test code tests the captured
- warnings directly.
-
- .. versionchanged:: 3.2
- New optional arguments *filters* and *quiet*.
-
-
-.. function:: check_no_resource_warning(testcase)
-
- Context manager to check that no :exc:`ResourceWarning` was raised. You
- must remove the object which may emit :exc:`ResourceWarning` before the
- end of the context manager.
-
-
.. function:: set_memlimit(limit)
Set the values for :data:`max_memuse` and :data:`real_max_memuse` for big
@@ -851,20 +778,6 @@ The :mod:`test.support` module defines the following functions:
the offset of the exception.
-.. function:: check_syntax_warning(testcase, statement, errtext='', *, lineno=1, offset=None)
-
- Test for syntax warning in *statement* by attempting to compile *statement*.
- Test also that the :exc:`SyntaxWarning` is emitted only once, and that it
- will be converted to a :exc:`SyntaxError` when turned into error.
- *testcase* is the :mod:`unittest` instance for the test. *errtext* is the
- regular expression which should match the string representation of the
- emitted :exc:`SyntaxWarning` and raised :exc:`SyntaxError`. If *lineno*
- is not ``None``, compares to the line of the warning and exception.
- If *offset* is not ``None``, compares to the offset of the exception.
-
- .. versionadded:: 3.8
-
-
.. function:: open_urlresource(url, *args, **kw)
Open *url*. If open fails, raises :exc:`TestFailed`.
@@ -1051,12 +964,6 @@ The :mod:`test.support` module defines the following classes:
Try to match a single stored value (*dv*) with a supplied value (*v*).
-.. class:: WarningsRecorder()
-
- Class used to record warnings for unit tests. See documentation of
- :func:`check_warnings` above for more details.
-
-
.. class:: BasicTestRunner()
.. method:: run(test)
@@ -1659,3 +1566,105 @@ The :mod:`test.support.import_helper` module provides support for import tests.
will be reverted at the end of the block.
+:mod:`test.support.warnings_helper` --- Utilities for warnings tests
+====================================================================
+
+.. module:: test.support.warnings_helper
+ :synopsis: Support for warnings tests.
+
+The :mod:`test.support.warnings_helper` module provides support for warnings tests.
+
+.. versionadded:: 3.10
+
+
+.. function:: check_no_resource_warning(testcase)
+
+ Context manager to check that no :exc:`ResourceWarning` was raised. You
+ must remove the object which may emit :exc:`ResourceWarning` before the
+ end of the context manager.
+
+
+.. function:: check_syntax_warning(testcase, statement, errtext='', *, lineno=1, offset=None)
+
+ Test for syntax warning in *statement* by attempting to compile *statement*.
+ Test also that the :exc:`SyntaxWarning` is emitted only once, and that it
+ will be converted to a :exc:`SyntaxError` when turned into error.
+ *testcase* is the :mod:`unittest` instance for the test. *errtext* is the
+ regular expression which should match the string representation of the
+ emitted :exc:`SyntaxWarning` and raised :exc:`SyntaxError`. If *lineno*
+ is not ``None``, compares to the line of the warning and exception.
+ If *offset* is not ``None``, compares to the offset of the exception.
+
+ .. versionadded:: 3.8
+
+
+.. function:: check_warnings(\*filters, quiet=True)
+
+ A convenience wrapper for :func:`warnings.catch_warnings()` that makes it
+ easier to test that a warning was correctly raised. It is approximately
+ equivalent to calling ``warnings.catch_warnings(record=True)`` with
+ :meth:`warnings.simplefilter` set to ``always`` and with the option to
+ automatically validate the results that are recorded.
+
+ ``check_warnings`` accepts 2-tuples of the form ``("message regexp",
+ WarningCategory)`` as positional arguments. If one or more *filters* are
+ provided, or if the optional keyword argument *quiet* is ``False``,
+ it checks to make sure the warnings are as expected: each specified filter
+ must match at least one of the warnings raised by the enclosed code or the
+ test fails, and if any warnings are raised that do not match any of the
+ specified filters the test fails. To disable the first of these checks,
+ set *quiet* to ``True``.
+
+ If no arguments are specified, it defaults to::
+
+ check_warnings(("", Warning), quiet=True)
+
+ In this case all warnings are caught and no errors are raised.
+
+ On entry to the context manager, a :class:`WarningRecorder` instance is
+ returned. The underlying warnings list from
+ :func:`~warnings.catch_warnings` is available via the recorder object's
+ :attr:`warnings` attribute. As a convenience, the attributes of the object
+ representing the most recent warning can also be accessed directly through
+ the recorder object (see example below). If no warning has been raised,
+ then any of the attributes that would otherwise be expected on an object
+ representing a warning will return ``None``.
+
+ The recorder object also has a :meth:`reset` method, which clears the
+ warnings list.
+
+ The context manager is designed to be used like this::
+
+ with check_warnings(("assertion is always true", SyntaxWarning),
+ ("", UserWarning)):
+ exec('assert(False, "Hey!")')
+ warnings.warn(UserWarning("Hide me!"))
+
+ In this case if either warning was not raised, or some other warning was
+ raised, :func:`check_warnings` would raise an error.
+
+ When a test needs to look more deeply into the warnings, rather than
+ just checking whether or not they occurred, code like this can be used::
+
+ with check_warnings(quiet=True) as w:
+ warnings.warn("foo")
+ assert str(w.args[0]) == "foo"
+ warnings.warn("bar")
+ assert str(w.args[0]) == "bar"
+ assert str(w.warnings[0].args[0]) == "foo"
+ assert str(w.warnings[1].args[0]) == "bar"
+ w.reset()
+ assert len(w.warnings) == 0
+
+
+ Here all warnings will be caught, and the test code tests the captured
+ warnings directly.
+
+ .. versionchanged:: 3.2
+ New optional arguments *filters* and *quiet*.
+
+
+.. class:: WarningsRecorder()
+
+ Class used to record warnings for unit tests. See documentation of
+ :func:`check_warnings` above for more details.
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 1ac65533a7b54..fa54ebe5620f4 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -14,7 +14,6 @@
import time
import types
import unittest
-import warnings
from .import_helper import (
CleanImport, DirsOnSysPath, _ignore_deprecated_imports,
@@ -30,6 +29,10 @@
rmtree, skip_unless_symlink, skip_unless_xattr,
temp_cwd, temp_dir, temp_umask, unlink,
EnvironmentVarGuard, FakePath, _longpath)
+from .warnings_helper import (
+ WarningsRecorder, _filterwarnings,
+ check_no_resource_warning, check_no_warnings,
+ check_syntax_warning, check_warnings, ignore_warnings)
from .testresult import get_test_runner
@@ -45,7 +48,7 @@
# unittest
"is_resource_enabled", "requires", "requires_freebsd_version",
"requires_linux_version", "requires_mac_ver",
- "check_syntax_error", "check_syntax_warning",
+ "check_syntax_error",
"TransientResource", "time_out", "socket_peer_reset", "ioerror_peer_reset",
"BasicTestRunner", "run_unittest", "run_doctest",
"requires_gzip", "requires_bz2", "requires_lzma",
@@ -53,7 +56,6 @@
"requires_IEEE_754", "requires_zlib",
"anticipate_failure", "load_package_tests", "detect_api_mismatch",
"check__all__", "skip_if_buggy_ucrt_strfptime",
- "ignore_warnings",
# sys
"is_jython", "is_android", "check_impl_detail", "unix_shell",
"setswitchinterval",
@@ -62,7 +64,6 @@
# processes
"reap_children",
# miscellaneous
- "check_warnings", "check_no_resource_warning", "check_no_warnings",
"run_with_locale", "swap_item", "findfile",
"swap_attr", "Matcher", "set_memlimit", "SuppressCrashReport", "sortdict",
"run_with_tz", "PGO", "missing_compiler_executable",
@@ -128,22 +129,6 @@ class ResourceDenied(unittest.SkipTest):
and unexpected skips.
"""
-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
-
-
def anticipate_failure(condition):
"""Decorator to mark a test that is known to be broken in some cases
@@ -511,32 +496,6 @@ def check_syntax_error(testcase, statement, errtext='', *, lineno=None, offset=N
if offset is not None:
testcase.assertEqual(err.offset, offset)
-def check_syntax_warning(testcase, statement, errtext='', *, lineno=1, offset=None):
- # Test also that a warning is emitted only once.
- 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 open_urlresource(url, *args, **kw):
import urllib.request, urllib.parse
@@ -592,134 +551,6 @@ def check_valid_file(fn):
raise TestFailed('invalid resource %r' % fn)
-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)
-
-
-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 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().
- """
- 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
-
-
class TransientResource(object):
"""Raise ResourceDenied if an exception is raised while the context manager
@@ -978,6 +809,7 @@ def __init__(self):
self.started = False
def start(self):
+ import warnings
try:
f = open(self.procfile, 'r')
except OSError as e:
diff --git a/Lib/test/support/warnings_helper.py b/Lib/test/support/warnings_helper.py
new file mode 100644
index 0000000000000..c9f9045405b80
--- /dev/null
+++ b/Lib/test/support/warnings_helper.py
@@ -0,0 +1,180 @@
+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])
More information about the Python-checkins
mailing list