diff --git a/docs/changelog/2969.bugfix.rst b/docs/changelog/2969.bugfix.rst new file mode 100644 index 000000000..29464086e --- /dev/null +++ b/docs/changelog/2969.bugfix.rst @@ -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`. diff --git a/src/tox/execute/stream.py b/src/tox/execute/stream.py index 980c97ffd..28c66be64 100644 --- a/src/tox/execute/stream.py +++ b/src/tox/execute/stream.py @@ -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: diff --git a/tests/execute/test_stream.py b/tests/execute/test_stream.py index 6e870826d..fe9dd5637 100644 --- a/tests/execute/test_stream.py +++ b/tests/execute/test_stream.py @@ -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"