diff --git a/changelog/13503.bugfix.rst b/changelog/13503.bugfix.rst new file mode 100644 index 00000000000..1edcf346c34 --- /dev/null +++ b/changelog/13503.bugfix.rst @@ -0,0 +1 @@ +Dictionary keys in assertion failure output now preserve insertion order instead of being sorted alphabetically, matching Python's dict behavior since 3.7+. diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 28f06909206..ec41b449ddf 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -162,7 +162,7 @@ def _pprint_dict( ) -> None: write = stream.write write("{") - items = sorted(object.items(), key=_safe_tuple) + items = object.items() self._format_dict_items(items, stream, indent, allowance, context, level) write("}") @@ -608,7 +608,7 @@ def _safe_repr( components: list[str] = [] append = components.append level += 1 - for k, v in sorted(object.items(), key=_safe_tuple): + for k, v in object.items(): krepr = self._safe_repr(k, context, maxlevels, level) vrepr = self._safe_repr(v, context, maxlevels, level) append(f"{krepr}: {vrepr}") diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 1326ef34b2e..be54fdb1e5e 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -406,3 +406,84 @@ class DataclassWithTwoItems: ) def test_consistent_pretty_printer(data: Any, expected: str) -> None: assert PrettyPrinter().pformat(data) == textwrap.dedent(expected).strip() + + +def test_dict_preserves_insertion_order() -> None: + """Test that dictionary keys maintain insertion order, not alphabetical. + + Relates to issue #13503 - dicts should preserve insertion order + since Python 3.7+, not sort alphabetically. + """ + # Create dict with non-alphabetical insertion order + d = {} + d["z"] = 1 + d["a"] = 2 + d["m"] = 3 + + result = PrettyPrinter().pformat(d) + + # Verify the keys appear in insertion order (z, a, m) + z_pos = result.index("'z'") + a_pos = result.index("'a'") + m_pos = result.index("'m'") + + # z should appear before a, and a before m + assert z_pos < a_pos < m_pos, ( + f"Expected insertion order z None: + """Test dict insertion order in _safe_repr (used with maxlevels/depth). + + This test ensures the _safe_repr method also preserves insertion order + when it formats dicts at maximum depth levels. + """ + # Create nested dict structure with non-alphabetical keys + d = {"z": {"inner": 1}, "a": {"inner": 2}, "m": {"inner": 3}} + + # Use depth parameter to trigger _safe_repr path + pp = PrettyPrinter(depth=1) + result = pp.pformat(d) + + # Verify insertion order is preserved (z before a before m) + z_pos = result.index("'z'") + a_pos = result.index("'a'") + m_pos = result.index("'m'") + assert z_pos < a_pos < m_pos, ( + f"Expected insertion order z None: + """Test dict insertion order in _safe_repr inline representation. + + This covers the _safe_repr dict formatting code path used for + inline/fallback representations. + """ + pp = PrettyPrinter() + + # Create a dict that will be rendered inline via _safe_repr + d = {"z": 1, "a": 2, "m": 3} + + # Call _safe_repr directly to ensure that code path is covered + result = pp._safe_repr(d, set(), None, 0) + + # Verify insertion order is preserved in the inline repr + z_pos = result.index("'z'") + a_pos = result.index("'a'") + m_pos = result.index("'m'") + assert z_pos < a_pos < m_pos, ( + f"Expected insertion order z