mirror of
https://github.com/python/cpython.git
synced 2025-10-19 16:03:42 +00:00
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.
This commit is contained in:
parent
db68bfc771
commit
0ac377ff23
2 changed files with 28 additions and 20 deletions
|
@ -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.
|
||||
|
|
|
@ -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", [""])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue