summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKit Choi <kitchoi@users.noreply.github.com>2020-07-01 21:08:38 (GMT)
committerGitHub <noreply@github.com>2020-07-01 21:08:38 (GMT)
commit6b34d7b51e33fcb21b8827d927474ce9ed1f605c (patch)
tree3c99cf049311bfa2296d47d69fa998b08da26aab
parent5d5c84ef78b19211671c2bfa68fe073485135eed (diff)
downloadcpython-6b34d7b51e33fcb21b8827d927474ce9ed1f605c.zip
cpython-6b34d7b51e33fcb21b8827d927474ce9ed1f605c.tar.gz
cpython-6b34d7b51e33fcb21b8827d927474ce9ed1f605c.tar.bz2
bpo-39385: Add an assertNoLogs context manager to unittest.TestCase (GH-18067)
Co-authored-by: RĂ©mi Lapeyre <remi.lapeyre@henki.fr>
-rw-r--r--Doc/library/unittest.rst21
-rw-r--r--Lib/unittest/_log.py28
-rw-r--r--Lib/unittest/case.py12
-rw-r--r--Lib/unittest/test/test_case.py75
-rw-r--r--Misc/NEWS.d/next/Library/2020-04-23-18-21-19.bpo-39385.MIAyS7.rst3
5 files changed, 131 insertions, 8 deletions
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
index b2e16cf..0dddbd2 100644
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -950,6 +950,9 @@ Test cases
| :meth:`assertLogs(logger, level) | The ``with`` block logs on *logger* | 3.4 |
| <TestCase.assertLogs>` | with minimum *level* | |
+---------------------------------------------------------+--------------------------------------+------------+
+ | :meth:`assertNoLogs(logger, level) | The ``with`` block does not log on | 3.10 |
+ | <TestCase.assertNoLogs>` | *logger* with minimum *level* | |
+ +---------------------------------------------------------+--------------------------------------+------------+
.. method:: assertRaises(exception, callable, *args, **kwds)
assertRaises(exception, *, msg=None)
@@ -1121,6 +1124,24 @@ Test cases
.. versionadded:: 3.4
+ .. method:: assertNoLogs(logger=None, level=None)
+
+ A context manager to test that no messages are logged on
+ the *logger* or one of its children, with at least the given
+ *level*.
+
+ If given, *logger* should be a :class:`logging.Logger` object or a
+ :class:`str` giving the name of a logger. The default is the root
+ logger, which will catch all messages.
+
+ If given, *level* should be either a numeric logging level or
+ its string equivalent (for example either ``"ERROR"`` or
+ :attr:`logging.ERROR`). The default is :attr:`logging.INFO`.
+
+ Unlike :meth:`assertLogs`, nothing will be returned by the context
+ manager.
+
+ .. versionadded:: 3.10
There are also other methods used to perform more specific checks, such as:
diff --git a/Lib/unittest/_log.py b/Lib/unittest/_log.py
index 94e7e75..961c448 100644
--- a/Lib/unittest/_log.py
+++ b/Lib/unittest/_log.py
@@ -26,11 +26,11 @@ class _CapturingHandler(logging.Handler):
class _AssertLogsContext(_BaseTestCaseContext):
- """A context manager used to implement TestCase.assertLogs()."""
+ """A context manager for assertLogs() and assertNoLogs() """
LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s"
- def __init__(self, test_case, logger_name, level):
+ def __init__(self, test_case, logger_name, level, no_logs):
_BaseTestCaseContext.__init__(self, test_case)
self.logger_name = logger_name
if level:
@@ -38,6 +38,7 @@ class _AssertLogsContext(_BaseTestCaseContext):
else:
self.level = logging.INFO
self.msg = None
+ self.no_logs = no_logs
def __enter__(self):
if isinstance(self.logger_name, logging.Logger):
@@ -54,16 +55,31 @@ class _AssertLogsContext(_BaseTestCaseContext):
logger.handlers = [handler]
logger.setLevel(self.level)
logger.propagate = False
+ if self.no_logs:
+ return
return handler.watcher
def __exit__(self, exc_type, exc_value, tb):
self.logger.handlers = self.old_handlers
self.logger.propagate = self.old_propagate
self.logger.setLevel(self.old_level)
+
if exc_type is not None:
# let unexpected exceptions pass through
return False
- if len(self.watcher.records) == 0:
- self._raiseFailure(
- "no logs of level {} or higher triggered on {}"
- .format(logging.getLevelName(self.level), self.logger.name))
+
+ if self.no_logs:
+ # assertNoLogs
+ if len(self.watcher.records) > 0:
+ self._raiseFailure(
+ "Unexpected logs found: {!r}".format(
+ self.watcher.output
+ )
+ )
+
+ else:
+ # assertLogs
+ if len(self.watcher.records) == 0:
+ self._raiseFailure(
+ "no logs of level {} or higher triggered on {}"
+ .format(logging.getLevelName(self.level), self.logger.name))
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index 52eb7d0..872f121 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -295,7 +295,6 @@ class _AssertWarnsContext(_AssertRaisesBaseContext):
self._raiseFailure("{} not triggered".format(exc_name))
-
class _OrderedChainMap(collections.ChainMap):
def __iter__(self):
seen = set()
@@ -788,7 +787,16 @@ class TestCase(object):
"""
# Lazy import to avoid importing logging if it is not needed.
from ._log import _AssertLogsContext
- return _AssertLogsContext(self, logger, level)
+ return _AssertLogsContext(self, logger, level, no_logs=False)
+
+ def assertNoLogs(self, logger=None, level=None):
+ """ Fail unless no log messages of level *level* or higher are emitted
+ on *logger_name* or its children.
+
+ This method must be used as a context manager.
+ """
+ from ._log import _AssertLogsContext
+ return _AssertLogsContext(self, logger, level, no_logs=True)
def _getAssertEqualityFunc(self, first, second):
"""Get a detailed comparison function for the types of the two args.
diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py
index 3dedcbe..0e41696 100644
--- a/Lib/unittest/test/test_case.py
+++ b/Lib/unittest/test/test_case.py
@@ -1681,6 +1681,81 @@ test case
with self.assertLogs('foo'):
log_quux.error("1")
+ def testAssertLogsUnexpectedException(self):
+ # Check unexpected exception will go through.
+ with self.assertRaises(ZeroDivisionError):
+ with self.assertLogs():
+ raise ZeroDivisionError("Unexpected")
+
+ def testAssertNoLogsDefault(self):
+ with self.assertRaises(self.failureException) as cm:
+ with self.assertNoLogs():
+ log_foo.info("1")
+ log_foobar.debug("2")
+ self.assertEqual(
+ str(cm.exception),
+ "Unexpected logs found: ['INFO:foo:1']",
+ )
+
+ def testAssertNoLogsFailureFoundLogs(self):
+ with self.assertRaises(self.failureException) as cm:
+ with self.assertNoLogs():
+ log_quux.error("1")
+ log_foo.error("foo")
+
+ self.assertEqual(
+ str(cm.exception),
+ "Unexpected logs found: ['ERROR:quux:1', 'ERROR:foo:foo']",
+ )
+
+ def testAssertNoLogsPerLogger(self):
+ with self.assertNoStderr():
+ with self.assertLogs(log_quux):
+ with self.assertNoLogs(logger=log_foo):
+ log_quux.error("1")
+
+ def testAssertNoLogsFailurePerLogger(self):
+ # Failure due to unexpected logs for the given logger or its
+ # children.
+ with self.assertRaises(self.failureException) as cm:
+ with self.assertLogs(log_quux):
+ with self.assertNoLogs(logger=log_foo):
+ log_quux.error("1")
+ log_foobar.info("2")
+ self.assertEqual(
+ str(cm.exception),
+ "Unexpected logs found: ['INFO:foo.bar:2']",
+ )
+
+ def testAssertNoLogsPerLevel(self):
+ # Check per-level filtering
+ with self.assertNoStderr():
+ with self.assertNoLogs(level="ERROR"):
+ log_foo.info("foo")
+ log_quux.debug("1")
+
+ def testAssertNoLogsFailurePerLevel(self):
+ # Failure due to unexpected logs at the specified level.
+ with self.assertRaises(self.failureException) as cm:
+ with self.assertNoLogs(level="DEBUG"):
+ log_foo.debug("foo")
+ log_quux.debug("1")
+ self.assertEqual(
+ str(cm.exception),
+ "Unexpected logs found: ['DEBUG:foo:foo', 'DEBUG:quux:1']",
+ )
+
+ def testAssertNoLogsUnexpectedException(self):
+ # Check unexpected exception will go through.
+ with self.assertRaises(ZeroDivisionError):
+ with self.assertNoLogs():
+ raise ZeroDivisionError("Unexpected")
+
+ def testAssertNoLogsYieldsNone(self):
+ with self.assertNoLogs() as value:
+ pass
+ self.assertIsNone(value)
+
def testDeprecatedMethodNames(self):
"""
Test that the deprecated methods raise a DeprecationWarning. See #9424.
diff --git a/Misc/NEWS.d/next/Library/2020-04-23-18-21-19.bpo-39385.MIAyS7.rst b/Misc/NEWS.d/next/Library/2020-04-23-18-21-19.bpo-39385.MIAyS7.rst
new file mode 100644
index 0000000..e6c5c0d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-04-23-18-21-19.bpo-39385.MIAyS7.rst
@@ -0,0 +1,3 @@
+A new test assertion context-manager, :func:`unittest.assertNoLogs` will
+ensure a given block of code emits no log messages using the logging module.
+Contributed by Kit Yan Choi.