From 6118f8cb0e7b535e27daa3fd8e0ea6ab3816a201 Mon Sep 17 00:00:00 2001 From: Casey Brooks Date: Thu, 25 Dec 2025 20:00:11 +0000 Subject: [PATCH 1/4] fix(capture): normalize text mode flag --- src/_pytest/capture.py | 4 ++++ testing/test_capture.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 25eab7fdf14..56017113429 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -447,6 +447,10 @@ def name(self): """Ensure that file.name is a string.""" return repr(self.buffer) + @property + def mode(self): + return self.buffer.mode.replace("b", "") + def __getattr__(self, name): return getattr(object.__getattribute__(self, "buffer"), name) diff --git a/testing/test_capture.py b/testing/test_capture.py index 5d80eb63da0..0a428144bb9 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1212,6 +1212,11 @@ def test_x(): """ ) + def test_stdout_mode(self): + with self.getcapture(): + assert "b" in sys.stdout.buffer.mode + assert "b" not in sys.stdout.mode + def test_intermingling(self): with self.getcapture() as cap: os.write(1, b"1") From 7b641acb60cd66201c441d6cc66758615b2950b0 Mon Sep 17 00:00:00 2001 From: Rowan Stein Date: Thu, 25 Dec 2025 20:05:23 +0000 Subject: [PATCH 2/4] fix(capture): EncodedFile.mode use getattr for missing attribute; refine docstring --- src/_pytest/capture.py | 12 +++++++++++- testing/test_capture.py | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 56017113429..fb9de2eb2bb 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -449,7 +449,17 @@ def name(self): @property def mode(self): - return self.buffer.mode.replace("b", "") + """ + Present a text-like mode to callers. + + The underlying buffer is opened in binary mode (e.g., 'rb+', 'wb+') + during fd-level capturing. Some libraries (e.g., youtube-dl) inspect + file.mode to decide whether to write bytes or text. Since EncodedFile + expects text on Python 3, report a mode without the binary flag. + """ + m = getattr(self.buffer, "mode", "") + return m.replace("b", "") + def __getattr__(self, name): return getattr(object.__getattribute__(self, "buffer"), name) diff --git a/testing/test_capture.py b/testing/test_capture.py index 0a428144bb9..68a1ef69ee7 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1194,6 +1194,14 @@ class TestStdCaptureFD(TestStdCapture): pytestmark = needsosdup captureclass = staticmethod(StdCaptureFD) + def test_stdout_mode(self): + with self.getcapture(): + assert hasattr(sys.stdout, "buffer") + # underlying buffer is binary + assert "b" in sys.stdout.buffer.mode + # EncodedFile.mode should not include "b" + assert "b" not in sys.stdout.mode + def test_simple_only_fd(self, testdir): testdir.makepyfile( """ From 60eebf536c52f6b205ef504dd71cdc62dc57ca3a Mon Sep 17 00:00:00 2001 From: Rowan Stein Date: Thu, 25 Dec 2025 20:09:58 +0000 Subject: [PATCH 3/4] test(capture): remove duplicate test_stdout_mode in TestStdCaptureFD --- testing/test_capture.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/testing/test_capture.py b/testing/test_capture.py index 68a1ef69ee7..b0af740fbe9 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1220,16 +1220,6 @@ def test_x(): """ ) - def test_stdout_mode(self): - with self.getcapture(): - assert "b" in sys.stdout.buffer.mode - assert "b" not in sys.stdout.mode - - def test_intermingling(self): - with self.getcapture() as cap: - os.write(1, b"1") - sys.stdout.write(str(2)) - sys.stdout.flush() os.write(1, b"3") os.write(2, b"a") sys.stderr.write("b") From aa5faa9b9de2c1502207add9af8b7468a944def0 Mon Sep 17 00:00:00 2001 From: Rowan Stein Date: Thu, 25 Dec 2025 20:10:55 +0000 Subject: [PATCH 4/4] test(capture): restore TestStdCaptureFD.test_intermingling after duplicate removal --- testing/test_capture.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testing/test_capture.py b/testing/test_capture.py index b0af740fbe9..eda56618797 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1220,6 +1220,11 @@ def test_x(): """ ) + def test_intermingling(self): + with self.getcapture() as cap: + os.write(1, b"1") + sys.stdout.write(str(2)) + sys.stdout.flush() os.write(1, b"3") os.write(2, b"a") sys.stderr.write("b")