https://github.com/python/cpython/commit/51ab66b3d519d3802430b3a31a135d2670e37408
commit: 51ab66b3d519d3802430b3a31a135d2670e37408
branch: main
author: Garry Cairns <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-07-02T09:51:19Z
summary:
gh-134567: Add the formatter parameter in unittest.TestCase.assertLogs
(GH-134570)
files:
A Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst
M Doc/library/unittest.rst
M Doc/whatsnew/3.15.rst
M Lib/test/test_unittest/test_case.py
M Lib/unittest/_log.py
M Lib/unittest/case.py
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
index dcdda1719bf593..d526e835caa18c 100644
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -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
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index f06d4c84ea53d1..706a816f888b30 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -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
----
diff --git a/Lib/test/test_unittest/test_case.py
b/Lib/test/test_unittest/test_case.py
index d66cab146af246..cf10e956bf2bdc 100644
--- a/Lib/test/test_unittest/test_case.py
+++ b/Lib/test/test_unittest/test_case.py
@@ -1920,6 +1920,22 @@ def testAssertLogsUnexpectedException(self):
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():
diff --git a/Lib/unittest/_log.py b/Lib/unittest/_log.py
index 94868e5bb95eb3..3d69385ea243e7 100644
--- a/Lib/unittest/_log.py
+++ b/Lib/unittest/_log.py
@@ -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 @@ def __init__(self, test_case, logger_name, level, no_logs):
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)
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index db10de68e4ac73..eba50839cd33ae 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -849,7 +849,7 @@ def _assertNotWarns(self, expected_warning, *args,
**kwargs):
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 @@ def assertLogs(self, logger=None, level=None):
`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 @@ def assertLogs(self, logger=None, level=None):
"""
# 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
diff --git
a/Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst
b/Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst
new file mode 100644
index 00000000000000..42e4a01c0ccb4b
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2025-05-23-09-19-52.gh-issue-134567.hwEIMb.rst
@@ -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.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]