From 35a0b9b14d5352a4ff20d243169bb41bf1daacc4 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Tue, 8 Mar 2022 15:44:18 +0000 Subject: [PATCH] improve -vv repr, fix #9742 --- AUTHORS | 1 + changelog/9742.improvement.rst | 1 + extra/setup-py.test/setup.py | 1 + src/_pytest/_io/saferepr.py | 14 ++++++++++++++ src/_pytest/assertion/util.py | 6 +++--- testing/io/test_saferepr.py | 22 +++++++++++++++++++++- testing/test_assertion.py | 14 ++++++++++++++ 7 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 changelog/9742.improvement.rst diff --git a/AUTHORS b/AUTHORS index 2b6ca66de7a..1c82b006de1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -289,6 +289,7 @@ Ruaridh Williamson Russel Winder Ryan Wooden Saiprasad Kale +Samuel Colvin Samuel Dion-Girardeau Samuel Searles-Bryant Samuele Pedroni diff --git a/changelog/9742.improvement.rst b/changelog/9742.improvement.rst new file mode 100644 index 00000000000..a1abfc27469 --- /dev/null +++ b/changelog/9742.improvement.rst @@ -0,0 +1 @@ +Display assertion message without escaped newline characters with ``-vv``. diff --git a/extra/setup-py.test/setup.py b/extra/setup-py.test/setup.py index d0560ce1f5f..97883852e2e 100644 --- a/extra/setup-py.test/setup.py +++ b/extra/setup-py.test/setup.py @@ -1,4 +1,5 @@ import sys + from distutils.core import setup if __name__ == "__main__": diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py index e7ff5cab203..07c726aa6e1 100644 --- a/src/_pytest/_io/saferepr.py +++ b/src/_pytest/_io/saferepr.py @@ -107,6 +107,20 @@ def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str 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. + """ + try: + return repr(obj) + except Exception as exc: + return _format_repr_exception(exc, obj) + + class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter): """PrettyPrinter that always dispatches (regardless of width).""" diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 03167ddd471..6d3a08bc10c 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -14,7 +14,7 @@ import _pytest._code from _pytest import outcomes from _pytest._io.saferepr import _pformat_dispatch -from _pytest._io.saferepr import safeformat +from _pytest._io.saferepr import saferepr_unlimited from _pytest._io.saferepr import saferepr from _pytest.config import Config @@ -160,8 +160,8 @@ def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[ """Return specialised explanations for some operators/operands.""" verbose = config.getoption("verbose") if verbose > 1: - left_repr = safeformat(left) - right_repr = safeformat(right) + left_repr = saferepr_unlimited(left) + right_repr = saferepr_unlimited(right) else: # XXX: "15 chars indentation" is wrong # ("E AssertionError: assert "); should use term width. diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py index 63d3af822b1..3bb5f3ece81 100644 --- a/testing/io/test_saferepr.py +++ b/testing/io/test_saferepr.py @@ -1,7 +1,7 @@ import pytest from _pytest._io.saferepr import _pformat_dispatch from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE -from _pytest._io.saferepr import saferepr +from _pytest._io.saferepr import saferepr, saferepr_unlimited def test_simple_repr(): @@ -179,3 +179,23 @@ def __repr__(self): assert saferepr(SomeClass()).startswith( "<[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' + ) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index d37ee72a2ec..0fcbca8e024 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1695,3 +1695,17 @@ def test(): "*= 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[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}" + )