Skip to content

Commit 8d8eba6

Browse files
authored
Avoid UnicodeDecodeError from command output (#2970)
* test_sync_write_decode_surrogate: utf-8 decode When SyncWrite decodes bytes as utf-8, it should replace unknown sequences with the unicode surrogate codepoint instead of crashing the program. Test case for #2969 * SyncWrite: decode with errors='surrogateescape' Avoid bubbling UnicodeDecodeError up from stream handling internals. Tox has no way of knowing that the bytestream emitted by a command will be valid utf-8, even if utf-8 is ostensibly the "correct" encoding for the stream. It's always possible for an arbitrary command to return non-utf-8 bytes, and this situation should not break tox. Fix #2969
1 parent 8a7327c commit 8d8eba6

File tree

3 files changed

+10
-1
lines changed

3 files changed

+10
-1
lines changed

docs/changelog/2969.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Instead of raising ``UnicodeDecodeError`` when command output includes non-utf-8 bytes,
2+
``tox`` will now use ``surrogateescape`` error handling to convert the unrecognized bytes
3+
to escape sequences according to :pep:`383` - by :user:`masenf`.

src/tox/execute/stream.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def colored(self) -> Iterator[None]:
100100
@property
101101
def text(self) -> str:
102102
with self._content_lock:
103-
return self._content.decode("utf-8")
103+
return self._content.decode("utf-8", errors="surrogateescape")
104104

105105
@property
106106
def content(self) -> bytearray:

tests/execute/test_stream.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,9 @@
88
def test_sync_write_repr() -> None:
99
sync_write = SyncWrite(name="a", target=None, color=Fore.RED)
1010
assert repr(sync_write) == f"SyncWrite(name='a', target=None, color={Fore.RED!r})"
11+
12+
13+
def test_sync_write_decode_surrogate() -> None:
14+
sync_write = SyncWrite(name="a", target=None)
15+
sync_write.handler(b"\xed\n")
16+
assert sync_write.text == "\udced\n"

0 commit comments

Comments
 (0)