Skip to content

Commit 532c59f

Browse files
mhsmithmiss-islington
authored andcommitted
Make Android streams respect the unbuffered (-u) option (pythonGH-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. (cherry picked from commit 0ac377f) Co-authored-by: Malcolm Smith <[email protected]>
1 parent fa6dbb1 commit 532c59f

File tree

2 files changed

+28
-20
lines changed

2 files changed

+28
-20
lines changed

Lib/_android_support.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,19 @@ def init_streams(android_log_write, stdout_prio, stderr_prio):
2929

3030
global logcat
3131
logcat = Logcat(android_log_write)
32-
33-
sys.stdout = TextLogStream(
34-
stdout_prio, "python.stdout", sys.stdout.fileno())
35-
sys.stderr = TextLogStream(
36-
stderr_prio, "python.stderr", sys.stderr.fileno())
32+
sys.stdout = TextLogStream(stdout_prio, "python.stdout", sys.stdout)
33+
sys.stderr = TextLogStream(stderr_prio, "python.stderr", sys.stderr)
3734

3835

3936
class TextLogStream(io.TextIOWrapper):
40-
def __init__(self, prio, tag, fileno=None, **kwargs):
37+
def __init__(self, prio, tag, original=None, **kwargs):
38+
# Respect the -u option.
39+
if original:
40+
kwargs.setdefault("write_through", original.write_through)
41+
fileno = original.fileno()
42+
else:
43+
fileno = None
44+
4145
# The default is surrogateescape for stdout and backslashreplace for
4246
# stderr, but in the context of an Android log, readability is more
4347
# important than reversibility.

Lib/test/test_android.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,34 +95,38 @@ def tearDown(self):
9595
self.logcat_thread = None
9696

9797
@contextmanager
98-
def unbuffered(self, stream):
99-
stream.reconfigure(write_through=True)
98+
def reconfigure(self, stream, **settings):
99+
original_settings = {key: getattr(stream, key, None) for key in settings.keys()}
100+
stream.reconfigure(**settings)
100101
try:
101102
yield
102103
finally:
103-
stream.reconfigure(write_through=False)
104+
stream.reconfigure(**original_settings)
104105

105-
# In --verbose3 mode, sys.stdout and sys.stderr are captured, so we can't
106-
# test them directly. Detect this mode and use some temporary streams with
107-
# the same properties.
108106
def stream_context(self, stream_name, level):
109-
# https://developer.android.com/ndk/reference/group/logging
110-
prio = {"I": 4, "W": 5}[level]
111-
112107
stack = ExitStack()
113108
stack.enter_context(self.subTest(stream_name))
109+
110+
# In --verbose3 mode, sys.stdout and sys.stderr are captured, so we can't
111+
# test them directly. Detect this mode and use some temporary streams with
112+
# the same properties.
114113
stream = getattr(sys, stream_name)
115114
native_stream = getattr(sys, f"__{stream_name}__")
116115
if isinstance(stream, io.StringIO):
116+
# https://developer.android.com/ndk/reference/group/logging
117+
prio = {"I": 4, "W": 5}[level]
117118
stack.enter_context(
118119
patch(
119120
f"sys.{stream_name}",
120-
TextLogStream(
121-
prio, f"python.{stream_name}", native_stream.fileno(),
122-
errors="backslashreplace"
121+
stream := TextLogStream(
122+
prio, f"python.{stream_name}", native_stream,
123123
),
124124
)
125125
)
126+
127+
# The tests assume the stream is initially buffered.
128+
stack.enter_context(self.reconfigure(stream, write_through=False))
129+
126130
return stack
127131

128132
def test_str(self):
@@ -149,7 +153,7 @@ def write(s, lines=None, *, write_len=None):
149153
self.assert_logs(level, tag, lines)
150154

151155
# Single-line messages,
152-
with self.unbuffered(stream):
156+
with self.reconfigure(stream, write_through=True):
153157
write("", [])
154158

155159
write("a")
@@ -196,7 +200,7 @@ def write(s, lines=None, *, write_len=None):
196200

197201
# However, buffering can be turned off completely if you want a
198202
# flush after every write.
199-
with self.unbuffered(stream):
203+
with self.reconfigure(stream, write_through=True):
200204
write("\nx", ["", "x"])
201205
write("\na\n", ["", "a"])
202206
write("\n", [""])

0 commit comments

Comments
 (0)