Skip to content

Commit

Permalink
capsys and capfd can again inspect output during teardown of other fi…
Browse files Browse the repository at this point in the history
…xtures.

Fix pytest-dev#3033
  • Loading branch information
nicoddemus committed Aug 18, 2018
1 parent c24c7e7 commit 3417fda
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 28 deletions.
1 change: 1 addition & 0 deletions changelog/3033.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``capsys`` and ``cafd`` can again inspect captured output during fixture teardown.
36 changes: 28 additions & 8 deletions src/_pytest/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def pytest_runtest_call(self, item):
def pytest_runtest_teardown(self, item):
self._current_item = item
self.resume_global_capture()
self.activate_fixture(item)
yield
self.suspend_capture_item(item, "teardown")
self._current_item = None
Expand Down Expand Up @@ -308,6 +309,9 @@ class CaptureFixture(object):
def __init__(self, captureclass, request):
self.captureclass = captureclass
self.request = request
self._capture = None
self._captured_out = self.captureclass.EMPTY_BUFFER
self._captured_err = self.captureclass.EMPTY_BUFFER

def _start(self):
self._capture = MultiCapture(
Expand All @@ -316,20 +320,26 @@ def _start(self):
self._capture.start_capturing()

def close(self):
cap = self.__dict__.pop("_capture", None)
if cap is not None:
self._outerr = cap.pop_outerr_to_orig()
cap.stop_capturing()
if self._capture is not None:
out, err = self._capture.pop_outerr_to_orig()
self._captured_out += out
self._captured_err += err
self._capture.stop_capturing()
self._capture = None

def readouterr(self):
"""Read and return the captured output so far, resetting the internal buffer.
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
"""
try:
return self._capture.readouterr()
except AttributeError:
return self._outerr
captured_out, captured_err = self._captured_out, self._captured_err
if self._capture is not None:
out, err = self._capture.readouterr()
captured_out += out
captured_err += err
self._captured_out = self.captureclass.EMPTY_BUFFER
self._captured_err = self.captureclass.EMPTY_BUFFER
return CaptureResult(captured_out, captured_err)

@contextlib.contextmanager
def _suspend(self):
Expand Down Expand Up @@ -462,6 +472,7 @@ def readouterr(self):


class NoCapture(object):
EMPTY_BUFFER = None
__init__ = start = done = suspend = resume = lambda *args: None


Expand All @@ -471,6 +482,8 @@ class FDCaptureBinary(object):
snap() produces `bytes`
"""

EMPTY_BUFFER = bytes()

def __init__(self, targetfd, tmpfile=None):
self.targetfd = targetfd
try:
Expand Down Expand Up @@ -544,6 +557,8 @@ class FDCapture(FDCaptureBinary):
snap() produces text
"""

EMPTY_BUFFER = str()

def snap(self):
res = FDCaptureBinary.snap(self)
enc = getattr(self.tmpfile, "encoding", None)
Expand All @@ -553,6 +568,9 @@ def snap(self):


class SysCapture(object):

EMPTY_BUFFER = str()

def __init__(self, fd, tmpfile=None):
name = patchsysdict[fd]
self._old = getattr(sys, name)
Expand Down Expand Up @@ -590,6 +608,8 @@ def writeorg(self, data):


class SysCaptureBinary(SysCapture):
EMPTY_BUFFER = bytes()

def snap(self):
res = self.tmpfile.buffer.getvalue()
self.tmpfile.seek(0)
Expand Down
48 changes: 28 additions & 20 deletions testing/test_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,34 @@ def test_captured_print(captured_print):
assert "stdout contents begin" not in result.stdout.str()
assert "stderr contents begin" not in result.stdout.str()

@pytest.mark.parametrize("cap", ["capsys", "capfd"])
def test_fixture_use_by_other_fixtures_teardown(self, testdir, cap):
"""Ensure we can access setup and teardown buffers from teardown when using capsys/capfd (##3033)"""
testdir.makepyfile(
"""
import sys
import pytest
import os
@pytest.fixture()
def fix({cap}):
print("setup out")
sys.stderr.write("setup err\\n")
yield
out, err = {cap}.readouterr()
assert out == 'setup out\\ncall out\\n'
assert err == 'setup err\\ncall err\\n'
def test_a(fix):
print("call out")
sys.stderr.write("call err\\n")
""".format(
cap=cap
)
)
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)


def test_setup_failure_does_not_kill_capturing(testdir):
sub1 = testdir.mkpydir("sub1")
Expand Down Expand Up @@ -1318,26 +1346,6 @@ def test_capattr():
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)

def test_capfd_after_test(testdir):
testdir.makepyfile("""
import sys
import pytest
import os
@pytest.fixture()
def fix(capfd):
yield
out, err = capfd.readouterr()
assert out == 'lolcatz' + os.linesep
assert err == 'err'
def test_a(fix):
print("lolcatz")
sys.stderr.write("err")
""")
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)


@pytest.mark.skipif(
not sys.platform.startswith("win") and sys.version_info[:2] >= (3, 6),
Expand Down

0 comments on commit 3417fda

Please sign in to comment.