From 9a879ee23e96eba2e07943eac4e98d896655e3fb Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 24 Aug 2020 12:11:09 +0300 Subject: [PATCH] Merge pull request #7651 from bluetech/capture-safe-disable capture: fix disabled()/global_and_fixture_disabled() enabling capturing when it was disabled (cherry picked from commit bb38ae9c5232b609a54c6b51f6c8077f5bee6bab) --- changelog/7148.bugfix.rst | 1 + src/_pytest/capture.py | 24 +++++++++++++++++++++--- testing/test_capture.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 changelog/7148.bugfix.rst diff --git a/changelog/7148.bugfix.rst b/changelog/7148.bugfix.rst new file mode 100644 index 000000000..71753334c --- /dev/null +++ b/changelog/7148.bugfix.rst @@ -0,0 +1 @@ +Fixed ``--log-cli`` potentially causing unrelated ``print`` output to be swallowed. diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index f538b67ec..a20d14fe7 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -537,7 +537,7 @@ class MultiCapture: self._in_suspended = True def resume_capturing(self) -> None: - self._state = "resumed" + self._state = "started" if self.out: self.out.resume() if self.err: @@ -558,6 +558,10 @@ class MultiCapture: if self.in_: self.in_.done() + def is_started(self) -> bool: + """Whether actively capturing -- not suspended or stopped.""" + return self._state == "started" + def readouterr(self) -> CaptureResult: if self.out: out = self.out.snap() @@ -697,11 +701,19 @@ class CaptureManager: @contextlib.contextmanager def global_and_fixture_disabled(self) -> Generator[None, None, None]: """Context manager to temporarily disable global and current fixture capturing.""" - self.suspend() + do_fixture = self._capture_fixture and self._capture_fixture._is_started() + if do_fixture: + self.suspend_fixture() + do_global = self._global_capturing and self._global_capturing.is_started() + if do_global: + self.suspend_global_capture() try: yield finally: - self.resume() + if do_global: + self.resume_global_capture() + if do_fixture: + self.resume_fixture() @contextlib.contextmanager def item_capture(self, when: str, item: Item) -> Generator[None, None, None]: @@ -810,6 +822,12 @@ class CaptureFixture: if self._capture is not None: self._capture.resume_capturing() + def _is_started(self) -> bool: + """Whether actively capturing -- not disabled or closed.""" + if self._capture is not None: + return self._capture.is_started() + return False + @contextlib.contextmanager def disabled(self) -> Generator[None, None, None]: """Temporarily disables capture while inside the 'with' block.""" diff --git a/testing/test_capture.py b/testing/test_capture.py index bc89501c7..678bc1863 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -16,6 +16,7 @@ from _pytest.capture import _get_multicapture from _pytest.capture import CaptureManager from _pytest.capture import MultiCapture from _pytest.config import ExitCode +from _pytest.pytester import Testdir # note: py.io capture tests where copied from # pylib 1.4.20.dev2 (rev 13d9af95547e) @@ -633,6 +634,34 @@ class TestCaptureFixture: else: result.stdout.no_fnmatch_line("*test_normal executed*") + def test_disabled_capture_fixture_twice(self, testdir: Testdir) -> None: + """Test that an inner disabled() exit doesn't undo an outer disabled(). + + Issue #7148. + """ + testdir.makepyfile( + """ + def test_disabled(capfd): + print('captured before') + with capfd.disabled(): + print('while capture is disabled 1') + with capfd.disabled(): + print('while capture is disabled 2') + print('while capture is disabled 1 after') + print('captured after') + assert capfd.readouterr() == ('captured before\\ncaptured after\\n', '') + """ + ) + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines( + [ + "*while capture is disabled 1", + "*while capture is disabled 2", + "*while capture is disabled 1 after", + ], + consecutive=True, + ) + @pytest.mark.parametrize("fixture", ["capsys", "capfd"]) def test_fixture_use_by_other_fixtures(self, testdir, fixture): """