Show a header for each testing phase during live logging

As suggested during review
This commit is contained in:
Bruno Oliveira 2018-01-22 21:00:52 -02:00
parent 29a7b5e064
commit 0df42b4426
2 changed files with 80 additions and 16 deletions

View File

@ -304,6 +304,8 @@ class LoggingPlugin(object):
"""Implements the internals of pytest_runtest_xxx() hook.""" """Implements the internals of pytest_runtest_xxx() hook."""
with catching_logs(LogCaptureHandler(), with catching_logs(LogCaptureHandler(),
formatter=self.formatter, level=self.log_level) as log_handler: formatter=self.formatter, level=self.log_level) as log_handler:
if self.log_cli_handler:
self.log_cli_handler.reset(item, when)
if not hasattr(item, 'catch_log_handlers'): if not hasattr(item, 'catch_log_handlers'):
item.catch_log_handlers = {} item.catch_log_handlers = {}
item.catch_log_handlers[when] = log_handler item.catch_log_handlers[when] = log_handler
@ -322,8 +324,6 @@ class LoggingPlugin(object):
@pytest.hookimpl(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_runtest_setup(self, item): def pytest_runtest_setup(self, item):
if self.log_cli_handler is not None:
self.log_cli_handler.reset()
with self._runtest_for(item, 'setup'): with self._runtest_for(item, 'setup'):
yield yield
@ -387,18 +387,28 @@ class _LiveLoggingStreamHandler(logging.StreamHandler):
self.capture_manager = capture_manager self.capture_manager = capture_manager
self._first_record_emitted = False self._first_record_emitted = False
def reset(self): self._section_name_shown = False
self._first_record_emitted = False self._when = None
self._run_tests = set()
def reset(self, item, when):
self._when = when
self._section_name_shown = False
if item.name not in self._run_tests:
self._first_record_emitted = False
self._run_tests.add(item.name)
def emit(self, record): def emit(self, record):
if self.capture_manager is not None: if self.capture_manager is not None:
self.capture_manager.suspend_global_capture() self.capture_manager.suspend_global_capture()
try: try:
if not self._first_record_emitted: if not self._first_record_emitted or self._when == 'teardown':
self.stream.write('\n') self.stream.write('\n')
# we might consider adding a header at this point using self.stream.section('live log', sep='-')
# or something similar when we improve live logging output
self._first_record_emitted = True self._first_record_emitted = True
if not self._section_name_shown:
header = 'live log' if self._when == 'call' else 'live log ' + self._when
self.stream.section(header, sep='-', bold=True)
self._section_name_shown = True
logging.StreamHandler.emit(self, record) logging.StreamHandler.emit(self, record)
finally: finally:
if self.capture_manager is not None: if self.capture_manager is not None:

View File

@ -196,17 +196,16 @@ def test_log_cli_default_level(testdir):
assert result.ret == 0 assert result.ret == 0
def test_log_cli_default_level_multiple_tests(testdir): def test_log_cli_default_level_multiple_tests(testdir, request):
"""Ensure we reset the first newline added by the live logger between tests""" """Ensure we reset the first newline added by the live logger between tests"""
# Default log file level filename = request.node.name + '.py'
testdir.makepyfile(''' testdir.makepyfile('''
import pytest
import logging import logging
def test_log_1(request): def test_log_1():
logging.warning("log message from test_log_1") logging.warning("log message from test_log_1")
def test_log_2(request): def test_log_2():
logging.warning("log message from test_log_2") logging.warning("log message from test_log_2")
''') ''')
testdir.makeini(''' testdir.makeini('''
@ -216,16 +215,63 @@ def test_log_cli_default_level_multiple_tests(testdir):
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
'test_log_cli_default_level_multiple_tests.py::test_log_1 ', '{}::test_log_1 '.format(filename),
'*WARNING*log message from test_log_1*', '*WARNING*log message from test_log_1*',
'PASSED *50%*', 'PASSED *50%*',
'test_log_cli_default_level_multiple_tests.py::test_log_2 ', '{}::test_log_2 '.format(filename),
'*WARNING*log message from test_log_2*', '*WARNING*log message from test_log_2*',
'PASSED *100%*', 'PASSED *100%*',
'=* 2 passed in *=', '=* 2 passed in *=',
]) ])
def test_log_cli_default_level_sections(testdir, request):
"""Check that with live logging enable we are printing the correct headers during setup/call/teardown."""
filename = request.node.name + '.py'
testdir.makepyfile('''
import pytest
import logging
@pytest.fixture
def fix(request):
logging.warning("log message from setup of {}".format(request.node.name))
yield
logging.warning("log message from teardown of {}".format(request.node.name))
def test_log_1(fix):
logging.warning("log message from test_log_1")
def test_log_2(fix):
logging.warning("log message from test_log_2")
''')
testdir.makeini('''
[pytest]
log_cli=true
''')
result = testdir.runpytest()
result.stdout.fnmatch_lines([
'{}::test_log_1 '.format(filename),
'*-- live log setup --*',
'*WARNING*log message from setup of test_log_1*',
'*-- live log --*',
'*WARNING*log message from test_log_1*',
'PASSED *50%*',
'*-- live log teardown --*',
'*WARNING*log message from teardown of test_log_1*',
'{}::test_log_2 '.format(filename),
'*-- live log setup --*',
'*WARNING*log message from setup of test_log_2*',
'*-- live log --*',
'*WARNING*log message from test_log_2*',
'PASSED *100%*',
'*-- live log teardown --*',
'*WARNING*log message from teardown of test_log_2*',
'=* 2 passed in *=',
])
def test_log_cli_level(testdir): def test_log_cli_level(testdir):
# Default log file level # Default log file level
testdir.makepyfile(''' testdir.makepyfile('''
@ -473,11 +519,19 @@ def test_live_logging_suspends_capture(has_capture_manager, request):
assert CaptureManager.suspend_capture_item assert CaptureManager.suspend_capture_item
assert CaptureManager.resume_global_capture assert CaptureManager.resume_global_capture
capture_manager = MockCaptureManager() if has_capture_manager else None class DummyTerminal(six.StringIO):
out_file = six.StringIO()
def section(self, *args, **kwargs):
pass
out_file = DummyTerminal()
capture_manager = MockCaptureManager() if has_capture_manager else None
handler = _LiveLoggingStreamHandler(out_file, capture_manager) handler = _LiveLoggingStreamHandler(out_file, capture_manager)
class DummyItem:
name = 'test_foo'
handler.reset(DummyItem(), 'call')
logger = logging.getLogger(__name__ + '.test_live_logging_suspends_capture') logger = logging.getLogger(__name__ + '.test_live_logging_suspends_capture')
logger.addHandler(handler) logger.addHandler(handler)
request.addfinalizer(partial(logger.removeHandler, handler)) request.addfinalizer(partial(logger.removeHandler, handler))