From 329740e8057d184c0f35e51a17413ac7306579a8 Mon Sep 17 00:00:00 2001 From: Friday Date: Wed, 18 Feb 2026 00:33:10 +0000 Subject: [PATCH 1/2] Fix capteesys producing doubled output with --capture=no When capteesys is used with -s (--capture=no), output was printed twice: once by TeeCaptureIO teeing to the original stream in real-time, and again by pop_outerr_to_orig() writing the buffered content back via writeorg() on close. Skip the writeorg() call when tee is enabled, since the output was already delivered live. Fixes #13784. --- changelog/13784.bugfix.rst | 1 + src/_pytest/capture.py | 9 ++++++++- testing/test_capture.py | 20 ++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 changelog/13784.bugfix.rst diff --git a/changelog/13784.bugfix.rst b/changelog/13784.bugfix.rst new file mode 100644 index 00000000000..a2f4cf54327 --- /dev/null +++ b/changelog/13784.bugfix.rst @@ -0,0 +1 @@ +Fixed ``capteesys`` producing doubled output when used with ``--capture=no`` (``-s``). diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 6d98676be5f..f9f8f9e4a28 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -944,7 +944,14 @@ def _start(self) -> None: def close(self) -> None: if self._capture is not None: - out, err = self._capture.pop_outerr_to_orig() + if self._config.get("tee"): + # When tee is enabled, output was already written to the + # original stream in real-time by TeeCaptureIO. Using + # pop_outerr_to_orig() would write it a second time via + # writeorg(), causing doubled output (see #13784). + out, err = self._capture.readouterr() + else: + out, err = self._capture.pop_outerr_to_orig() self._captured_out += out self._captured_err += err self._capture.stop_capturing() diff --git a/testing/test_capture.py b/testing/test_capture.py index 11fd18f08ff..45ab1e05e5f 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -478,6 +478,26 @@ def test_one(capteesys): result.stdout.fnmatch_lines(["sTdoUt", "sTdeRr"]) # no tee, just reported assert not result.stderr.lines + def test_capteesys_no_double_output_with_capture_no( + self, pytester: Pytester + ) -> None: + """capteesys with --capture=no should not produce doubled output (#13784).""" + p = pytester.makepyfile( + """\ + def test_one(capteesys): + print("hello world stdout") + out, err = capteesys.readouterr() + assert out == "hello world stdout\\n" + """ + ) + result = pytester.runpytest(p, "--quiet", "--quiet", "-rN", "-s") + assert result.ret == ExitCode.OK + # "hello world stdout" should appear exactly once, not twice. + count = result.stdout.lines.count("hello world stdout") + assert count == 1, ( + f"Expected 'hello world stdout' once, but found {count} times" + ) + def test_capsyscapfd(self, pytester: Pytester) -> None: p = pytester.makepyfile( """\ From f577edc8daa3b1f18ec54f348c4f34b3799e8e43 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:33:48 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/test_capture.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_capture.py b/testing/test_capture.py index 45ab1e05e5f..3083d2d7a37 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -481,7 +481,7 @@ def test_one(capteesys): def test_capteesys_no_double_output_with_capture_no( self, pytester: Pytester ) -> None: - """capteesys with --capture=no should not produce doubled output (#13784).""" + """Capteesys with --capture=no should not produce doubled output (#13784).""" p = pytester.makepyfile( """\ def test_one(capteesys):