From cc503c1821e709376d9a05ee4f6459e4e4f41a0c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 8 Nov 2019 04:04:23 +0100 Subject: [PATCH] _compare_eq_iterable: use AlwaysDispatchingPrettyPrinter This fixes/removes the previous hack of re-trying with minimum width, which fails short when it splits strings. This inherits from `pprint.PrettyPrinter` to override `_format` in a minimal way to always dispatch, regardless of the given width. Code ref: https://github.com/python/cpython/blob/5c0c325453a175350e3c18ebb10cc10c37f9595c/Lib/pprint.py#L170-L178 --- src/_pytest/assertion/util.py | 32 +++++++++++++++++++++++--------- testing/test_assertion.py | 33 +++++++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 7521c08e4..4af35bd57 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -28,6 +28,27 @@ _reprcompare = None # type: Optional[Callable[[str, object, object], Optional[s _assertion_pass = None # type: Optional[Callable[[int, str, str], None]] +class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter): + """PrettyPrinter that always dispatches (regardless of width).""" + + def _format(self, object, stream, indent, allowance, context, level): + p = self._dispatch.get(type(object).__repr__, None) + + objid = id(object) + if objid in context or p is None: + return super()._format(object, stream, indent, allowance, context, level) + + context[objid] = 1 + p(self, object, stream, indent, allowance, context, level + 1) + del context[objid] + + +def _pformat_dispatch(object, indent=1, width=80, depth=None, *, compact=False): + return AlwaysDispatchingPrettyPrinter( + indent=1, width=80, depth=None, compact=False + ).pformat(object) + + def format_explanation(explanation: str) -> str: """This formats an explanation @@ -270,15 +291,8 @@ def _compare_eq_iterable( lines_left = len(left_formatting) lines_right = len(right_formatting) if lines_left != lines_right: - if lines_left > lines_right: - max_width = min(len(x) for x in left_formatting) - else: - max_width = min(len(x) for x in right_formatting) - - right_formatting = pprint.pformat(right, width=max_width).splitlines() - lines_right = len(right_formatting) - left_formatting = pprint.pformat(left, width=max_width).splitlines() - lines_left = len(left_formatting) + left_formatting = _pformat_dispatch(left).splitlines() + right_formatting = _pformat_dispatch(right).splitlines() if lines_left > 1 or lines_right > 1: _surrounding_parens_on_own_lines(left_formatting) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index aac21a0df..6c700567a 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -462,6 +462,29 @@ class TestAssert_reprcompare: " ]", ] + def test_list_dont_wrap_strings(self): + long_a = "a" * 10 + l1 = ["a"] + [long_a for _ in range(0, 7)] + l2 = ["should not get wrapped"] + diff = callequal(l1, l2, verbose=True) + assert diff == [ + "['a', 'aaaaaa...aaaaaaa', ...] == ['should not get wrapped']", + "At index 0 diff: 'a' != 'should not get wrapped'", + "Left contains 7 more items, first extra item: 'aaaaaaaaaa'", + "Full diff:", + " [", + "+ 'should not get wrapped',", + "- 'a',", + "- 'aaaaaaaaaa',", + "- 'aaaaaaaaaa',", + "- 'aaaaaaaaaa',", + "- 'aaaaaaaaaa',", + "- 'aaaaaaaaaa',", + "- 'aaaaaaaaaa',", + "- 'aaaaaaaaaa',", + " ]", + ] + def test_dict_wrap(self): d1 = {"common": 1, "env": {"env1": 1}} d2 = {"common": 1, "env": {"env1": 1, "env2": 2}} @@ -479,22 +502,20 @@ class TestAssert_reprcompare: ] long_a = "a" * 80 - sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped"}} + sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 2}} d1 = {"env": {"sub": sub}} d2 = {"env": {"sub": sub}, "new": 1} diff = callequal(d1, d2, verbose=True) assert diff == [ - "{'env': {'sub...s wrapped'}}}} == {'env': {'sub...}}}, 'new': 1}", + "{'env': {'sub... wrapped '}}}} == {'env': {'sub...}}}, 'new': 1}", "Omitting 1 identical items, use -vv to show", "Right contains 1 more item:", "{'new': 1}", "Full diff:", " {", " 'env': {'sub': {'long_a': '" + long_a + "',", - " 'sub1': {'long_a': 'substring '", - " 'that '", - " 'gets '", - " 'wrapped'}}},", + " 'sub1': {'long_a': 'substring that gets wrapped substring '", + " 'that gets wrapped '}}},", "+ 'new': 1,", " }", ]