Skip to content

Commit 862ac24

Browse files
Fix: assertrepr_compare should respect dictionary insertion order (Closes #13503)
1 parent 2fb64da commit 862ac24

File tree

4 files changed

+55
-1
lines changed

4 files changed

+55
-1
lines changed

changelog/14050.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Assertion comparison output now preserves dictionary insertion order instead of sorting keys.

src/_pytest/_io/saferepr.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from itertools import islice
34
import pprint
45
import reprlib
56

@@ -77,8 +78,32 @@ def repr_instance(self, x: object, level: int) -> str:
7778
s = _format_repr_exception(exc, x)
7879
if self.maxsize is not None:
7980
s = _ellipsize(s, self.maxsize)
81+
8082
return s
8183

84+
def repr_dict(self, x: dict[object, object], level: int) -> str:
85+
"""Represent a dict while preserving its insertion order.
86+
87+
Differs from ``reprlib.Repr.repr_dict`` by iterating directly over ``x``
88+
rather than using the stdlib's sorting helper.
89+
"""
90+
n = len(x)
91+
if n == 0:
92+
return "{}"
93+
if level <= 0:
94+
return "{" + self.fillvalue + "}"
95+
newlevel = level - 1
96+
repr1 = self.repr1
97+
pieces = []
98+
for key in islice(x, self.maxdict):
99+
keyrepr = repr1(key, newlevel)
100+
valrepr = repr1(x[key], newlevel)
101+
pieces.append(f"{keyrepr}: {valrepr}")
102+
if n > self.maxdict:
103+
pieces.append(self.fillvalue)
104+
s = self._join(pieces, level)
105+
return f"{{{s}}}"
106+
82107

83108
def safeformat(obj: object) -> str:
84109
"""Return a pretty printed string for the given object.

testing/io/test_saferepr.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,31 @@ def __repr__(self):
192192
assert saferepr_unlimited(A()).startswith(
193193
"<[ValueError(42) raised in repr()] A object at 0x"
194194
)
195+
196+
197+
def test_saferepr_dict_preserves_insertion_order():
198+
d = {"b": 1, "a": 2}
199+
assert saferepr(d, maxsize=None) == "{'b': 1, 'a': 2}"
200+
201+
202+
def test_saferepr_dict_truncation_preserves_insertion_order():
203+
from _pytest._io.saferepr import SafeRepr
204+
205+
d = {"b": 1, "a": 2}
206+
s = SafeRepr(maxsize=None)
207+
s.maxdict = 1
208+
assert s.repr(d) == "{'b': 1, ...}"
209+
210+
211+
def test_saferepr_dict_fillvalue_when_level_is_zero():
212+
from _pytest._io.saferepr import SafeRepr
213+
214+
s = SafeRepr(maxsize=None)
215+
assert s.repr_dict({"a": 1}, level=0) == "{...}"
216+
217+
218+
def test_saferepr_dict_empty():
219+
from _pytest._io.saferepr import SafeRepr
220+
221+
s = SafeRepr(maxsize=None)
222+
assert s.repr_dict({}, level=1) == "{}"

testing/test_assertion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def test_dummy_failure(pytester): # how meta!
129129
[
130130
"> r.assertoutcome(passed=1)",
131131
"E AssertionError: ([[][]], [[][]], [[]<TestReport *>[]])*",
132-
"E assert {'failed': 1,... 'skipped': 0} == {'failed': 0,... 'skipped': 0}",
132+
"E assert {'passed': 0,...*'failed': 1} == {'passed': 1,...*'failed': 0}",
133133
"E Omitting 1 identical items, use -vv to show",
134134
"E Differing items:",
135135
"E Use -v to get more diff",

0 commit comments

Comments
 (0)