Skip to content

Commit

Permalink
Undo log level set by caplog.set_level at the end of the test
Browse files Browse the repository at this point in the history
Otherwise this leaks the log level information to other tests

Ref: pytest-dev#3013
  • Loading branch information
nicoddemus committed Jan 16, 2018
1 parent ea18549 commit 1b9f427
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 12 deletions.
40 changes: 28 additions & 12 deletions _pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ class LogCaptureFixture(object):
def __init__(self, item):
"""Creates a new funcarg."""
self._item = item
self._initial_log_levels = {} # type: Dict[str, int] # dict of log name -> log level

def _finalize(self):
"""Finalizes the fixture.
This restores the log levels changed by :meth:`set_level`.
"""
# restore log levels
for logger_name, level in self._initial_log_levels.items():
logger = logging.getLogger(logger_name)
logger.setLevel(level)

@property
def handler(self):
Expand Down Expand Up @@ -160,27 +171,30 @@ def clear(self):
self.handler.records = []

def set_level(self, level, logger=None):
"""Sets the level for capturing of logs.
"""Sets the level for capturing of logs. The level will be restored to its previous value at the end of
the test.
:param int level: the logger to level.
:param str logger: the logger to update the level. If not given, the root logger level is updated.
.. versionchanged:: 3.4
The levels of the loggers changed by this function will be restored to their initial values at the
end of the test.
"""
logger = logging.getLogger(logger)
logger_name = logger
logger = logging.getLogger(logger_name)
self._initial_log_levels.setdefault(logger_name, logger.level)
logger.setLevel(level)

@contextmanager
def at_level(self, level, logger=None):
"""Context manager that sets the level for capturing of logs.
"""Context manager that sets the level for capturing of logs. After the end of the 'with' statement the
level is restored to its original value.
By default, the level is set on the handler used to capture
logs. Specify a logger name to instead set the level of any
logger.
:param int level: the logger to level.
:param str logger: the logger to update the level. If not given, the root logger level is updated.
"""
if logger is None:
logger = self.handler
else:
logger = logging.getLogger(logger)

logger = logging.getLogger(logger)
orig_level = logger.level
logger.setLevel(level)
try:
Expand All @@ -199,7 +213,9 @@ def caplog(request):
* caplog.records() -> list of logging.LogRecord instances
* caplog.record_tuples() -> list of (logger_name, level, message) tuples
"""
return LogCaptureFixture(request.node)
result = LogCaptureFixture(request.node)
yield result
result._finalize()


def get_actual_log_level(config, *setting_names):
Expand Down
28 changes: 28 additions & 0 deletions testing/logging/test_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ def test_change_level(caplog):
assert 'CRITICAL' in caplog.text


def test_change_level_undo(testdir):
"""Ensure that 'set_level' is undone after the end of the test"""
testdir.makepyfile('''
import logging
def test1(caplog):
caplog.set_level(logging.INFO)
# using + operator here so fnmatch_lines doesn't match the code in the traceback
logging.info('log from ' + 'test1')
assert 0
def test2(caplog):
# using + operator here so fnmatch_lines doesn't match the code in the traceback
logging.info('log from ' + 'test2')
assert 0
''')
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines([
'*log from test1*',
'*2 failed in *',
])
assert 'log from test2' not in result.stdout.str()


def test_with_statement(caplog):
with caplog.at_level(logging.INFO):
logger.debug('handler DEBUG level')
Expand All @@ -42,13 +66,15 @@ def test_with_statement(caplog):


def test_log_access(caplog):
caplog.set_level(logging.INFO)
logger.info('boo %s', 'arg')
assert caplog.records[0].levelname == 'INFO'
assert caplog.records[0].msg == 'boo %s'
assert 'boo arg' in caplog.text


def test_record_tuples(caplog):
caplog.set_level(logging.INFO)
logger.info('boo %s', 'arg')

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


def test_unicode(caplog):
caplog.set_level(logging.INFO)
logger.info(u'bū')
assert caplog.records[0].levelname == 'INFO'
assert caplog.records[0].msg == u'bū'
assert u'bū' in caplog.text


def test_clear(caplog):
caplog.set_level(logging.INFO)
logger.info(u'bū')
assert len(caplog.records)
caplog.clear()
Expand Down

0 comments on commit 1b9f427

Please sign in to comment.