Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Doc/library/unittest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,13 @@ Test cases
self.assertIn('myfile.py', cm.filename)
self.assertEqual(320, cm.lineno)

The context managers can be nested to test that multiple different
warnings are emitted::

with (self.assertWarns(SomeWarning),
self.assertWarns(OtherWarning)):
do_something()

This method works regardless of the warning filters in place when it
is called.

Expand All @@ -1103,6 +1110,10 @@ Test cases
.. versionchanged:: 3.3
Added the *msg* keyword argument when used as a context manager.

.. versionchanged:: next
Warnings that do not match the specified category are no longer
swallowed.
Nested context managers are now supported.

.. method:: assertWarnsRegex(warning, regex, callable, *args, **kwds)
assertWarnsRegex(warning, regex, *, msg=None)
Expand All @@ -1121,11 +1132,23 @@ Test cases
with self.assertWarnsRegex(RuntimeWarning, 'unsafe frobnicating'):
frobnicate('/etc/passwd')

The context managers can be nested to test that multiple different
warnings are emitted::

with (self.assertWarns(SomeWarning, regex1),
self.assertWarns(OtherWarning, regex2)):
do_something()

.. versionadded:: 3.2

.. versionchanged:: 3.3
Added the *msg* keyword argument when used as a context manager.

.. versionchanged:: next
Warnings that do not match the specified category or regex are
no longer swallowed.
Nested context managers are now supported.

.. method:: assertLogs(logger=None, level=None, formatter=None)

A context manager to test that at least one message is logged on
Expand Down
16 changes: 15 additions & 1 deletion Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1471,10 +1471,16 @@ unicodedata
unittest
--------

* :func:`unittest.TestCase.assertLogs` will now accept a formatter
* :meth:`unittest.TestCase.assertLogs` will now accept a formatter
to control how messages are formatted.
(Contributed by Garry Cairns in :gh:`134567`.)

* :meth:`unittest.TestCase.assertWarns` and
:meth:`unittest.TestCase.assertWarnsRegex` no longer swallow warnings that
do not match the specified category or regex.
Nested context managers are now supported.
(Contributed by Serhiy Storchaka in :gh:`143231`.)


urllib.parse
------------
Expand Down Expand Up @@ -2352,3 +2358,11 @@ that may require changes to your code.
with argument ``altchars=b'-_'`` (this works with older Python versions)
to make padding required.
(Contributed by Serhiy Storchaka in :gh:`73613`.)

* Since :meth:`unittest.TestCase.assertWarns` and
:meth:`unittest.TestCase.assertWarnsRegex` no longer swallow warnings that
do not match the specified category or regex, your tests may start leaking
some warnings that were previously masked.
Use warning filters to silence them or additional :meth:`!assertWarns*`
to catch and check them.
(Contributed by Serhiy Storchaka in :gh:`143231`.)
24 changes: 16 additions & 8 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,17 +504,25 @@ def check(z, x, y):
with self.assertWarnsRegex(DeprecationWarning,
"argument 'imag' must be a real number, not complex"):
check(complex(0.0, 4.25j), -4.25, 0.0)
with self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"):
with (self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"),
self.assertWarnsRegex(DeprecationWarning,
"argument 'imag' must be a real number, not complex")):
check(complex(4.25+0j, 0j), 4.25, 0.0)
with self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"):
with (self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"),
self.assertWarnsRegex(DeprecationWarning,
"argument 'imag' must be a real number, not complex")):
check(complex(4.25j, 0j), 0.0, 4.25)
with self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"):
with (self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"),
self.assertWarnsRegex(DeprecationWarning,
"argument 'imag' must be a real number, not complex")):
check(complex(0j, 4.25+0j), 0.0, 4.25)
with self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"):
with (self.assertWarnsRegex(DeprecationWarning,
"argument 'real' must be a real number, not complex"),
self.assertWarnsRegex(DeprecationWarning,
"argument 'imag' must be a real number, not complex")):
check(complex(0j, 4.25j), -4.25, 0.0)

check(complex(real=4.25), 4.25, 0.0)
Expand Down
11 changes: 6 additions & 5 deletions Lib/test/test_unittest/test_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,12 @@ def testAssertWarnsRegex(self):
# test warning raised but with wrong message
def raise_wrong_message():
warnings.warn('foo')
self.assertMessagesCM('assertWarnsRegex', (UserWarning, 'regex'),
raise_wrong_message,
['^"regex" does not match "foo"$', '^oops$',
'^"regex" does not match "foo"$',
'^"regex" does not match "foo" : oops$'])
with self.assertWarnsRegex(UserWarning, 'foo'):
self.assertMessagesCM('assertWarnsRegex', (UserWarning, 'regex'),
raise_wrong_message,
['^"regex" does not match "foo"$', '^oops$',
'^"regex" does not match "foo"$',
'^"regex" does not match "foo" : oops$'])


if __name__ == "__main__":
Expand Down
128 changes: 109 additions & 19 deletions Lib/test/test_unittest/test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -1631,11 +1631,11 @@ def testAssertRaisesRegexNoExceptionType(self):
self.assertRaisesRegex((ValueError, object), 'expect')

def testAssertWarnsCallable(self):
def _runtime_warn():
warnings.warn("foo", RuntimeWarning)
def _runtime_warn(categories=(RuntimeWarning,)):
for category in categories:
warnings.warn("foo", category)
# Success when the right warning is triggered, even several times
self.assertWarns(RuntimeWarning, _runtime_warn)
self.assertWarns(RuntimeWarning, _runtime_warn)
self.assertWarns(RuntimeWarning, _runtime_warn, (RuntimeWarning, RuntimeWarning))
Comment thread
bitdancer marked this conversation as resolved.
# A tuple of warning classes is accepted
self.assertWarns((DeprecationWarning, RuntimeWarning), _runtime_warn)
# *args and **kwargs also work
Expand All @@ -1648,22 +1648,35 @@ def _runtime_warn():
with self.assertRaises(TypeError):
self.assertWarns(RuntimeWarning, None)
# Failure when another warning is triggered
with warnings.catch_warnings():
with warnings.catch_warnings(record=True) as log:
Comment thread
bitdancer marked this conversation as resolved.
# Force default filter (in case tests are run with -We)
warnings.simplefilter("default", RuntimeWarning)
with self.assertRaises(self.failureException):
self.assertWarns(DeprecationWarning, _runtime_warn)
self.assertWarns(DeprecationWarning, _runtime_warn,
(RuntimeWarning, RuntimeWarning))
self.assertEqual(len(log), 1, log)
self.assertIsInstance(log[0].message, RuntimeWarning)
# Filters for other warnings are not modified
with warnings.catch_warnings():
warnings.simplefilter("error", RuntimeWarning)
with self.assertRaises(RuntimeWarning):
self.assertWarns(DeprecationWarning, _runtime_warn)
# Warnings that do not match the category are not swallowed.
with self.assertWarns(RuntimeWarning):
with self.assertRaises(self.failureException):
self.assertWarns(DeprecationWarning, _runtime_warn)
with self.assertWarns(RuntimeWarning):
self.assertWarns(DeprecationWarning, _runtime_warn,
(RuntimeWarning, DeprecationWarning))
with self.assertWarns(RuntimeWarning):
self.assertWarns(DeprecationWarning, _runtime_warn,
(DeprecationWarning, RuntimeWarning))

def testAssertWarnsContext(self):
# Believe it or not, it is preferable to duplicate all tests above,
# to make sure the __warningregistry__ $@ is circumvented correctly.
def _runtime_warn():
warnings.warn("foo", RuntimeWarning)
def _runtime_warn(category=RuntimeWarning):
warnings.warn("foo", category)
_runtime_warn_lineno = inspect.getsourcelines(_runtime_warn)[1]
with self.assertWarns(RuntimeWarning) as cm:
_runtime_warn()
Expand Down Expand Up @@ -1694,18 +1707,58 @@ def _runtime_warn():
with self.assertWarns(RuntimeWarning, foobar=42):
pass
# Failure when another warning is triggered
with warnings.catch_warnings():
with warnings.catch_warnings(record=True) as log:
# Force default filter (in case tests are run with -We)
warnings.simplefilter("default", RuntimeWarning)
with self.assertRaises(self.failureException):
with self.assertWarns(DeprecationWarning):
_runtime_warn()
_runtime_warn()
self.assertEqual(len(log), 1, log)
self.assertIsInstance(log[0].message, RuntimeWarning)
with warnings.catch_warnings(record=True) as log:
# Force default filter (in case tests are run with -We)
warnings.simplefilter("error", RuntimeWarning)
warnings.filterwarnings("default", category=RuntimeWarning,
module=__name__)
with self.assertRaises(self.failureException):
with self.assertWarns(DeprecationWarning):
_runtime_warn()
_runtime_warn()
self.assertEqual(len(log), 1, log)
self.assertIsInstance(log[0].message, RuntimeWarning)
# Filters for other warnings are not modified
with warnings.catch_warnings():
warnings.simplefilter("error", RuntimeWarning)
with self.assertRaises(RuntimeWarning):
with self.assertWarns(DeprecationWarning):
_runtime_warn()
# Warnings that do not match the category are not swallowed.
with self.assertWarns(RuntimeWarning):
with self.assertRaises(self.failureException):
with self.assertWarns(DeprecationWarning):
_runtime_warn()
with self.assertWarns(RuntimeWarning):
with self.assertWarns(DeprecationWarning):
_runtime_warn()
_runtime_warn(DeprecationWarning)
with self.assertWarns(RuntimeWarning):
with self.assertWarns(DeprecationWarning):
_runtime_warn(DeprecationWarning)
_runtime_warn()
# Filters by module name work for other warnings.
with warnings.catch_warnings(record=True) as log:
warnings.filterwarnings("error", category=RuntimeWarning)
warnings.filterwarnings("default", category=RuntimeWarning,
module=re.escape(__name__))
warnings.filterwarnings("error", category=RuntimeWarning,
module='test_case')
with self.assertWarns(DeprecationWarning):
_runtime_warn(DeprecationWarning)
_runtime_warn()
_runtime_warn()
self.assertEqual(len(log), 1, log)
self.assertIsInstance(log[0].message, RuntimeWarning)

def testAssertWarnsNoExceptionType(self):
with self.assertRaises(TypeError):
Expand All @@ -1722,8 +1775,9 @@ def testAssertWarnsNoExceptionType(self):
self.assertWarns((UserWarning, Exception))

def testAssertWarnsRegexCallable(self):
def _runtime_warn(msg):
warnings.warn(msg, RuntimeWarning)
def _runtime_warn(*msgs):
for msg in msgs:
warnings.warn(msg, RuntimeWarning)
self.assertWarnsRegex(RuntimeWarning, "o+",
_runtime_warn, "foox")
# Failure when no warning is triggered
Expand All @@ -1734,16 +1788,26 @@ def _runtime_warn(msg):
with self.assertRaises(TypeError):
self.assertWarnsRegex(RuntimeWarning, "o+", None)
# Failure when another warning is triggered
with warnings.catch_warnings():
with warnings.catch_warnings(record=True) as log:
# Force default filter (in case tests are run with -We)
warnings.simplefilter("default", RuntimeWarning)
with self.assertRaises(self.failureException):
self.assertWarnsRegex(DeprecationWarning, "o+",
_runtime_warn, "foox")
# Failure when message doesn't match
with self.assertRaises(self.failureException):
_runtime_warn, "foox", "foox")
self.assertEqual(len(log), 1, log)
self.assertIsInstance(log[0].message, RuntimeWarning)
# Failure when message doesn't match.
# Warnings that do not match the regex are not swallowed.
with self.assertWarnsRegex(RuntimeWarning, "ar"):
with self.assertRaises(self.failureException):
self.assertWarnsRegex(RuntimeWarning, "o+",
_runtime_warn, "barz")
with self.assertWarnsRegex(RuntimeWarning, "ar"):
self.assertWarnsRegex(RuntimeWarning, "o+",
_runtime_warn, "barz")
_runtime_warn, "barz", "foox")
with self.assertWarnsRegex(RuntimeWarning, "ar"):
self.assertWarnsRegex(RuntimeWarning, "o+",
_runtime_warn, "foox", "barz")
# A little trickier: we ask RuntimeWarnings to be raised, and then
# check for some of them. It is implementation-defined whether
# non-matching RuntimeWarnings are simply re-raised, or produce a
Expand Down Expand Up @@ -1778,15 +1842,28 @@ def _runtime_warn(msg):
with self.assertWarnsRegex(RuntimeWarning, 'o+', foobar=42):
pass
# Failure when another warning is triggered
with warnings.catch_warnings():
with warnings.catch_warnings(record=True) as log:
# Force default filter (in case tests are run with -We)
warnings.simplefilter("default", RuntimeWarning)
with self.assertRaises(self.failureException):
with self.assertWarnsRegex(DeprecationWarning, "o+"):
_runtime_warn("foox")
# Failure when message doesn't match
with self.assertRaises(self.failureException):
_runtime_warn("foox")
self.assertEqual(len(log), 1, log)
self.assertIsInstance(log[0].message, RuntimeWarning)
# Failure when message doesn't match.
# Warnings that do not match the regex are not swallowed.
with self.assertWarnsRegex(RuntimeWarning, "ar"):
with self.assertRaises(self.failureException):
with self.assertWarnsRegex(RuntimeWarning, "o+"):
_runtime_warn("barz")
with self.assertWarnsRegex(RuntimeWarning, "ar"):
with self.assertWarnsRegex(RuntimeWarning, "o+"):
_runtime_warn("barz")
_runtime_warn("foox")
with self.assertWarnsRegex(RuntimeWarning, "ar"):
with self.assertWarnsRegex(RuntimeWarning, "o+"):
_runtime_warn("foox")
_runtime_warn("barz")
# A little trickier: we ask RuntimeWarnings to be raised, and then
# check for some of them. It is implementation-defined whether
Expand All @@ -1797,6 +1874,19 @@ def _runtime_warn(msg):
with self.assertRaises((RuntimeWarning, self.failureException)):
with self.assertWarnsRegex(RuntimeWarning, "o+"):
_runtime_warn("barz")
# Filters by module name work for warnings with other message.
with warnings.catch_warnings(record=True) as log:
warnings.filterwarnings("error", category=RuntimeWarning)
warnings.filterwarnings("default", category=RuntimeWarning,
module=re.escape(__name__))
warnings.filterwarnings("error", category=RuntimeWarning,
module='test_case')
with self.assertWarnsRegex(RuntimeWarning, "ar"):
_runtime_warn("bar")
_runtime_warn("foox")
_runtime_warn("foox")
self.assertEqual(len(log), 1, log)
self.assertIsInstance(log[0].message, RuntimeWarning)

def testAssertWarnsRegexNoExceptionType(self):
with self.assertRaises(TypeError):
Expand Down
Loading
Loading