Skip to content

Commit 1b9f427

Browse files
committed
Undo log level set by caplog.set_level at the end of the test
Otherwise this leaks the log level information to other tests Ref: pytest-dev#3013
1 parent ea18549 commit 1b9f427

File tree

2 files changed

+56
-12
lines changed

2 files changed

+56
-12
lines changed

_pytest/logging.py

+28-12
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,17 @@ class LogCaptureFixture(object):
129129
def __init__(self, item):
130130
"""Creates a new funcarg."""
131131
self._item = item
132+
self._initial_log_levels = {} # type: Dict[str, int] # dict of log name -> log level
133+
134+
def _finalize(self):
135+
"""Finalizes the fixture.
136+
137+
This restores the log levels changed by :meth:`set_level`.
138+
"""
139+
# restore log levels
140+
for logger_name, level in self._initial_log_levels.items():
141+
logger = logging.getLogger(logger_name)
142+
logger.setLevel(level)
132143

133144
@property
134145
def handler(self):
@@ -160,27 +171,30 @@ def clear(self):
160171
self.handler.records = []
161172

162173
def set_level(self, level, logger=None):
163-
"""Sets the level for capturing of logs.
174+
"""Sets the level for capturing of logs. The level will be restored to its previous value at the end of
175+
the test.
164176
165177
:param int level: the logger to level.
166178
:param str logger: the logger to update the level. If not given, the root logger level is updated.
179+
180+
.. versionchanged:: 3.4
181+
The levels of the loggers changed by this function will be restored to their initial values at the
182+
end of the test.
167183
"""
168-
logger = logging.getLogger(logger)
184+
logger_name = logger
185+
logger = logging.getLogger(logger_name)
186+
self._initial_log_levels.setdefault(logger_name, logger.level)
169187
logger.setLevel(level)
170188

171189
@contextmanager
172190
def at_level(self, level, logger=None):
173-
"""Context manager that sets the level for capturing of logs.
191+
"""Context manager that sets the level for capturing of logs. After the end of the 'with' statement the
192+
level is restored to its original value.
174193
175-
By default, the level is set on the handler used to capture
176-
logs. Specify a logger name to instead set the level of any
177-
logger.
194+
:param int level: the logger to level.
195+
:param str logger: the logger to update the level. If not given, the root logger level is updated.
178196
"""
179-
if logger is None:
180-
logger = self.handler
181-
else:
182-
logger = logging.getLogger(logger)
183-
197+
logger = logging.getLogger(logger)
184198
orig_level = logger.level
185199
logger.setLevel(level)
186200
try:
@@ -199,7 +213,9 @@ def caplog(request):
199213
* caplog.records() -> list of logging.LogRecord instances
200214
* caplog.record_tuples() -> list of (logger_name, level, message) tuples
201215
"""
202-
return LogCaptureFixture(request.node)
216+
result = LogCaptureFixture(request.node)
217+
yield result
218+
result._finalize()
203219

204220

205221
def get_actual_log_level(config, *setting_names):

testing/logging/test_fixture.py

+28
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,30 @@ def test_change_level(caplog):
2626
assert 'CRITICAL' in caplog.text
2727

2828

29+
def test_change_level_undo(testdir):
30+
"""Ensure that 'set_level' is undone after the end of the test"""
31+
testdir.makepyfile('''
32+
import logging
33+
34+
def test1(caplog):
35+
caplog.set_level(logging.INFO)
36+
# using + operator here so fnmatch_lines doesn't match the code in the traceback
37+
logging.info('log from ' + 'test1')
38+
assert 0
39+
40+
def test2(caplog):
41+
# using + operator here so fnmatch_lines doesn't match the code in the traceback
42+
logging.info('log from ' + 'test2')
43+
assert 0
44+
''')
45+
result = testdir.runpytest_subprocess()
46+
result.stdout.fnmatch_lines([
47+
'*log from test1*',
48+
'*2 failed in *',
49+
])
50+
assert 'log from test2' not in result.stdout.str()
51+
52+
2953
def test_with_statement(caplog):
3054
with caplog.at_level(logging.INFO):
3155
logger.debug('handler DEBUG level')
@@ -42,13 +66,15 @@ def test_with_statement(caplog):
4266

4367

4468
def test_log_access(caplog):
69+
caplog.set_level(logging.INFO)
4570
logger.info('boo %s', 'arg')
4671
assert caplog.records[0].levelname == 'INFO'
4772
assert caplog.records[0].msg == 'boo %s'
4873
assert 'boo arg' in caplog.text
4974

5075

5176
def test_record_tuples(caplog):
77+
caplog.set_level(logging.INFO)
5278
logger.info('boo %s', 'arg')
5379

5480
assert caplog.record_tuples == [
@@ -57,13 +83,15 @@ def test_record_tuples(caplog):
5783

5884

5985
def test_unicode(caplog):
86+
caplog.set_level(logging.INFO)
6087
logger.info(u'bū')
6188
assert caplog.records[0].levelname == 'INFO'
6289
assert caplog.records[0].msg == u'bū'
6390
assert u'bū' in caplog.text
6491

6592

6693
def test_clear(caplog):
94+
caplog.set_level(logging.INFO)
6795
logger.info(u'bū')
6896
assert len(caplog.records)
6997
caplog.clear()

0 commit comments

Comments
 (0)