Skip to content
Open
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
3 changes: 3 additions & 0 deletions changelog/13750(pytest).bugfix.rst
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion src/pluggy/_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ def after(
kwargs: Mapping[str, object],
) -> None:
if outcome.exception is None:
hooktrace("finish", hook_name, "-->", outcome.get_result())
hooktrace("finish", hook_name, "-->", repr(outcome.get_result()))
hooktrace.root.indent -= 1

return self.add_hookcall_monitoring(before, after)
Expand Down
4 changes: 2 additions & 2 deletions src/pluggy/_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ def _format_message(self, tags: Sequence[str], args: Sequence[object]) -> str:
else:
extra = {}

content = " ".join(map(str, args))
content = " ".join(repr(a) for a in args)
indent = " " * self.indent

lines = [f"{indent}{content} [{':'.join(tags)}]\n"]

for name, value in extra.items():
lines.append(f"{indent} {name}: {value}\n")
lines.append(f"{indent} {name}: {value!r}\n")

return "".join(lines)

Expand Down
30 changes: 20 additions & 10 deletions testing/test_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ def test_simple(rootlogger: TagTracer) -> None:
rootlogger.setwriter(out.append)
log("world")
assert len(out) == 1
assert out[0] == "world [pytest]\n"
assert out[0] == "'world' [pytest]\n"
sublog = log.get("collection")
sublog("hello")
assert out[1] == "hello [pytest:collection]\n"
assert out[1] == "'hello' [pytest:collection]\n"


def test_indent(rootlogger: TagTracer) -> None:
Expand All @@ -39,13 +39,13 @@ def test_indent(rootlogger: TagTracer) -> None:
assert len(out) == 7
names = [x[: x.rfind(" [")] for x in out]
assert names == [
"hello",
" line1",
" line2",
" line3",
" line4",
" line5",
"last",
"'hello'",
" 'line1'",
" 'line2'",
" 'line3'",
" 'line4'",
" 'line5'",
"'last'",
]


Expand All @@ -54,7 +54,7 @@ def test_readable_output_dictargs(rootlogger: TagTracer) -> None:
assert out == "1 [test]\n"

out2 = rootlogger._format_message(["test"], ["test", {"a": 1}])
assert out2 == "test [test]\n a: 1\n"
assert out2 == "'test' [test]\n a: 1\n"


def test_setprocessor(rootlogger: TagTracer) -> None:
Expand All @@ -75,3 +75,13 @@ 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 "hello \\ud800 world" in out[0]