diff --git a/changelog/7148.bugfix.rst b/changelog/7148.bugfix.rst new file mode 100644 index 00000000000..71753334c51 --- /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 f538b67eceb..a20d14fe7e3 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -537,7 +537,7 @@ def suspend_capturing(self, in_: bool = False) -> None: 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 @@ def stop_capturing(self) -> None: 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 @@ def resume_fixture(self) -> None: @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 @@ def _resume(self) -> None: 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 bc89501c73b..678bc1863c1 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -16,6 +16,7 @@ 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 @@ def test_normal(): 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): """