Skip to content

Commit

Permalink
Avoid UnicodeDecodeError from command output (#2970)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
masenf committed Apr 5, 2023
1 parent 8a7327c commit 8d8eba6
Show file tree
Hide file tree
Showing 3 changed files with 10 additions and 1 deletion.
3 changes: 3 additions & 0 deletions docs/changelog/2969.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Instead of raising ``UnicodeDecodeError`` when command output includes non-utf-8 bytes,
``tox`` will now use ``surrogateescape`` error handling to convert the unrecognized bytes
to escape sequences according to :pep:`383` - by :user:`masenf`.
2 changes: 1 addition & 1 deletion src/tox/execute/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def colored(self) -> Iterator[None]:
@property
def text(self) -> str:
with self._content_lock:
return self._content.decode("utf-8")
return self._content.decode("utf-8", errors="surrogateescape")

@property
def content(self) -> bytearray:
Expand Down
6 changes: 6 additions & 0 deletions tests/execute/test_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@
def test_sync_write_repr() -> None:
sync_write = SyncWrite(name="a", target=None, color=Fore.RED)
assert repr(sync_write) == f"SyncWrite(name='a', target=None, color={Fore.RED!r})"


def test_sync_write_decode_surrogate() -> None:
sync_write = SyncWrite(name="a", target=None)
sync_write.handler(b"\xed\n")
assert sync_write.text == "\udced\n"

0 comments on commit 8d8eba6

Please sign in to comment.