Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/14050.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Assertion comparison output now preserves dictionary insertion order instead of sorting keys.
25 changes: 25 additions & 0 deletions src/_pytest/_io/saferepr.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from itertools import islice
import pprint
import reprlib

Expand Down Expand Up @@ -77,8 +78,32 @@ def repr_instance(self, x: object, level: int) -> str:
s = _format_repr_exception(exc, x)
if self.maxsize is not None:
s = _ellipsize(s, self.maxsize)

return s

def repr_dict(self, x: dict[object, object], level: int) -> str:
"""Represent a dict while preserving its insertion order.

Differs from ``reprlib.Repr.repr_dict`` by iterating directly over ``x``
rather than using the stdlib's sorting helper.
"""
fillvalue = "..."
n = len(x)
if n == 0:
return "{}"
if level <= 0:
return "{" + fillvalue + "}"
newlevel = level - 1
repr1 = self.repr1
pieces = []
for key in islice(x, self.maxdict):
keyrepr = repr1(key, newlevel)
valrepr = repr1(x[key], newlevel)
pieces.append(f"{keyrepr}: {valrepr}")
if n > self.maxdict:
pieces.append(fillvalue)
return "{" + ", ".join(pieces) + "}"


def safeformat(obj: object) -> str:
"""Return a pretty printed string for the given object.
Expand Down
28 changes: 28 additions & 0 deletions testing/io/test_saferepr.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,31 @@ def __repr__(self):
assert saferepr_unlimited(A()).startswith(
"<[ValueError(42) raised in repr()] A object at 0x"
)


def test_saferepr_dict_preserves_insertion_order():
d = {"b": 1, "a": 2}
assert saferepr(d, maxsize=None) == "{'b': 1, 'a': 2}"


def test_saferepr_dict_truncation_preserves_insertion_order():
from _pytest._io.saferepr import SafeRepr

d = {"b": 1, "a": 2}
s = SafeRepr(maxsize=None)
s.maxdict = 1
assert s.repr(d) == "{'b': 1, ...}"


def test_saferepr_dict_fillvalue_when_level_is_zero():
from _pytest._io.saferepr import SafeRepr

s = SafeRepr(maxsize=None)
assert s.repr_dict({"a": 1}, level=0) == "{...}"


def test_saferepr_dict_empty():
from _pytest._io.saferepr import SafeRepr

s = SafeRepr(maxsize=None)
assert s.repr_dict({}, level=1) == "{}"
2 changes: 1 addition & 1 deletion testing/test_assertion.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def test_dummy_failure(pytester): # how meta!
[
"> r.assertoutcome(passed=1)",
"E AssertionError: ([[][]], [[][]], [[]<TestReport *>[]])*",
"E assert {'failed': 1,... 'skipped': 0} == {'failed': 0,... 'skipped': 0}",
"E assert {'passed': 0,...*'failed': 1} == {'passed': 1,...*'failed': 0}",
"E Omitting 1 identical items, use -vv to show",
"E Differing items:",
"E Use -v to get more diff",
Expand Down