From 4dc45cd467e821a93af462c3ab4a6295b69c8c87 Mon Sep 17 00:00:00 2001 From: Ronak Bhalgami Date: Tue, 7 Oct 2025 12:58:23 +0530 Subject: [PATCH 1/2] tests: add regression test for capteesys with -s; fix tee-only behavior --- src/_pytest/capture.py | 22 ++++++++++++++-------- testing/test_capteesys_issue.py | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 testing/test_capteesys_issue.py diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 6d98676be5f..33659fac952 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -1049,14 +1049,20 @@ def test_output(capteesys): assert captured.out == "hello\n" """ capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture( - SysCapture, request, config=dict(tee=True), _ispytest=True - ) - capman.set_fixture(capture_fixture) - capture_fixture._start() - yield capture_fixture - capture_fixture.close() - capman.unset_fixture() + if capman.is_globally_capturing(): + capture_fixture = CaptureFixture( + SysCapture, request, config=dict(tee=True), _ispytest=True + ) + capman.set_fixture(capture_fixture) + capture_fixture._start() + yield capture_fixture + capture_fixture.close() + capman.unset_fixture() + else: + # capteesys does nothing when global capturing is disabled. + # This is so that the "tee" part of cap-tee-sys is not + # implemented without the "cap" part. + yield CaptureFixture(SysCapture, request, _ispytest=True) @fixture diff --git a/testing/test_capteesys_issue.py b/testing/test_capteesys_issue.py new file mode 100644 index 00000000000..b06f217470f --- /dev/null +++ b/testing/test_capteesys_issue.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +import sys +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from _pytest.capture import CaptureFixture + from _pytest.fixtures import SubRequest + + +def test_dummy_test_with_traceback( + request: SubRequest, capteesys: CaptureFixture[str] +) -> None: + print("Hello world stdout", flush=True) + print("Hello world stderr", file=sys.stderr, flush=True) From 8985ca201f2c45c0559d11a2a648bea3b862079e Mon Sep 17 00:00:00 2001 From: Ronak Bhalgami Date: Fri, 10 Oct 2025 22:27:24 +0530 Subject: [PATCH 2/2] tests(capture): add capteesys regression when -s; remove standalone test, integrate into test_capture.py --- testing/test_capteesys_issue.py | 16 ---------------- testing/test_capture.py | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 16 deletions(-) delete mode 100644 testing/test_capteesys_issue.py diff --git a/testing/test_capteesys_issue.py b/testing/test_capteesys_issue.py deleted file mode 100644 index b06f217470f..00000000000 --- a/testing/test_capteesys_issue.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import annotations - -import sys -from typing import TYPE_CHECKING - - -if TYPE_CHECKING: - from _pytest.capture import CaptureFixture - from _pytest.fixtures import SubRequest - - -def test_dummy_test_with_traceback( - request: SubRequest, capteesys: CaptureFixture[str] -) -> None: - print("Hello world stdout", flush=True) - print("Hello world stderr", file=sys.stderr, flush=True) diff --git a/testing/test_capture.py b/testing/test_capture.py index ffbc440e3bf..2ebd54b11f9 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -478,6 +478,30 @@ def test_one(capteesys): result.stdout.fnmatch_lines(["sTdoUt", "sTdeRr"]) # no tee, just reported assert not result.stderr.lines + def test_capteesys_no_global_capture(self, pytester: Pytester) -> None: + """When global capture is disabled (-s), capteesys should not duplicate output. + + It should pass output straight through (printed once) and capteesys.readouterr() + should return empty strings since no per-test capture is active. + """ + p = pytester.makepyfile( + """\ + import sys + + def test_one(capteesys): + print("sTdoUt") + print("sTdeRr", file=sys.stderr) + out, err = capteesys.readouterr() + assert out == "" + assert err == "" + """ + ) + # Run with -s to disable global capture; ensure each line appears exactly once + result = pytester.runpytest_subprocess(p, "-s", "--quiet", "--quiet") + assert result.ret == ExitCode.OK + assert result.stdout.str().count("sTdoUt") == 1 + assert result.stderr.str().count("sTdeRr") == 1 + def test_capsyscapfd(self, pytester: Pytester) -> None: p = pytester.makepyfile( """\