From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Mon, 29 Sep 2025 08:16:25 +0000 (+0200) Subject: [3.14] Make Android streams respect the unbuffered (`-u`) option (GH-138806) (#139108) X-Git-Tag: v3.14.0~35 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7a65a477951a541e29920832e4ab90e0b7783f21;p=thirdparty%2FPython%2Fcpython.git [3.14] Make Android streams respect the unbuffered (`-u`) option (GH-138806) (#139108) Co-authored-by: Malcolm Smith --- diff --git a/Lib/_android_support.py b/Lib/_android_support.py index ae506f6a4b57..a439d03a144d 100644 --- a/Lib/_android_support.py +++ b/Lib/_android_support.py @@ -29,15 +29,19 @@ def init_streams(android_log_write, stdout_prio, stderr_prio): global logcat logcat = Logcat(android_log_write) - - sys.stdout = TextLogStream( - stdout_prio, "python.stdout", sys.stdout.fileno()) - sys.stderr = TextLogStream( - stderr_prio, "python.stderr", sys.stderr.fileno()) + sys.stdout = TextLogStream(stdout_prio, "python.stdout", sys.stdout) + sys.stderr = TextLogStream(stderr_prio, "python.stderr", sys.stderr) class TextLogStream(io.TextIOWrapper): - def __init__(self, prio, tag, fileno=None, **kwargs): + def __init__(self, prio, tag, original=None, **kwargs): + # Respect the -u option. + if original: + kwargs.setdefault("write_through", original.write_through) + fileno = original.fileno() + else: + fileno = None + # The default is surrogateescape for stdout and backslashreplace for # stderr, but in the context of an Android log, readability is more # important than reversibility. diff --git a/Lib/test/test_android.py b/Lib/test/test_android.py index de83ce081c27..c6c4a15a7ee3 100644 --- a/Lib/test/test_android.py +++ b/Lib/test/test_android.py @@ -91,34 +91,38 @@ class TestAndroidOutput(unittest.TestCase): self.logcat_thread = None @contextmanager - def unbuffered(self, stream): - stream.reconfigure(write_through=True) + def reconfigure(self, stream, **settings): + original_settings = {key: getattr(stream, key, None) for key in settings.keys()} + stream.reconfigure(**settings) try: yield finally: - stream.reconfigure(write_through=False) + stream.reconfigure(**original_settings) - # In --verbose3 mode, sys.stdout and sys.stderr are captured, so we can't - # test them directly. Detect this mode and use some temporary streams with - # the same properties. def stream_context(self, stream_name, level): - # https://developer.android.com/ndk/reference/group/logging - prio = {"I": 4, "W": 5}[level] - stack = ExitStack() stack.enter_context(self.subTest(stream_name)) + + # In --verbose3 mode, sys.stdout and sys.stderr are captured, so we can't + # test them directly. Detect this mode and use some temporary streams with + # the same properties. stream = getattr(sys, stream_name) native_stream = getattr(sys, f"__{stream_name}__") if isinstance(stream, io.StringIO): + # https://developer.android.com/ndk/reference/group/logging + prio = {"I": 4, "W": 5}[level] stack.enter_context( patch( f"sys.{stream_name}", - TextLogStream( - prio, f"python.{stream_name}", native_stream.fileno(), - errors="backslashreplace" + stream := TextLogStream( + prio, f"python.{stream_name}", native_stream, ), ) ) + + # The tests assume the stream is initially buffered. + stack.enter_context(self.reconfigure(stream, write_through=False)) + return stack def test_str(self): @@ -145,7 +149,7 @@ class TestAndroidOutput(unittest.TestCase): self.assert_logs(level, tag, lines) # Single-line messages, - with self.unbuffered(stream): + with self.reconfigure(stream, write_through=True): write("", []) write("a") @@ -192,7 +196,7 @@ class TestAndroidOutput(unittest.TestCase): # However, buffering can be turned off completely if you want a # flush after every write. - with self.unbuffered(stream): + with self.reconfigure(stream, write_through=True): write("\nx", ["", "x"]) write("\na\n", ["", "a"]) write("\n", [""])