]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] Make Android streams respect the unbuffered (`-u`) option (GH-138806) (#139110)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Thu, 18 Sep 2025 11:45:03 +0000 (13:45 +0200)
committerGitHub <noreply@github.com>
Thu, 18 Sep 2025 11:45:03 +0000 (12:45 +0100)
Android pipes stdout/stderr to the log, which means every write to the log
becomes a separate log line. As a result, most practical uses of stdout/stderr
should be buffered; but it doesn't hurt to preserve unbuffered handling in case
it's useful.
(cherry picked from commit 0ac377ff23672ae7e2451f4cdfbe37c4efd464e7)

Co-authored-by: Malcolm Smith <smith@chaquo.com>
Lib/_android_support.py
Lib/test/test_android.py

index ae506f6a4b57b899600b97ac79a4f04a0f7297e2..a439d03a144dd22cdfa8a17e8b250fb51d3deb12 100644 (file)
@@ -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.
index b583328a253890aad42e2803a1be8d35acb9ef6e..6e6768e37e8e2c34f61f0fcecadffe00af09f0ee 100644 (file)
@@ -95,34 +95,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):
@@ -149,7 +153,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")
@@ -196,7 +200,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", [""])