Remove newlines from left/right operands with '-vv' (#9743)

The left/right operands produced when `verbose > 1` should not contain newlines, because they are used to 
build the `summary` string. The `assertrepr_compare` function returns a list of lines, and the summary is one of those lines and should not contain newlines itself. 

Fix #9742

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
This commit is contained in:
Samuel Colvin 2022-03-19 11:55:39 +00:00 committed by GitHub
parent eb22339dc3
commit b75cbee290
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 58 additions and 3 deletions

View File

@ -289,6 +289,7 @@ Ruaridh Williamson
Russel Winder Russel Winder
Ryan Wooden Ryan Wooden
Saiprasad Kale Saiprasad Kale
Samuel Colvin
Samuel Dion-Girardeau Samuel Dion-Girardeau
Samuel Searles-Bryant Samuel Searles-Bryant
Samuele Pedroni Samuele Pedroni

View File

@ -0,0 +1 @@
Display assertion message without escaped newline characters with ``-vv``.

View File

@ -107,6 +107,23 @@ def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str
return SafeRepr(maxsize).repr(obj) return SafeRepr(maxsize).repr(obj)
def saferepr_unlimited(obj: object) -> str:
"""Return an unlimited-size safe repr-string for the given object.
As with saferepr, failing __repr__ functions of user instances
will be represented with a short exception info.
This function is a wrapper around simple repr.
Note: a cleaner solution would be to alter ``saferepr``this way
when maxsize=None, but that might affect some other code.
"""
try:
return repr(obj)
except Exception as exc:
return _format_repr_exception(exc, obj)
class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter): class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):
"""PrettyPrinter that always dispatches (regardless of width).""" """PrettyPrinter that always dispatches (regardless of width)."""

View File

@ -14,8 +14,8 @@ from typing import Sequence
import _pytest._code import _pytest._code
from _pytest import outcomes from _pytest import outcomes
from _pytest._io.saferepr import _pformat_dispatch from _pytest._io.saferepr import _pformat_dispatch
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
from _pytest._io.saferepr import saferepr_unlimited
from _pytest.config import Config from _pytest.config import Config
# The _reprcompare attribute on the util module is used by the new assertion # The _reprcompare attribute on the util module is used by the new assertion
@ -160,8 +160,8 @@ def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[
"""Return specialised explanations for some operators/operands.""" """Return specialised explanations for some operators/operands."""
verbose = config.getoption("verbose") verbose = config.getoption("verbose")
if verbose > 1: if verbose > 1:
left_repr = safeformat(left) left_repr = saferepr_unlimited(left)
right_repr = safeformat(right) right_repr = saferepr_unlimited(right)
else: else:
# XXX: "15 chars indentation" is wrong # XXX: "15 chars indentation" is wrong
# ("E AssertionError: assert "); should use term width. # ("E AssertionError: assert "); should use term width.

View File

@ -2,6 +2,7 @@ import pytest
from _pytest._io.saferepr import _pformat_dispatch from _pytest._io.saferepr import _pformat_dispatch
from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
from _pytest._io.saferepr import saferepr_unlimited
def test_simple_repr(): def test_simple_repr():
@ -179,3 +180,23 @@ def test_broken_getattribute():
assert saferepr(SomeClass()).startswith( assert saferepr(SomeClass()).startswith(
"<[RuntimeError() raised in repr()] SomeClass object at 0x" "<[RuntimeError() raised in repr()] SomeClass object at 0x"
) )
def test_saferepr_unlimited():
dict5 = {f"v{i}": i for i in range(5)}
assert saferepr_unlimited(dict5) == "{'v0': 0, 'v1': 1, 'v2': 2, 'v3': 3, 'v4': 4}"
dict_long = {f"v{i}": i for i in range(1_000)}
r = saferepr_unlimited(dict_long)
assert "..." not in r
assert "\n" not in r
def test_saferepr_unlimited_exc():
class A:
def __repr__(self):
raise ValueError(42)
assert saferepr_unlimited(A()).startswith(
"<[ValueError(42) raised in repr()] A object at 0x"
)

View File

@ -1695,3 +1695,18 @@ def test_assertion_location_with_coverage(pytester: Pytester) -> None:
"*= 1 failed in*", "*= 1 failed in*",
] ]
) )
def test_reprcompare_verbose_long() -> None:
a = {f"v{i}": i for i in range(11)}
b = a.copy()
b["v2"] += 10
lines = callop("==", a, b, verbose=2)
assert lines is not None
assert lines[0] == (
"{'v0': 0, 'v1': 1, 'v2': 2, 'v3': 3, 'v4': 4, 'v5': 5, "
"'v6': 6, 'v7': 7, 'v8': 8, 'v9': 9, 'v10': 10}"
" == "
"{'v0': 0, 'v1': 1, 'v2': 12, 'v3': 3, 'v4': 4, 'v5': 5, "
"'v6': 6, 'v7': 7, 'v8': 8, 'v9': 9, 'v10': 10}"
)