Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions Lib/_android_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
32 changes: 18 additions & 14 deletions Lib/test/test_android.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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")
Expand Down Expand Up @@ -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", [""])
Expand Down
Loading