diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 45fde880f..c09e4d7ee 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -34,6 +34,10 @@ * New ``pytest_make_parametrize_id`` hook. Thanks `@palaviv`_ for the PR. +* ``capsys`` and ``capfd`` now have a ``disabled()`` method, which is a context manager + that can be used to temporarily disable capture within a test. + Thanks `@nicoddemus`_ for the PR. + * New cli flag ``--fixtures-per-test`` that shows which fixtures are being used for each selected test item. Features doc strings of fixtures by default. Can also show where fixtures are defined if combined with ``-v``. diff --git a/_pytest/capture.py b/_pytest/capture.py index 3895a714a..78401e96d 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -4,6 +4,7 @@ per-test stdout/stderr capturing mechanism. """ from __future__ import with_statement +import contextlib import sys import os from tempfile import TemporaryFile @@ -146,8 +147,8 @@ class CaptureManager: def pytest_internalerror(self, excinfo): self.reset_capturings() - def suspendcapture_item(self, item, when): - out, err = self.suspendcapture() + def suspendcapture_item(self, item, when, in_=False): + out, err = self.suspendcapture(in_=in_) item.add_report_section(when, "stdout", out) item.add_report_section(when, "stderr", err) @@ -162,7 +163,7 @@ def capsys(request): """ if "capfd" in request._funcargs: raise request.raiseerror(error_capsysfderror) - request.node._capfuncarg = c = CaptureFixture(SysCapture) + request.node._capfuncarg = c = CaptureFixture(SysCapture, request) return c @pytest.fixture @@ -175,17 +176,18 @@ def capfd(request): request.raiseerror(error_capsysfderror) if not hasattr(os, 'dup'): pytest.skip("capfd funcarg needs os.dup") - request.node._capfuncarg = c = CaptureFixture(FDCapture) + request.node._capfuncarg = c = CaptureFixture(FDCapture, request) return c class CaptureFixture: - def __init__(self, captureclass): + def __init__(self, captureclass, request): self.captureclass = captureclass + self.request = request def _start(self): self._capture = MultiCapture(out=True, err=True, in_=False, - Capture=self.captureclass) + Capture=self.captureclass) self._capture.start_capturing() def close(self): @@ -200,6 +202,15 @@ class CaptureFixture: except AttributeError: return self._outerr + @contextlib.contextmanager + def disabled(self): + capmanager = self.request.config.pluginmanager.getplugin('capturemanager') + capmanager.suspendcapture_item(self.request.node, "call", in_=True) + try: + yield + finally: + capmanager.resumecapture() + def safe_text_dupfile(f, mode, default_encoding="UTF8"): """ return a open text file object that's a duplicate of f on the diff --git a/doc/en/capture.rst b/doc/en/capture.rst index 1778602cb..f46920712 100644 --- a/doc/en/capture.rst +++ b/doc/en/capture.rst @@ -115,4 +115,19 @@ same interface but allows to also capture output from libraries or subprocesses that directly write to operating system level output streams (FD1 and FD2). + +.. versionadded:: 2.10 + +To temporarily disable capture within a test, both ``capsys`` +and ``capfd`` have a ``disabled()`` method that can be used +as a context manager, disabling capture inside the ``with`` block: + +.. code-block:: python + + def test_disabling_capturing(capsys): + print('this output is captured') + with capsys.disabled(): + print('output not captured, going directly to sys.stdout') + print('this output is also captured') + .. include:: links.inc diff --git a/testing/test_capture.py b/testing/test_capture.py index 73660692b..2e69cfc85 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -480,6 +480,22 @@ class TestCaptureFixture: result = testdir.runpytest_subprocess(p) assert 'closed' not in result.stderr.str() + @pytest.mark.parametrize('fixture', ['capsys', 'capfd']) + def test_disabled_capture_fixture(self, testdir, fixture): + testdir.makepyfile(""" + def test_disabled({fixture}): + print('captured before') + with {fixture}.disabled(): + print('while capture is disabled') + print('captured after') + """.format(fixture=fixture)) + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines(""" + *while capture is disabled* + """) + assert 'captured before' not in result.stdout.str() + assert 'captured after' not in result.stdout.str() + def test_setup_failure_does_not_kill_capturing(testdir): sub1 = testdir.mkpydir("sub1")