diff options
-rw-r--r-- | Doc/library/test.rst | 64 | ||||
-rw-r--r-- | Lib/_pyio.py | 15 | ||||
-rw-r--r-- | Lib/test/support.py | 72 | ||||
-rw-r--r-- | Lib/test/test_fileio.py | 2 | ||||
-rw-r--r-- | Misc/NEWS | 3 |
5 files changed, 122 insertions, 34 deletions
diff --git a/Doc/library/test.rst b/Doc/library/test.rst index cc7ff4d..d8dd46c 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -130,13 +130,13 @@ guidelines to be followed: self.func(self.arg) class AcceptLists(TestFuncAcceptsSequences): - arg = [1,2,3] + arg = [1, 2, 3] class AcceptStrings(TestFuncAcceptsSequences): arg = 'abc' class AcceptTuples(TestFuncAcceptsSequences): - arg = (1,2,3) + arg = (1, 2, 3) .. seealso:: @@ -198,16 +198,9 @@ This module defines the following exceptions: methods. -.. exception:: TestSkipped - - Subclass of :exc:`TestFailed`. Raised when a test is skipped. This occurs when a - needed resource (such as a network connection) is not available at the time of - testing. - - .. exception:: ResourceDenied - Subclass of :exc:`TestSkipped`. Raised when a resource (such as a network + Subclass of :exc:`unittest.SkipTest`. Raised when a resource (such as a network connection) is not available. Raised by the :func:`requires` function. The :mod:`test.support` module defines the following constants: @@ -227,7 +220,7 @@ The :mod:`test.support` module defines the following constants: .. data:: TESTFN - Set to the path that a temporary file may be created at. Any temporary that is + Set to the name that a temporary file could use. Any temporary file that is created should be closed and unlinked (removed). The :mod:`test.support` module defines the following functions: @@ -235,21 +228,21 @@ The :mod:`test.support` module defines the following functions: .. function:: forget(module_name) - Removes the module named *module_name* from ``sys.modules`` and deletes any + Remove the module named *module_name* from ``sys.modules`` and deletes any byte-compiled files of the module. .. function:: is_resource_enabled(resource) - Returns :const:`True` if *resource* is enabled and available. The list of + Return :const:`True` if *resource* is enabled and available. The list of available resources is only set when :mod:`test.regrtest` is executing the tests. .. function:: requires(resource, msg=None) - Raises :exc:`ResourceDenied` if *resource* is not available. *msg* is the - argument to :exc:`ResourceDenied` if it is raised. Always returns true if called + Raise :exc:`ResourceDenied` if *resource* is not available. *msg* is the + argument to :exc:`ResourceDenied` if it is raised. Always returns True if called by a function whose ``__name__`` is ``'__main__'``. Used when tests are executed by :mod:`test.regrtest`. @@ -277,14 +270,24 @@ The :mod:`test.support` module defines the following functions: This will run all tests defined in the named module. -.. function:: check_warnings() +.. function:: check_warnings(*filters, quiet=False) A convenience wrapper for ``warnings.catch_warnings()`` that makes it easier to test that a warning was correctly raised with a single assertion. It is approximately equivalent to calling ``warnings.catch_warnings(record=True)``. - The main difference is that on entry to the context manager, a + It accepts 2-tuples ``("message regexp", WarningCategory)`` as positional + arguments. When the optional keyword argument ``quiet`` is True, it does + not fail if a filter catches nothing. Without argument, it defaults to:: + + check_warnings(("", Warning), quiet=False) + + The main difference is that it verifies the warnings raised. If some filter + did not catch any warning, the test fails. If some warnings are not caught, + the test fails, too. To disable these checks, use argument ``quiet=True``. + + Another significant difference is that on entry to the context manager, a :class:`WarningRecorder` instance is returned instead of a simple list. The underlying warnings list is available via the recorder object's :attr:`warnings` attribute, while the attributes of the last raised @@ -294,19 +297,34 @@ The :mod:`test.support` module defines the following functions: A :meth:`reset` method is also provided on the recorder object. This method simply clears the warning list. - The context manager is used like this:: + The context manager may be used like this:: + + import warnings + + with check_warnings(): + exec('assert(False, "Hey!")') + warnings.warn(UserWarning("Hide me!")) - with check_warnings() as w: + with check_warnings(("assertion is always true", SyntaxWarning), + ("", UserWarning)): + exec('assert(False, "Hey!")') + warnings.warn(UserWarning("Hide me!")) + + with check_warnings(quiet=True) as w: warnings.simplefilter("always") warnings.warn("foo") - assert str(w.message) == "foo" + assert str(w.args[0]) == "foo" warnings.warn("bar") - assert str(w.message) == "bar" - assert str(w.warnings[0].message) == "foo" - assert str(w.warnings[1].message) == "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 + .. versionchanged:: 2.7 + The test fails when the context manager do not catch any warning. + New optional attributes ``*filters`` and ``quiet``. + .. function:: captured_stdout() diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 8960ce9..c58548e 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -828,7 +828,7 @@ class BytesIO(BufferedIOBase): if self.closed: raise ValueError("seek on closed file") try: - pos = pos.__index__() + pos.__index__ except AttributeError as err: raise TypeError("an integer is required") from err if whence == 0: @@ -853,8 +853,13 @@ class BytesIO(BufferedIOBase): raise ValueError("truncate on closed file") if pos is None: pos = self._pos - elif pos < 0: - raise ValueError("negative truncate position %r" % (pos,)) + else: + try: + pos.__index__ + except AttributeError as err: + raise TypeError("an integer is required") from err + if pos < 0: + raise ValueError("negative truncate position %r" % (pos,)) del self._buffer[pos:] return pos @@ -1803,6 +1808,10 @@ class TextIOWrapper(TextIOBase): if n is None: n = -1 decoder = self._decoder or self._get_decoder() + try: + n.__index__ + except AttributeError as err: + raise TypeError("an integer is required") from err if n < 0: # Read everything. result = (self._get_decoded_chars() + diff --git a/Lib/test/support.py b/Lib/test/support.py index bf49bab..769f94a 100644 --- a/Lib/test/support.py +++ b/Lib/test/support.py @@ -16,6 +16,7 @@ import warnings import unittest import importlib import collections +import re __all__ = ["Error", "TestFailed", "ResourceDenied", "import_module", "verbose", "use_resources", "max_memuse", "record_original_stdout", @@ -464,22 +465,80 @@ class WarningsRecorder(object): entry to the warnings.catch_warnings() context manager. """ def __init__(self, warnings_list): - self.warnings = warnings_list + self._warnings = warnings_list + self._last = 0 def __getattr__(self, attr): - if self.warnings: - return getattr(self.warnings[-1], 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): - del self.warnings[:] + self._last = len(self._warnings) -@contextlib.contextmanager -def check_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 = [warning.message for warning in w] + missing = [] + for msg, cat in filters: + seen = False + for exc in reraise[:]: + message = str(exc) + # Filter out the matching messages + if (re.match(msg, message, re.I) and + issubclass(exc.__class__, cat)): + seen = True + reraise.remove(exc) + if not seen and not quiet: + # This filter caught nothing + missing.append((msg, cat.__name__)) + if reraise: + raise AssertionError("unhandled warning %r" % reraise[0]) + if missing: + raise AssertionError("filter (%r, %s) did not catch any warning" % + missing[0]) + + +@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 False) + + Without argument, it defaults to: + check_warnings(("", Warning), quiet=False) + """ + if not filters: + filters = (("", Warning),) + return _filterwarnings(filters, kwargs.get('quiet')) class CleanImport(object): @@ -714,7 +773,6 @@ _4G = 4 * _1G MAX_Py_ssize_t = sys.maxsize def set_memlimit(limit): - import re global max_memuse global real_max_memuse sizes = { diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py index 8348f6e..fd27512 100644 --- a/Lib/test/test_fileio.py +++ b/Lib/test/test_fileio.py @@ -390,7 +390,7 @@ class OtherFileTests(unittest.TestCase): self.assertRaises(TypeError, _FileIO, "1", 0, 0) def testWarnings(self): - with check_warnings() as w: + with check_warnings(quiet=True) as w: self.assertEqual(w.warnings, []) self.assertRaises(TypeError, _FileIO, []) self.assertEqual(w.warnings, []) @@ -869,6 +869,9 @@ Documentation Tests ----- +- Issue #7849: Now the utility ``check_warnings`` verifies if the warnings are + effectively raised. + - The four path modules (genericpath, macpath, ntpath, posixpath) share a common TestCase for some tests: test_genericpath.CommonTest. |