Add detail to traced values where str() is ambiguous - #729
RonnyPfannschmidt wants to merge 3 commits into
Conversation
566041a to
6015a75
Compare
6015a75 to
5831d00
Compare
Trace output stays str() based, because that is what makes it readable, but str() hides things a reader needs often enough to be worth fixing case by case: - an empty string is indistinguishable from no value at all, which is exactly wrong for a comparison trace showing left and right - a string carrying whitespace has no visible boundaries - an IntEnum prints as a bare number, losing the member name - a path prints as text, so a PosixPath and a py.path.local argument pointing at the same place look identical - a multi line value runs into column 0 and reads as a trace line of its own rather than as the value of its key So values that read unambiguously as themselves -- a non empty printable string without spaces, and every type whose repr adds nothing -- stay bare, and the rest gain quotes, their type, or a block. Multi line values are drawn as a box, each line prefixed with | and the last with \\, so the extent of the value is visible at a glance. Measured on a real pytest --debug run, this changes 45 of 1329 trace lines, against 216 for rendering every value with repr(). Every changed line carries information the previous rendering dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Code <noreply@anthropic.com>
_safe_repr is only reached for enums, paths and quoted strings, which all have working reprs in the existing tests, so its guards were dead in coverage. A path-like with a broken repr is the pytest-dev#424 scenario applied to a value the heuristic sends through repr. _tracing.py is back at 100% statement and branch coverage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Code <noreply@anthropic.com>
5831d00 to
2143bb6
Compare
| Traced values now gain detail where ``str()`` is ambiguous: an empty string or a string | ||
| carrying whitespace is quoted, an enum member shows its name, and a path shows its type, | ||
| so that two arguments pointing at the same place are distinguishable. Values that read | ||
| unambiguously as themselves are unchanged, and a value spanning several lines is drawn | ||
| as a block attached to its key instead of running into the surrounding trace. |
There was a problem hiding this comment.
Too verbose IMO. My suggestion to write it in human language:
| Traced values now gain detail where ``str()`` is ambiguous: an empty string or a string | |
| carrying whitespace is quoted, an enum member shows its name, and a path shows its type, | |
| so that two arguments pointing at the same place are distinguishable. Values that read | |
| unambiguously as themselves are unchanged, and a value spanning several lines is drawn | |
| as a block attached to its key instead of running into the surrounding trace. | |
| Tracing now displays details with more detail or quoting when ``str()`` is ambiguous (like an empty string | |
| or string containing whitespace, enums and paths). | |
| A value spanning several lines is now displayed as a block instead of running into the surrounding trace. |
| Values are rendered with :func:`str` wherever that reads unambiguously, and with | ||
| :func:`repr` where it does not: an empty string, a string carrying whitespace, | ||
| an enum member, or a path, whose type is otherwise easy to lose. A value | ||
| spanning several lines is drawn as a block so that it stays attached to its key | ||
| instead of running into the surrounding trace. |
There was a problem hiding this comment.
The example seems useful but I think this paragraph is unnecessary, would just remove it.
| def _format_block(indent: str, text: str) -> list[str]: | ||
| """Draw a multi line value as a box, so it reads as one value. | ||
|
|
||
| The left edge marks every line as continuation, and the final ``\\`` |
There was a problem hiding this comment.
Is the \ for the final line conventional? To me it seems confusing (I don't know what it means). I think the indentation is sufficient to indicate when the block ends?
| def _format_block(indent: str, text: str) -> list[str]: | ||
| """Draw a multi line value as a box, so it reads as one value. | ||
|
|
||
| The left edge marks every line as continuation, and the final ``\\`` |
There was a problem hiding this comment.
In plaintext the \\ is a bit confusing, would make the docstring raw and then can write a single \
| around it. | ||
| """ | ||
| body = text.split("\n") | ||
| edges = ["|"] * (len(body) - 1) + ["\\"] |
There was a problem hiding this comment.
Seems a bit inefficient to create the entire list just for this, maybe replace with an if? But it's OK if you prefer it
| Most values keep their plain ``str`` rendering, which is what makes a trace | ||
| readable. ``repr`` is used only where ``str`` hides something the reader | ||
| needs: the type of a path, the name of an enum member, or the boundaries of | ||
| a string that is empty or carries whitespace. |
There was a problem hiding this comment.
Would remove this paragraph as too verbose. The code itself is clear enough I'd say.
|
Verified the three rendering rules on the branch (
The trigger is specifically For contrast, on The fix is to test the enum/PathLike case first. I tried it: over A bare CR is still emitted inline. The bare-CR case is the one that matters for the stated goal: a terminal acts on the Either half would close it: split on Happy to send a patch for either or both — the reorder is a one-line move and the CR fix is a |
Why
#728 fixes the two tracing crashes without touching the output, and says explicitly that
the type-visibility idea from #627/#681 should be argued on its own. This is that
argument.
Rendering every value with
repr(), as #681 proposed, changes 216 of 1329 lines in areal
pytest --debugrun, and the overwhelming majority of that is quotes added aroundstrings that were already readable. But
str()does drop things a reader needs:left:/right:val: with spaceexitstatus: 1IntEnumprints as a bare number, the member name is lostcollection_path: /xpath: /xPosixPathand apy.path.localpointing at the same place look identicalThe rule
repr()enum.Enumor anos.PathLikegetsrepr()str(), unchangedWhat it costs
Measured on a real
pytest --debugrun: 45 of 1329 lines change (3.4%), against 216for blanket
repr(). By category: 21 paths, 11 whitespace strings, 3 empty strings, 2enums, 2 blocks.
Multi-line values
Today a multi-line value breaks the layout — the continuation escapes to column 0 and
reads as a top-level trace line:
With this change the value is boxed, so its extent is visible at a glance:
Rough edges, for review
nodeid: test_mix.py::test_p[with space]gains quotes, because the parameter idcontains a space. The rule fires correctly, but on something that is not really "a
string with whitespace" in spirit.
orig: n or n == ""becomesorig: 'n or n == ""'— nested quotes on source text.'\ud800'rather than bare, since they are notisprintable(). Arguably clearer; it does change two tests added in Make hook tracing unable to fail a hook call #728.enabled — around 0.5 ms across a whole pytest run.
Testing
uv run pytest— 203 passed.uv run pre-commit run -a— all hooks pass._tracing.pyat 100% statement and branch coverage.newly reaches — a path-like with a broken repr, and
KeyboardInterruptraised from arepr.
pytest --debugoutput diffed againstmain: 45 changed lines, each one listed above.🤖 Generated with Claude Code