diff --git a/changelog/3033.bugfix.rst b/changelog/3033.bugfix.rst new file mode 100644 index 00000000000..3fcd9dd116c --- /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 b2ab4e57abb..deabcac8de4 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -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 @@ -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 @@ 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): @@ -462,6 +472,7 @@ def readouterr(self): 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 @@ def snap(self): class SysCapture(object): + + EMPTY_BUFFER = str() + def __init__(self, fd, tmpfile=None): name = patchsysdict[fd] self._old = getattr(sys, name) @@ -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) diff --git a/testing/test_capture.py b/testing/test_capture.py index da0fb5369f5..be3385dfbcc 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -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") @@ -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),