gh-134567: Add the formatter parameter in unittest.TestCase.assertLogs (GH-134570)

This commit is contained in:
Garry Cairns 2025-07-02 10:51:19 +01:00 committed by GitHub
parent b19c9da401
commit 51ab66b3d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 42 additions and 5 deletions

View file

@ -1131,7 +1131,7 @@ Test cases
.. versionchanged:: 3.3
Added the *msg* keyword argument when used as a context manager.
.. method:: assertLogs(logger=None, level=None)
.. method:: assertLogs(logger=None, level=None, formatter=None)
A context manager to test that at least one message is logged on
the *logger* or one of its children, with at least the given
@ -1146,6 +1146,10 @@ Test cases
its string equivalent (for example either ``"ERROR"`` or
:const:`logging.ERROR`). The default is :const:`logging.INFO`.
If given, *formatter* should be a :class:`logging.Formatter` object.
The default is a formatter with format string
``"%(levelname)s:%(name)s:%(message)s"``
The test passes if at least one message emitted inside the ``with``
block matches the *logger* and *level* conditions, otherwise it fails.
@ -1173,6 +1177,9 @@ Test cases
.. versionadded:: 3.4
.. versionchanged:: next
Now accepts a *formatter* to control how messages are formatted.
.. method:: assertNoLogs(logger=None, level=None)
A context manager to test that no messages are logged on

View file

@ -291,6 +291,15 @@ typing
(Contributed by Bénédikt Tran in :gh:`133823`.)
unittest
--------
* Lets users specify formatter in TestCase.assertLogs.
:func:`unittest.TestCase.assertLogs` will now accept a formatter
to control how messages are formatted.
(Contributed by Garry Cairns in :gh:`134567`.)
wave
----

View file

@ -1920,6 +1920,22 @@ test case
with self.assertLogs():
raise ZeroDivisionError("Unexpected")
def testAssertLogsWithFormatter(self):
# Check alternative formats will be respected
format = "[No.1: the larch] %(levelname)s:%(name)s:%(message)s"
formatter = logging.Formatter(format)
with self.assertNoStderr():
with self.assertLogs() as cm:
log_foo.info("1")
log_foobar.debug("2")
self.assertEqual(cm.output, ["INFO:foo:1"])
self.assertLogRecords(cm.records, [{'name': 'foo'}])
with self.assertLogs(formatter=formatter) as cm:
log_foo.info("1")
log_foobar.debug("2")
self.assertEqual(cm.output, ["[No.1: the larch] INFO:foo:1"])
self.assertLogRecords(cm.records, [{'name': 'foo'}])
def testAssertNoLogsDefault(self):
with self.assertRaises(self.failureException) as cm:
with self.assertNoLogs():

View file

@ -30,7 +30,7 @@ class _AssertLogsContext(_BaseTestCaseContext):
LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s"
def __init__(self, test_case, logger_name, level, no_logs):
def __init__(self, test_case, logger_name, level, no_logs, formatter=None):
_BaseTestCaseContext.__init__(self, test_case)
self.logger_name = logger_name
if level:
@ -39,13 +39,14 @@ class _AssertLogsContext(_BaseTestCaseContext):
self.level = logging.INFO
self.msg = None
self.no_logs = no_logs
self.formatter = formatter
def __enter__(self):
if isinstance(self.logger_name, logging.Logger):
logger = self.logger = self.logger_name
else:
logger = self.logger = logging.getLogger(self.logger_name)
formatter = logging.Formatter(self.LOGGING_FORMAT)
formatter = self.formatter or logging.Formatter(self.LOGGING_FORMAT)
handler = _CapturingHandler()
handler.setLevel(self.level)
handler.setFormatter(formatter)

View file

@ -849,7 +849,7 @@ class TestCase(object):
context = _AssertNotWarnsContext(expected_warning, self)
return context.handle('_assertNotWarns', args, kwargs)
def assertLogs(self, logger=None, level=None):
def assertLogs(self, logger=None, level=None, formatter=None):
"""Fail unless a log message of level *level* or higher is emitted
on *logger_name* or its children. If omitted, *level* defaults to
INFO and *logger* defaults to the root logger.
@ -861,6 +861,8 @@ class TestCase(object):
`records` attribute will be a list of the corresponding LogRecord
objects.
Optionally supply `formatter` to control how messages are formatted.
Example::
with self.assertLogs('foo', level='INFO') as cm:
@ -871,7 +873,7 @@ class TestCase(object):
"""
# Lazy import to avoid importing logging if it is not needed.
from ._log import _AssertLogsContext
return _AssertLogsContext(self, logger, level, no_logs=False)
return _AssertLogsContext(self, logger, level, no_logs=False, formatter=formatter)
def assertNoLogs(self, logger=None, level=None):
""" Fail unless no log messages of level *level* or higher are emitted

View file

@ -0,0 +1,2 @@
Expose log formatter to users in TestCase.assertLogs.
:func:`unittest.TestCase.assertLogs` will now optionally accept a formatter that will be used to format the strings in output if provided.