diff --git a/changelog/12444.bugfix.rst b/changelog/12444.bugfix.rst new file mode 100644 index 00000000000..146cfc7ab24 --- /dev/null +++ b/changelog/12444.bugfix.rst @@ -0,0 +1 @@ +Fixed :func:`pytest.approx` which now correctly takes account Mapping keys order to compare them. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 1e389eb0663..bab70aa4a8c 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -242,7 +242,7 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]: f"Lengths: {len(self.expected)} and {len(other_side)}", ] - if set(self.expected.keys()) != set(other_side.keys()): + if self.expected.keys() != other_side.keys(): return [ "comparison failed.", f"Mappings has different keys: expected {self.expected.keys()} but got {other_side.keys()}", @@ -256,9 +256,8 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]: max_abs_diff = -math.inf max_rel_diff = -math.inf different_ids = [] - for (approx_key, approx_value), other_value in zip( - approx_side_as_map.items(), other_side.values(), strict=True - ): + for approx_key, approx_value in approx_side_as_map.items(): + other_value = other_side[approx_key] if approx_value != other_value: if approx_value.expected is not None and other_value is not None: try: diff --git a/testing/python/approx.py b/testing/python/approx.py index f870b9bd4d8..481df80565c 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1062,6 +1062,46 @@ def test_approx_dicts_with_mismatch_on_keys(self) -> None: ): assert actual == approx(expected) + def test_approx_on_unordered_mapping_with_mismatch( + self, pytester: Pytester + ) -> None: + """https://github.com/pytest-dev/pytest/issues/12444""" + pytester.makepyfile( + """ + import pytest + + def test_approx_on_unordered_mapping_with_mismatch(): + expected = {"a": 1, "b": 2, "c": 3, "d": 4} + actual = {"d": 4, "c": 5, "a": 8, "b": 2} + assert actual == pytest.approx(expected) + """ + ) + result = pytester.runpytest() + result.assert_outcomes(failed=1) + result.stdout.fnmatch_lines( + [ + "*comparison failed.**Mismatched elements: 2 / 4:*", + "*Max absolute difference: 7*", + "*Index | Obtained | Expected *", + "* a * | 8 * | 1 *", + "* c * | 5 * | 3 *", + ] + ) + + def test_approx_on_unordered_mapping_matching(self, pytester: Pytester) -> None: + """https://github.com/pytest-dev/pytest/issues/12444""" + pytester.makepyfile( + """ + import pytest + def test_approx_on_unordered_mapping_matching(): + expected = {"a": 1, "b": 2, "c": 3, "d": 4} + actual = {"d": 4, "c": 3, "a": 1, "b": 2} + assert actual == pytest.approx(expected) + """ + ) + result = pytester.runpytest() + result.assert_outcomes(passed=1) + class MyVec3: # incomplete """sequence like"""