diff --git a/changelog/13750(pytest).bugfix.rst b/changelog/13750(pytest).bugfix.rst new file mode 100644 index 00000000..62d2dc7e --- /dev/null +++ b/changelog/13750(pytest).bugfix.rst @@ -0,0 +1,3 @@ +This change addresses an issue in pluggy that occured when running pytest with any pluggy tracing enabled when parametrized values contained surrogate escape characters. +Before, pluggy attempted to write trace messages using UTF-8 enconding, which fails for lone surrogates. Tracing now encodes lone surrogates with errors="replace" in order +to ensure that trace logging will not crash hook execution in the future. diff --git a/src/pluggy/_tracing.py b/src/pluggy/_tracing.py index f0b36db1..6b471600 100644 --- a/src/pluggy/_tracing.py +++ b/src/pluggy/_tracing.py @@ -41,7 +41,8 @@ def _format_message(self, tags: Sequence[str], args: Sequence[object]) -> str: def _processmessage(self, tags: tuple[str, ...], args: tuple[object, ...]) -> None: if self._writer is not None and args: - self._writer(self._format_message(tags, args)) + msg = self._format_message(tags, args) + self._writer(msg.encode("utf-8", "replace").decode("utf-8")) try: processor = self._tags2proc[tags] except KeyError: diff --git a/testing/test_tracer.py b/testing/test_tracer.py index c90c78f1..49744aa0 100644 --- a/testing/test_tracer.py +++ b/testing/test_tracer.py @@ -75,3 +75,28 @@ def test_setprocessor(rootlogger: TagTracer) -> None: log2("seen") tags, args = l2[0] assert args == ("seen",) + + +def test_unicode_surrogate_handling(rootlogger: TagTracer) -> None: + out: list[str] = [] + rootlogger.setwriter(out.append) + log = rootlogger.get("pytest") + s = "hello \ud800 world" + log(s) + assert len(out) == 1 + assert "\ud800" not in out + assert "hello ? world" in out[0] + + +def test_unicode_surrogate_handling_2(rootlogger: TagTracer) -> None: + out: list[str] = [] + rootlogger.setwriter(out.append) + log = rootlogger.get("pytest") + + bad = b"\xed\xa0\x80".decode("utf-8", "surrogatepass") + + log(bad) + + assert len(out) == 1 + assert "\ud800" not in out[0] + assert "?" in out[0]