diff --git a/changelog/1531.improvement.rst b/changelog/1531.improvement.rst new file mode 100644 index 00000000000..d444ea2e783 --- /dev/null +++ b/changelog/1531.improvement.rst @@ -0,0 +1,4 @@ +Improved the very verbose diff for every standard library container types: the indentation is now consistent and the markers are on their own separate lines, which should reduce the diffs shown to users. + +Previously, the default python pretty printer was used to generate the output, which puts opening and closing +markers on the same line as the first/last entry, in addition to not having consistent indentation. diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 9923d0a6256..bb59253f70b 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -58,7 +58,7 @@ def _safe_tuple(t): class PrettyPrinter: def __init__( self, - indent=1, + indent=4, width=80, depth=None, stream=None, @@ -146,7 +146,6 @@ def _format(self, object, stream, indent, allowance, context, level): def _pprint_dataclass(self, object, stream, indent, allowance, context, level): cls_name = object.__class__.__name__ - indent += len(cls_name) + 1 items = [ (f.name, getattr(object, f.name)) for f in _dataclasses.fields(object) @@ -164,17 +163,11 @@ def _pprint_dataclass(self, object, stream, indent, allowance, context, level): def _pprint_dict(self, object, stream, indent, allowance, context, level): write = stream.write write("{") - if self._indent_per_level > 1: - write((self._indent_per_level - 1) * " ") - length = len(object) - if length: - if self._sort_dicts: - items = sorted(object.items(), key=_safe_tuple) - else: - items = object.items() - self._format_dict_items( - items, stream, indent, allowance + 1, context, level - ) + if self._sort_dicts: + items = sorted(object.items(), key=_safe_tuple) + else: + items = object.items() + self._format_dict_items(items, stream, indent, allowance, context, level) write("}") _dispatch[dict.__repr__] = _pprint_dict @@ -185,32 +178,22 @@ def _pprint_ordered_dict(self, object, stream, indent, allowance, context, level return cls = object.__class__ stream.write(cls.__name__ + "(") - self._format( - list(object.items()), - stream, - indent + len(cls.__name__) + 1, - allowance + 1, - context, - level, - ) + self._pprint_dict(object, stream, indent, allowance, context, level) stream.write(")") _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict def _pprint_list(self, object, stream, indent, allowance, context, level): stream.write("[") - self._format_items(object, stream, indent, allowance + 1, context, level) + self._format_items(object, stream, indent, allowance, context, level) stream.write("]") _dispatch[list.__repr__] = _pprint_list def _pprint_tuple(self, object, stream, indent, allowance, context, level): stream.write("(") - endchar = ",)" if len(object) == 1 else ")" - self._format_items( - object, stream, indent, allowance + len(endchar), context, level - ) - stream.write(endchar) + self._format_items(object, stream, indent, allowance, context, level) + stream.write(")") _dispatch[tuple.__repr__] = _pprint_tuple @@ -225,11 +208,8 @@ def _pprint_set(self, object, stream, indent, allowance, context, level): else: stream.write(typ.__name__ + "({") endchar = "})" - indent += len(typ.__name__) + 1 object = sorted(object, key=_safe_key) - self._format_items( - object, stream, indent, allowance + len(endchar), context, level - ) + self._format_items(object, stream, indent, allowance, context, level) stream.write(endchar) _dispatch[set.__repr__] = _pprint_set @@ -319,7 +299,7 @@ def _pprint_bytearray(self, object, stream, indent, allowance, context, level): def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level): stream.write("mappingproxy(") - self._format(object.copy(), stream, indent + 13, allowance + 1, context, level) + self._format(object.copy(), stream, indent, allowance, context, level) stream.write(")") _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy @@ -333,7 +313,6 @@ def _pprint_simplenamespace( cls_name = "namespace" else: cls_name = object.__class__.__name__ - indent += len(cls_name) + 1 items = object.__dict__.items() stream.write(cls_name + "(") self._format_namespace_items(items, stream, indent, allowance, context, level) @@ -342,32 +321,30 @@ def _pprint_simplenamespace( _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace def _format_dict_items(self, items, stream, indent, allowance, context, level): + if not items: + return + write = stream.write - indent += self._indent_per_level - delimnl = ",\n" + " " * indent - last_index = len(items) - 1 - for i, (key, ent) in enumerate(items): - last = i == last_index - rep = self._repr(key, context, level) - write(rep) + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + for key, ent in items: + write(delimnl) + write(self._repr(key, context, level)) write(": ") - self._format( - ent, - stream, - indent + len(rep) + 2, - allowance if last else 1, - context, - level, - ) - if not last: - write(delimnl) + self._format(ent, stream, item_indent, 1, context, level) + write(",") + + write("\n" + " " * indent) def _format_namespace_items(self, items, stream, indent, allowance, context, level): + if not items: + return + write = stream.write - delimnl = ",\n" + " " * indent - last_index = len(items) - 1 - for i, (key, ent) in enumerate(items): - last = i == last_index + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + for key, ent in items: + write(delimnl) write(key) write("=") if id(ent) in context: @@ -378,52 +355,30 @@ def _format_namespace_items(self, items, stream, indent, allowance, context, lev self._format( ent, stream, - indent + len(key) + 1, - allowance if last else 1, + item_indent + len(key) + 1, + 1, context, level, ) - if not last: - write(delimnl) + + write(",") + + write("\n" + " " * indent) def _format_items(self, items, stream, indent, allowance, context, level): - write = stream.write - indent += self._indent_per_level - if self._indent_per_level > 1: - write((self._indent_per_level - 1) * " ") - delimnl = ",\n" + " " * indent - delim = "" - width = max_width = self._width - indent + 1 - it = iter(items) - try: - next_ent = next(it) - except StopIteration: + if not items: return - last = False - while not last: - ent = next_ent - try: - next_ent = next(it) - except StopIteration: - last = True - max_width -= allowance - width -= allowance - if self._compact: - rep = self._repr(ent, context, level) - w = len(rep) + 2 - if width < w: - width = max_width - if delim: - delim = delimnl - if width >= w: - width -= w - write(delim) - delim = ", " - write(rep) - continue - write(delim) - delim = delimnl - self._format(ent, stream, indent, allowance if last else 1, context, level) + + write = stream.write + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + + for item in items: + write(delimnl) + self._format(item, stream, item_indent, 1, context, level) + write(",") + + write("\n" + " " * indent) def _repr(self, object, context, level): repr, readable, recursive = self.format( @@ -443,66 +398,45 @@ def format(self, object, context, maxlevels, level): return self._safe_repr(object, context, maxlevels, level) def _pprint_default_dict(self, object, stream, indent, allowance, context, level): - if not len(object): - stream.write(repr(object)) - return rdf = self._repr(object.default_factory, context, level) - cls = object.__class__ - indent += len(cls.__name__) + 1 - stream.write(f"{cls.__name__}({rdf},\n{' ' * indent}") - self._pprint_dict(object, stream, indent, allowance + 1, context, level) + stream.write(f"{object.__class__.__name__}({rdf}, ") + self._pprint_dict(object, stream, indent, allowance, context, level) stream.write(")") _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict def _pprint_counter(self, object, stream, indent, allowance, context, level): - if not len(object): - stream.write(repr(object)) - return - cls = object.__class__ - stream.write(cls.__name__ + "({") - if self._indent_per_level > 1: - stream.write((self._indent_per_level - 1) * " ") - items = object.most_common() - self._format_dict_items( - items, stream, indent + len(cls.__name__) + 1, allowance + 2, context, level - ) - stream.write("})") + stream.write(object.__class__.__name__ + "(") + + if object: + stream.write("{") + items = object.most_common() + self._format_dict_items(items, stream, indent, allowance, context, level) + stream.write("}") + + stream.write(")") _dispatch[_collections.Counter.__repr__] = _pprint_counter def _pprint_chain_map(self, object, stream, indent, allowance, context, level): - if not len(object.maps): + if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): stream.write(repr(object)) return - cls = object.__class__ - stream.write(cls.__name__ + "(") - indent += len(cls.__name__) + 1 - for i, m in enumerate(object.maps): - if i == len(object.maps) - 1: - self._format(m, stream, indent, allowance + 1, context, level) - stream.write(")") - else: - self._format(m, stream, indent, 1, context, level) - stream.write(",\n" + " " * indent) + + stream.write(object.__class__.__name__ + "(") + self._format_items(object.maps, stream, indent, allowance, context, level) + stream.write(")") _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map def _pprint_deque(self, object, stream, indent, allowance, context, level): - if not len(object): - stream.write(repr(object)) - return - cls = object.__class__ - stream.write(cls.__name__ + "(") - indent += len(cls.__name__) + 1 + stream.write(object.__class__.__name__ + "(") + if object.maxlen is not None: + stream.write("maxlen=%d, " % object.maxlen) stream.write("[") - if object.maxlen is None: - self._format_items(object, stream, indent, allowance + 2, context, level) - stream.write("])") - else: - self._format_items(object, stream, indent, 2, context, level) - rml = self._repr(object.maxlen, context, level) - stream.write(f"],\n{' ' * indent}maxlen={rml})") + + self._format_items(object, stream, indent, allowance + 1, context, level) + stream.write("])") _dispatch[_collections.deque.__repr__] = _pprint_deque diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 4d9fd114bc2..214c321f081 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -318,18 +318,6 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: return explanation -def _surrounding_parens_on_own_lines(lines: List[str]) -> None: - """Move opening/closing parenthesis/bracket to own lines.""" - opening = lines[0][:1] - if opening in ["(", "[", "{"]: - lines[0] = " " + lines[0][1:] - lines[:] = [opening] + lines - closing = lines[-1][-1:] - if closing in [")", "]", "}"]: - lines[-1] = lines[-1][:-1] + "," - lines[:] = lines + [closing] - - def _compare_eq_iterable( left: Iterable[Any], right: Iterable[Any], @@ -341,20 +329,8 @@ def _compare_eq_iterable( # dynamic import to speedup pytest import difflib - left_formatting = pprint.pformat(left).splitlines() - right_formatting = pprint.pformat(right).splitlines() - - # Re-format for different output lengths. - lines_left = len(left_formatting) - lines_right = len(right_formatting) - if lines_left != lines_right: - printer = PrettyPrinter() - left_formatting = printer.pformat(left).splitlines() - right_formatting = printer.pformat(right).splitlines() - - if lines_left > 1 or lines_right > 1: - _surrounding_parens_on_own_lines(left_formatting) - _surrounding_parens_on_own_lines(right_formatting) + left_formatting = PrettyPrinter().pformat(left).splitlines() + right_formatting = PrettyPrinter().pformat(right).splitlines() explanation = ["Full diff:"] # "right" is the expected base against which we compare "left", diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 8c15740bd41..3432c63f688 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -40,15 +40,19 @@ class DataclassWithTwoItems: pytest.param( DataclassWithOneItem(foo="bar"), """ - DataclassWithOneItem(foo='bar') + DataclassWithOneItem( + foo='bar', + ) """, id="dataclass-one-item", ), pytest.param( DataclassWithTwoItems(foo="foo", bar="bar"), """ - DataclassWithTwoItems(foo='foo', - bar='bar') + DataclassWithTwoItems( + foo='foo', + bar='bar', + ) """, id="dataclass-two-items", ), @@ -60,15 +64,19 @@ class DataclassWithTwoItems: pytest.param( {"one": 1}, """ - {'one': 1} + { + 'one': 1, + } """, id="dict-one-item", ), pytest.param( {"one": 1, "two": 2}, """ - {'one': 1, - 'two': 2} + { + 'one': 1, + 'two': 2, + } """, id="dict-two-items", ), @@ -76,18 +84,19 @@ class DataclassWithTwoItems: pytest.param( OrderedDict({"one": 1}), """ - OrderedDict([('one', - 1)]) + OrderedDict({ + 'one': 1, + }) """, id="ordereddict-one-item", ), pytest.param( OrderedDict({"one": 1, "two": 2}), """ - OrderedDict([('one', - 1), - ('two', - 2)]) + OrderedDict({ + 'one': 1, + 'two': 2, + }) """, id="ordereddict-two-items", ), @@ -99,15 +108,19 @@ class DataclassWithTwoItems: pytest.param( [1], """ - [1] + [ + 1, + ] """, id="list-one-item", ), pytest.param( [1, 2], """ - [1, - 2] + [ + 1, + 2, + ] """, id="list-two-items", ), @@ -119,15 +132,19 @@ class DataclassWithTwoItems: pytest.param( (1,), """ - (1,) + ( + 1, + ) """, id="tuple-one-item", ), pytest.param( (1, 2), """ - (1, - 2) + ( + 1, + 2, + ) """, id="tuple-two-items", ), @@ -139,15 +156,19 @@ class DataclassWithTwoItems: pytest.param( {1}, """ - {1} + { + 1, + } """, id="set-one-item", ), pytest.param( {1, 2}, """ - {1, - 2} + { + 1, + 2, + } """, id="set-two-items", ), @@ -159,15 +180,19 @@ class DataclassWithTwoItems: pytest.param( MappingProxyType({"one": 1}), """ - mappingproxy({'one': 1}) + mappingproxy({ + 'one': 1, + }) """, id="mappingproxy-one-item", ), pytest.param( MappingProxyType({"one": 1, "two": 2}), """ - mappingproxy({'one': 1, - 'two': 2}) + mappingproxy({ + 'one': 1, + 'two': 2, + }) """, id="mappingproxy-two-items", ), @@ -179,15 +204,19 @@ class DataclassWithTwoItems: pytest.param( SimpleNamespace(one=1), """ - namespace(one=1) + namespace( + one=1, + ) """, id="simplenamespace-one-item", ), pytest.param( SimpleNamespace(one=1, two=2), """ - namespace(one=1, - two=2) + namespace( + one=1, + two=2, + ) """, id="simplenamespace-two-items", ), @@ -197,17 +226,19 @@ class DataclassWithTwoItems: pytest.param( defaultdict(str, {"one": "1"}), """ - defaultdict(, - {'one': '1'}) + defaultdict(, { + 'one': '1', + }) """, id="defaultdict-one-item", ), pytest.param( defaultdict(str, {"one": "1", "two": "2"}), """ - defaultdict(, - {'one': '1', - 'two': '2'}) + defaultdict(, { + 'one': '1', + 'two': '2', + }) """, id="defaultdict-two-items", ), @@ -219,15 +250,19 @@ class DataclassWithTwoItems: pytest.param( Counter("1"), """ - Counter({'1': 1}) + Counter({ + '1': 1, + }) """, id="counter-one-item", ), pytest.param( Counter("121"), """ - Counter({'1': 2, - '2': 1}) + Counter({ + '1': 2, + '2': 1, + }) """, id="counter-two-items", ), @@ -235,16 +270,26 @@ class DataclassWithTwoItems: pytest.param( ChainMap({"one": 1, "two": 2}), """ - ChainMap({'one': 1, - 'two': 2}) + ChainMap( + { + 'one': 1, + 'two': 2, + }, + ) """, id="chainmap-one-item", ), pytest.param( ChainMap({"one": 1}, {"two": 2}), """ - ChainMap({'one': 1}, - {'two': 2}) + ChainMap( + { + 'one': 1, + }, + { + 'two': 2, + }, + ) """, id="chainmap-two-items", ), @@ -256,24 +301,29 @@ class DataclassWithTwoItems: pytest.param( deque([1]), """ - deque([1]) + deque([ + 1, + ]) """, id="deque-one-item", ), pytest.param( deque([1, 2]), """ - deque([1, - 2]) + deque([ + 1, + 2, + ]) """, id="deque-two-items", ), pytest.param( deque([1, 2], maxlen=3), """ - deque([1, - 2], - maxlen=3) + deque(maxlen=3, [ + 1, + 2, + ]) """, id="deque-maxlen", ), @@ -293,34 +343,60 @@ class DataclassWithTwoItems: "tuple": (1, 2), }, """ - {'chainmap': ChainMap({'one': 1}, - {'two': 2}), - 'counter': Counter({'2': 2, - '1': 1}), - 'dataclass': DataclassWithTwoItems(foo='foo', - bar='bar'), - 'defaultdict': defaultdict(, - {'one': '1', - 'two': '2'}), - 'deque': deque([1, - 2], - maxlen=3), - 'dict': {'one': 1, - 'two': 2}, - 'list': [1, - 2], - 'mappingproxy': mappingproxy({'one': 1, - 'two': 2}), - 'ordereddict': OrderedDict([('one', - 1), - ('two', - 2)]), - 'set': {1, - 2}, - 'simplenamespace': namespace(one=1, - two=2), - 'tuple': (1, - 2)} + { + 'chainmap': ChainMap( + { + 'one': 1, + }, + { + 'two': 2, + }, + ), + 'counter': Counter({ + '2': 2, + '1': 1, + }), + 'dataclass': DataclassWithTwoItems( + foo='foo', + bar='bar', + ), + 'defaultdict': defaultdict(, { + 'one': '1', + 'two': '2', + }), + 'deque': deque(maxlen=3, [ + 1, + 2, + ]), + 'dict': { + 'one': 1, + 'two': 2, + }, + 'list': [ + 1, + 2, + ], + 'mappingproxy': mappingproxy({ + 'one': 1, + 'two': 2, + }), + 'ordereddict': OrderedDict({ + 'one': 1, + 'two': 2, + }), + 'set': { + 1, + 2, + }, + 'simplenamespace': namespace( + one=1, + two=2, + ), + 'tuple': ( + 1, + 2, + ), + } """, id="deep-example", ), diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 7c8c0155681..ce10ed8c4e6 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -451,11 +451,14 @@ def test_list(self) -> None: [0, 2], """ Full diff: - - [0, 2] + [ + 0, + - 2, ? ^ - + [0, 1] + + 1, ? ^ - """, + ] + """, id="lists", ), pytest.param( @@ -463,10 +466,12 @@ def test_list(self) -> None: {0: 2}, """ Full diff: - - {0: 2} - ? ^ - + {0: 1} - ? ^ + { + - 0: 2, + ? ^ + + 0: 1, + ? ^ + } """, id="dicts", ), @@ -475,10 +480,13 @@ def test_list(self) -> None: {0, 2}, """ Full diff: - - {0, 2} + { + 0, + - 2, ? ^ - + {0, 1} + + 1, ? ^ + } """, id="sets", ), @@ -542,10 +550,10 @@ def test_list_wrap_for_multiple_lines(self) -> None: "Right contains one more item: '" + long_d + "'", "Full diff:", " [", - " 'a',", - " 'b',", - " 'c',", - "- '" + long_d + "',", + " 'a',", + " 'b',", + " 'c',", + "- '" + long_d + "',", " ]", ] @@ -555,10 +563,10 @@ def test_list_wrap_for_multiple_lines(self) -> None: "Left contains one more item: '" + long_d + "'", "Full diff:", " [", - " 'a',", - " 'b',", - " 'c',", - "+ '" + long_d + "',", + " 'a',", + " 'b',", + " 'c',", + "+ '" + long_d + "',", " ]", ] @@ -574,10 +582,10 @@ def test_list_wrap_for_width_rewrap_same_length(self) -> None: "At index 0 diff: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' != 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", "Full diff:", " [", - "+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", - " 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',", - " 'cccccccccccccccccccccccccccccc',", - "- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", + "+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", + " 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',", + " 'cccccccccccccccccccccccccccccc',", + "- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", " ]", ] @@ -592,15 +600,15 @@ def test_list_dont_wrap_strings(self) -> None: "Left contains 7 more items, first extra item: 'aaaaaaaaaa'", "Full diff:", " [", - "- 'should not get wrapped',", - "+ 'a',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", + "- 'should not get wrapped',", + "+ 'a',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", " ]", ] @@ -615,13 +623,17 @@ def test_dict_wrap(self) -> None: "Differing items:", "{'env': {'env1': 1, 'env2': 2}} != {'env': {'env1': 1}}", "Full diff:", - "- {'common': 1, 'env': {'env1': 1}}", - "+ {'common': 1, 'env': {'env1': 1, 'env2': 2}}", - "? +++++++++++", + " {", + " 'common': 1,", + " 'env': {", + " 'env1': 1,", + "+ 'env2': 2,", + " },", + " }", ] long_a = "a" * 80 - sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 2}} + sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 3}} d1 = {"env": {"sub": sub}} d2 = {"env": {"sub": sub}, "new": 1} diff = callequal(d1, d2, verbose=True) @@ -632,10 +644,16 @@ def test_dict_wrap(self) -> None: "{'new': 1}", "Full diff:", " {", - " 'env': {'sub': {'long_a': '" + long_a + "',", - " 'sub1': {'long_a': 'substring that gets wrapped substring '", - " 'that gets wrapped '}}},", - "- 'new': 1,", + " 'env': {", + " 'sub': {", + f" 'long_a': '{long_a}',", + " 'sub1': {", + " 'long_a': 'substring that gets wrapped substring that gets wrapped '", + " 'substring that gets wrapped ',", + " },", + " },", + " },", + "- 'new': 1,", " }", ] @@ -677,8 +695,13 @@ def test_dict_different_items(self) -> None: "Right contains 2 more items:", "{'b': 1, 'c': 2}", "Full diff:", - "- {'b': 1, 'c': 2}", - "+ {'a': 0}", + " {", + "- 'b': 1,", + "? ^ ^", + "+ 'a': 0,", + "? ^ ^", + "- 'c': 2,", + " }", ] lines = callequal({"b": 1, "c": 2}, {"a": 0}, verbose=2) assert lines == [ @@ -688,8 +711,13 @@ def test_dict_different_items(self) -> None: "Right contains 1 more item:", "{'a': 0}", "Full diff:", - "- {'a': 0}", - "+ {'b': 1, 'c': 2}", + " {", + "- 'a': 0,", + "? ^ ^", + "+ 'b': 1,", + "? ^ ^", + "+ 'c': 2,", + " }", ] def test_sequence_different_items(self) -> None: @@ -699,8 +727,17 @@ def test_sequence_different_items(self) -> None: "At index 0 diff: 1 != 3", "Right contains one more item: 5", "Full diff:", - "- (3, 4, 5)", - "+ (1, 2)", + " (", + "- 3,", + "? ^", + "+ 1,", + "? ^", + "- 4,", + "? ^", + "+ 2,", + "? ^", + "- 5,", + " )", ] lines = callequal((1, 2, 3), (4,), verbose=2) assert lines == [ @@ -708,8 +745,27 @@ def test_sequence_different_items(self) -> None: "At index 0 diff: 1 != 4", "Left contains 2 more items, first extra item: 2", "Full diff:", - "- (4,)", - "+ (1, 2, 3)", + " (", + "- 4,", + "? ^", + "+ 1,", + "? ^", + "+ 2,", + "+ 3,", + " )", + ] + lines = callequal((1, 2, 3), (1, 20, 3), verbose=2) + assert lines == [ + "(1, 2, 3) == (1, 20, 3)", + "At index 1 diff: 2 != 20", + "Full diff:", + " (", + " 1,", + "- 20,", + "? -", + "+ 2,", + " 3,", + " )", ] def test_set(self) -> None: @@ -1844,8 +1900,8 @@ def test(): assert [0, 1] == [0, 2] """, [ - "{bold}{red}E {light-red}- [0, 2]{hl-reset}{endline}{reset}", - "{bold}{red}E {light-green}+ [0, 1]{hl-reset}{endline}{reset}", + "{bold}{red}E {light-red}- 2,{hl-reset}{endline}{reset}", + "{bold}{red}E {light-green}+ 1,{hl-reset}{endline}{reset}", ], ), ( @@ -1857,8 +1913,8 @@ def test(): """, [ "{bold}{red}E {light-gray} {hl-reset} {{{endline}{reset}", - "{bold}{red}E {light-gray} {hl-reset} 'number-is-1': 1,{endline}{reset}", - "{bold}{red}E {light-green}+ 'number-is-5': 5,{hl-reset}{endline}{reset}", + "{bold}{red}E {light-gray} {hl-reset} 'number-is-1': 1,{endline}{reset}", + "{bold}{red}E {light-green}+ 'number-is-5': 5,{hl-reset}{endline}{reset}", ], ), ), @@ -1917,14 +1973,32 @@ def test_long_text_fail(): f"{p.name} .FFF [100%]", "E At index 2 diff: 'grapes' != 'orange'", "E Full diff:", - "E - ['banana', 'apple', 'orange', 'melon', 'kiwi']", - "E ? ^ ^^", - "E + ['banana', 'apple', 'grapes', 'melon', 'kiwi']", - "E ? ^ ^ +", + "E [", + "E 'banana',", + "E 'apple',", + "E - 'orange',", + "E ? ^ ^^", + "E + 'grapes',", + "E ? ^ ^ +", + "E 'melon',", + "E 'kiwi',", + "E ]", "E Full diff:", - "E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}", - "E ? - - - - - - - -", - "E + {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}", + "E {", + "E '0': 0,", + "E - '10': 10,", + "E ? - -", + "E + '1': 1,", + "E - '20': 20,", + "E ? - -", + "E + '2': 2,", + "E - '30': 30,", + "E ? - -", + "E + '3': 3,", + "E - '40': 40,", + "E ? - -", + "E + '4': 4,", + "E }", f"E AssertionError: assert 'hello world' in '{long_text}'", ] ) diff --git a/testing/test_error_diffs.py b/testing/test_error_diffs.py index eb781210872..cad7a17c047 100644 --- a/testing/test_error_diffs.py +++ b/testing/test_error_diffs.py @@ -21,10 +21,14 @@ def test_this(): E assert [1, 4, 3] == [1, 2, 3] E At index 1 diff: 4 != 2 E Full diff: - E - [1, 2, 3] + E [ + E 1, + E - 2, E ? ^ - E + [1, 4, 3] + E + 4, E ? ^ + E 3, + E ] """, id="Compare lists, one item differs", ), @@ -40,9 +44,11 @@ def test_this(): E assert [1, 2, 3] == [1, 2] E Left contains one more item: 3 E Full diff: - E - [1, 2] - E + [1, 2, 3] - E ? +++ + E [ + E 1, + E 2, + E + 3, + E ] """, id="Compare lists, one extra item", ), @@ -59,9 +65,11 @@ def test_this(): E At index 1 diff: 3 != 2 E Right contains one more item: 3 E Full diff: - E - [1, 2, 3] - E ? --- - E + [1, 3] + E [ + E 1, + E - 2, + E 3, + E ] """, id="Compare lists, one item missing", ), @@ -77,10 +85,14 @@ def test_this(): E assert (1, 4, 3) == (1, 2, 3) E At index 1 diff: 4 != 2 E Full diff: - E - (1, 2, 3) + E ( + E 1, + E - 2, E ? ^ - E + (1, 4, 3) + E + 4, E ? ^ + E 3, + E ) """, id="Compare tuples", ), @@ -99,10 +111,12 @@ def test_this(): E Extra items in the right set: E 2 E Full diff: - E - {1, 2, 3} - E ? ^ ^ - E + {1, 3, 4} - E ? ^ ^ + E { + E 1, + E - 2, + E 3, + E + 4, + E } """, id="Compare sets", ), @@ -123,10 +137,13 @@ def test_this(): E Right contains 1 more item: E {2: 'eggs'} E Full diff: - E - {1: 'spam', 2: 'eggs'} - E ? ^ - E + {1: 'spam', 3: 'eggs'} - E ? ^ + E { + E 1: 'spam', + E - 2: 'eggs', + E ? ^ + E + 3: 'eggs', + E ? ^ + E } """, id="Compare dicts with differing keys", ), @@ -145,10 +162,11 @@ def test_this(): E Differing items: E {2: 'eggs'} != {2: 'bacon'} E Full diff: - E - {1: 'spam', 2: 'bacon'} - E ? ^^^^^ - E + {1: 'spam', 2: 'eggs'} - E ? ^^^^ + E { + E 1: 'spam', + E - 2: 'bacon', + E + 2: 'eggs', + E } """, id="Compare dicts with differing values", ), @@ -169,10 +187,11 @@ def test_this(): E Right contains 1 more item: E {3: 'bacon'} E Full diff: - E - {1: 'spam', 3: 'bacon'} - E ? ^ ^^^^^ - E + {1: 'spam', 2: 'eggs'} - E ? ^ ^^^^ + E { + E 1: 'spam', + E - 3: 'bacon', + E + 2: 'eggs', + E } """, id="Compare dicts with differing items", ),