Skip to content

Commit 9ec1dcb

Browse files
rhoban13nicoddemus
andauthored
Log in subtests similar to stdout (#91)
Fix #87 --------- Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
1 parent 90157f1 commit 9ec1dcb

File tree

3 files changed

+118
-3
lines changed

3 files changed

+118
-3
lines changed

CHANGELOG.rst

+2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ CHANGELOG
44
UNRELEASED
55
----------
66

7+
* Logging is displayed for failing subtests (`#92`_)
78
* Passing subtests no longer turn the pytest output to yellow (as if warnings have been issued) (`#86`_). Thanks to `Andrew-Brock`_ for providing the solution.
89
* Now the ``msg`` contents of a subtest is displayed when running pytest with ``-v`` (`#6`_).
910

1011
.. _#6: https://github.com/pytest-dev/pytest-subtests/issues/6
1112
.. _#86: https://github.com/pytest-dev/pytest-subtests/issues/86
13+
.. _#92: https://github.com/pytest-dev/pytest-subtests/issues/87
1214

1315
.. _`Andrew-Brock`: https://github.com/Andrew-Brock
1416

src/pytest_subtests.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from _pytest.capture import CaptureFixture
99
from _pytest.capture import FDCapture
1010
from _pytest.capture import SysCapture
11+
from _pytest.logging import LogCaptureHandler, catching_logs
1112
from _pytest.outcomes import OutcomeException
1213
from _pytest.reports import TestReport
1314
from _pytest.runner import CallInfo
@@ -176,14 +177,27 @@ def _capturing_output(self):
176177
fixture.close()
177178
captured.out = out
178179
captured.err = err
180+
181+
@contextmanager
182+
def _capturing_logs(self):
183+
logging_plugin = self.request.config.pluginmanager.getplugin("logging-plugin")
184+
if logging_plugin is None:
185+
yield NullCapturedLogs()
186+
else:
187+
handler = LogCaptureHandler()
188+
handler.setFormatter(logging_plugin.formatter)
189+
190+
captured_logs = CapturedLogs(handler)
191+
with catching_logs(handler):
192+
yield captured_logs
179193

180194
@contextmanager
181195
def test(self, msg=None, **kwargs):
182196
start = time.time()
183197
precise_start = time.perf_counter()
184198
exc_info = None
185199

186-
with self._capturing_output() as captured:
200+
with self._capturing_output() as captured_output, self._capturing_logs() as captured_logs:
187201
try:
188202
yield
189203
except (Exception, OutcomeException):
@@ -200,8 +214,9 @@ def test(self, msg=None, **kwargs):
200214
sub_report = SubTestReport._from_test_report(report)
201215
sub_report.context = SubTestContext(msg, kwargs.copy())
202216

203-
captured.update_report(sub_report)
204-
217+
captured_output.update_report(sub_report)
218+
captured_logs.update_report(sub_report)
219+
205220
with self.suspend_capture_ctx():
206221
self.ihook.pytest_runtest_logreport(report=sub_report)
207222

@@ -248,6 +263,19 @@ def update_report(self, report):
248263
report.sections.append(("Captured stderr call", self.err))
249264

250265

266+
class CapturedLogs:
267+
def __init__(self, handler):
268+
self._handler = handler
269+
270+
def update_report(self, report):
271+
report.sections.append(("Captured log call", self._handler.stream.getvalue()))
272+
273+
274+
class NullCapturedLogs:
275+
def update_report(self, report):
276+
pass
277+
278+
251279
def pytest_report_to_serializable(report):
252280
if isinstance(report, SubTestReport):
253281
return report._to_json()

tests/test_subtests.py

+85
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,91 @@ def test(subtests, {fixture}):
365365
)
366366

367367

368+
class TestLogging:
369+
def create_file(self, testdir):
370+
testdir.makepyfile(
371+
"""
372+
import logging
373+
374+
def test_foo(subtests):
375+
logging.info("before")
376+
377+
with subtests.test("sub1"):
378+
print("sub1 stdout")
379+
logging.info("sub1 logging")
380+
381+
with subtests.test("sub2"):
382+
print("sub2 stdout")
383+
logging.info("sub2 logging")
384+
assert False
385+
"""
386+
)
387+
388+
def test_capturing(self, testdir):
389+
self.create_file(testdir)
390+
result = testdir.runpytest("--log-level=INFO")
391+
result.stdout.fnmatch_lines(
392+
[
393+
"*___ test_foo [[]sub2[]] __*",
394+
"*-- Captured stdout call --*",
395+
"sub2 stdout",
396+
"*-- Captured log call ---*",
397+
"INFO root:test_capturing.py:12 sub2 logging",
398+
"*== short test summary info ==*"
399+
]
400+
)
401+
402+
def test_caplog(self, testdir):
403+
testdir.makepyfile(
404+
"""
405+
import logging
406+
407+
def test(subtests, caplog):
408+
caplog.set_level(logging.INFO)
409+
logging.info("start test")
410+
411+
with subtests.test("sub1"):
412+
logging.info("inside %s", "subtest1")
413+
414+
assert len(caplog.records) == 2
415+
assert caplog.records[0].getMessage() == "start test"
416+
assert caplog.records[1].getMessage() == "inside subtest1"
417+
"""
418+
)
419+
result = testdir.runpytest()
420+
result.stdout.fnmatch_lines(
421+
[
422+
"*1 passed*",
423+
]
424+
)
425+
426+
def test_no_logging(self, testdir):
427+
testdir.makepyfile(
428+
"""
429+
import logging
430+
431+
def test(subtests):
432+
logging.info("start log line")
433+
434+
with subtests.test("sub passing"):
435+
logging.info("inside %s", "passing log line")
436+
437+
with subtests.test("sub failing"):
438+
logging.info("inside %s", "failing log line")
439+
assert False
440+
441+
logging.info("end log line")
442+
"""
443+
)
444+
result = testdir.runpytest("-p no:logging")
445+
result.stdout.fnmatch_lines(
446+
[
447+
"*1 passed*",
448+
]
449+
)
450+
result.stdout.no_fnmatch_line("*root:test_no_logging.py*log line*")
451+
452+
368453
class TestDebugging:
369454
"""Check --pdb support for subtests fixture and TestCase.subTest."""
370455

0 commit comments

Comments
 (0)