https://github.com/python/cpython/commit/0ac377ff23672ae7e2451f4cdfbe37c4efd464e7
commit: 0ac377ff23672ae7e2451f4cdfbe37c4efd464e7
branch: main
author: Malcolm Smith <[email protected]>
committer: freakboy3742 <[email protected]>
date: 2025-09-18T11:41:21+01:00
summary:
Make Android streams respect the unbuffered (`-u`) option (#138806)
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.
files:
M Lib/_android_support.py
M Lib/test/test_android.py
diff --git a/Lib/_android_support.py b/Lib/_android_support.py
index ae506f6a4b57b8..a439d03a144dd2 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 de83ce081c2790..c6c4a15a7ee34d 100644
--- a/Lib/test/test_android.py
+++ b/Lib/test/test_android.py
@@ -91,34 +91,38 @@ def tearDown(self):
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 @@ def write(s, lines=None, *, write_len=None):
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 @@ def write(s, lines=None, *, write_len=None):
# 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", [""])
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]