From 273670b2a204f950df93d87baff26b04a47c0400 Mon Sep 17 00:00:00 2001 From: Tyler Richard Date: Wed, 20 Dec 2017 10:28:50 -0800 Subject: [PATCH 1/3] Fixes capfd so data is available after teardown. --- src/_pytest/capture.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index c84ba825e..b2ab4e57a 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -188,7 +188,6 @@ class CaptureManager(object): 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 From c24c7e75e26516e032c750d49cec2b840fb128cf Mon Sep 17 00:00:00 2001 From: Tyler Richard Date: Tue, 26 Dec 2017 10:25:25 -0800 Subject: [PATCH 2/3] Added regression test for capfd in a fixture --- testing/test_capture.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/testing/test_capture.py b/testing/test_capture.py index 782971af0..da0fb5369 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1318,6 +1318,26 @@ def test_error_attribute_issue555(testdir): 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), From f4c5994d27287e1ac2440a4ae17032f272208cb3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 18 Aug 2018 14:32:10 -0300 Subject: [PATCH 3/3] Fixtures during teardown can use capsys and capfd to get output from tests Fix #3033 --- changelog/3033.bugfix.rst | 1 + src/_pytest/capture.py | 36 ++++++++++++++++++++++------- testing/test_capture.py | 48 +++++++++++++++++++++++---------------- 3 files changed, 57 insertions(+), 28 deletions(-) create mode 100644 changelog/3033.bugfix.rst diff --git a/changelog/3033.bugfix.rst b/changelog/3033.bugfix.rst new file mode 100644 index 000000000..3fcd9dd11 --- /dev/null +++ b/changelog/3033.bugfix.rst @@ -0,0 +1 @@ +Fixtures during teardown can again use ``capsys`` and ``cafd`` to inspect output captured during tests. diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index b2ab4e57a..deabcac8d 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -188,6 +188,7 @@ class CaptureManager(object): 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 @@ -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( @@ -316,20 +320,26 @@ class CaptureFixture(object): 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): @@ -462,6 +472,7 @@ class MultiCapture(object): class NoCapture(object): + EMPTY_BUFFER = None __init__ = start = done = suspend = resume = lambda *args: None @@ -471,6 +482,8 @@ class FDCaptureBinary(object): snap() produces `bytes` """ + EMPTY_BUFFER = bytes() + def __init__(self, targetfd, tmpfile=None): self.targetfd = targetfd try: @@ -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) @@ -553,6 +568,9 @@ class FDCapture(FDCaptureBinary): class SysCapture(object): + + EMPTY_BUFFER = str() + def __init__(self, fd, tmpfile=None): name = patchsysdict[fd] self._old = getattr(sys, name) @@ -590,6 +608,8 @@ class SysCapture(object): class SysCaptureBinary(SysCapture): + EMPTY_BUFFER = bytes() + def snap(self): res = self.tmpfile.buffer.getvalue() self.tmpfile.seek(0) diff --git a/testing/test_capture.py b/testing/test_capture.py index da0fb5369..be3385dfb 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -647,6 +647,34 @@ class TestCaptureFixture(object): 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") @@ -1318,26 +1346,6 @@ def test_error_attribute_issue555(testdir): 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),