Skip to content

Commit 8d071b9

Browse files
committed
gh-89900: Add option to not disable existing handlers for assertLogs
added documentation for new keep_handlers kwarg for assertLogs function in unittest
1 parent bb25f72 commit 8d071b9

File tree

4 files changed

+51
-6
lines changed

4 files changed

+51
-6
lines changed

Doc/library/unittest.rst

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,7 @@ Test cases
11311131
.. versionchanged:: 3.3
11321132
Added the *msg* keyword argument when used as a context manager.
11331133

1134-
.. method:: assertLogs(logger=None, level=None, formatter=None)
1134+
.. method:: assertLogs(logger=None, level=None, formatter=None, keep_handlers=False)
11351135

11361136
A context manager to test that at least one message is logged on
11371137
the *logger* or one of its children, with at least the given
@@ -1150,6 +1150,15 @@ Test cases
11501150
The default is a formatter with format string
11511151
``"%(levelname)s:%(name)s:%(message)s"``
11521152

1153+
If given, *keep_handlers* should be a boolean value. If ``True``,
1154+
existing handlers attached to the logger will be preserved and
1155+
continue to function normally alongside the capturing handler.
1156+
If ``False`` (the default), existing handlers are temporarily
1157+
removed during the assertion to prevent log output during tests.
1158+
Note that when *keep_handlers* is ``True``, the logger's level
1159+
is still temporarily set to the requested level, which may cause
1160+
existing handlers to process more messages than they normally would.
1161+
11531162
The test passes if at least one message emitted inside the ``with``
11541163
block matches the *logger* and *level* conditions, otherwise it fails.
11551164

@@ -1180,6 +1189,10 @@ Test cases
11801189
.. versionchanged:: 3.15
11811190
Now accepts a *formatter* to control how messages are formatted.
11821191

1192+
.. versionchanged:: 3.16
1193+
Added the *keep_handlers* parameter to optionally preserve
1194+
existing handlers.
1195+
11831196
.. method:: assertNoLogs(logger=None, level=None)
11841197

11851198
A context manager to test that no messages are logged on

Lib/test/test_unittest/test_case.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,6 +2005,28 @@ def testAssertNoLogsYieldsNone(self):
20052005
pass
20062006
self.assertIsNone(value)
20072007

2008+
def testAssertLogsKeepHandlers(self):
2009+
# Verify keep_handlers=True preserves existing handlers
2010+
handler_records = []
2011+
handler = logging.Handler()
2012+
handler.emit = lambda record: handler_records.append(record)
2013+
log_foo.addHandler(handler)
2014+
test_message = "test message"
2015+
2016+
try:
2017+
with self.assertNoStderr():
2018+
with self.assertLogs('foo', level='INFO', keep_handlers=True) as cm:
2019+
log_foo.info(test_message)
2020+
2021+
self.assertEqual(cm.output, [f'INFO:foo:{test_message}'])
2022+
2023+
self.assertEqual(len(handler_records), 1)
2024+
self.assertEqual(handler_records[0].getMessage(), test_message)
2025+
2026+
self.assertEqual(log_foo.handlers, [handler])
2027+
finally:
2028+
log_foo.removeHandler(handler)
2029+
20082030
def testAssertStartsWith(self):
20092031
self.assertStartsWith('ababahalamaha', 'ababa')
20102032
self.assertStartsWith('ababahalamaha', ('x', 'ababa', 'y'))

Lib/unittest/_log.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class _AssertLogsContext(_BaseTestCaseContext):
3030

3131
LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s"
3232

33-
def __init__(self, test_case, logger_name, level, no_logs, formatter=None):
33+
def __init__(self, test_case, logger_name, level, no_logs, formatter=None, keep_handlers=False):
3434
_BaseTestCaseContext.__init__(self, test_case)
3535
self.logger_name = logger_name
3636
if level:
@@ -40,6 +40,7 @@ def __init__(self, test_case, logger_name, level, no_logs, formatter=None):
4040
self.msg = None
4141
self.no_logs = no_logs
4242
self.formatter = formatter
43+
self.keep_handlers = keep_handlers
4344

4445
def __enter__(self):
4546
if isinstance(self.logger_name, logging.Logger):
@@ -54,9 +55,13 @@ def __enter__(self):
5455
self.old_handlers = logger.handlers[:]
5556
self.old_level = logger.level
5657
self.old_propagate = logger.propagate
57-
logger.handlers = [handler]
58+
if self.keep_handlers:
59+
logger.addHandler(handler)
60+
else:
61+
logger.handlers = [handler]
62+
logger.propagate = False
5863
logger.setLevel(self.level)
59-
logger.propagate = False
64+
6065
if self.no_logs:
6166
return
6267
return handler.watcher

Lib/unittest/case.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ def _assertNotWarns(self, expected_warning, *args, **kwargs):
849849
context = _AssertNotWarnsContext(expected_warning, self)
850850
return context.handle('_assertNotWarns', args, kwargs)
851851

852-
def assertLogs(self, logger=None, level=None, formatter=None):
852+
def assertLogs(self, logger=None, level=None, formatter=None, keep_handlers=False):
853853
"""Fail unless a log message of level *level* or higher is emitted
854854
on *logger_name* or its children. If omitted, *level* defaults to
855855
INFO and *logger* defaults to the root logger.
@@ -863,6 +863,11 @@ def assertLogs(self, logger=None, level=None, formatter=None):
863863
864864
Optionally supply `formatter` to control how messages are formatted.
865865
866+
Optionally supply `keep_handlers` to control whether to preserve existing handlers.
867+
Note that the logger's level will still be temporarily set to the requested level,
868+
which may cause existing handlers to process more messages than usual
869+
during the context manager.
870+
866871
Example::
867872
868873
with self.assertLogs('foo', level='INFO') as cm:
@@ -873,7 +878,7 @@ def assertLogs(self, logger=None, level=None, formatter=None):
873878
"""
874879
# Lazy import to avoid importing logging if it is not needed.
875880
from ._log import _AssertLogsContext
876-
return _AssertLogsContext(self, logger, level, no_logs=False, formatter=formatter)
881+
return _AssertLogsContext(self, logger, level, no_logs=False, formatter=formatter, keep_handlers=keep_handlers)
877882

878883
def assertNoLogs(self, logger=None, level=None):
879884
""" Fail unless no log messages of level *level* or higher are emitted

0 commit comments

Comments
 (0)