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 AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ David Szotten
David Vierra
Daw-Ran Liou
Debi Mishra
deepak7lal
Denis Cherednichenko
Denis Kirisov
Denivy Braiam Rück
Expand Down
12 changes: 12 additions & 0 deletions changelog/15009.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
:func:`pytest.approx` now reports mismatch details for mappings whose values are
not numbers.

Previously an unequal pair of non-numeric values under the same key -- for
example ``{"item": "a"} == approx({"item": "b"})`` -- made the diff calculation
raise ``TypeError``, so the assertion detail table was replaced by a
"representation of details failed" message. The equivalent sequence comparison
already handled this.

``decimal.FloatOperation`` is re-raised ahead of that handler, matching the
sequence path, so a mapping that mixes Decimals with floats still surfaces the
trap instead of reporting the smaller of two differences as the maximum.
7 changes: 6 additions & 1 deletion src/_pytest/approx.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,12 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]:
/ approx_value.expected
),
)
except ZeroDivisionError:
# decimal.FloatOperation subclasses TypeError, so it would be
# caught below and reported as a missing difference.
except FloatOperation:
raise
# Ignore non-numbers for the diff calculations (#15009).
except (ZeroDivisionError, TypeError):
pass
different_ids.append(approx_key)

Expand Down
51 changes: 51 additions & 0 deletions testing/python/approx.py
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,57 @@ def test_mixed_decimal_and_float_sequence_does_not_hide_float_operation(
with pytest.raises(decimal.FloatOperation):
approx(expected)._repr_compare(other)

def test_mixed_decimal_and_float_mapping_does_not_hide_float_operation(
self, monkeypatch
) -> None:
"""The mapping path must re-raise FloatOperation like the sequence one.

Catching TypeError to skip non-numbers also catches
decimal.FloatOperation, and the reported maximum then silently becomes
the smaller of the two differences. Insertion order decides which,
so the wrong number is not even stable.
"""
expected = {"a": Decimal(9), "b": 9.0}
other = {"a": Decimal(1), "b": 2.0}
assert approx(expected)._repr_compare(other)[1] == "Max absolute difference: 8"

monkeypatch.setitem(decimal.getcontext().traps, decimal.FloatOperation, True)
with pytest.raises(decimal.FloatOperation):
approx(expected)._repr_compare(other)

def test_mixed_mapping(self, assert_approx_raises_regex) -> None:
"""Approx should work on mappings that also contain non-numbers (#15009)."""
assert_approx_raises_regex(
{"a": 1.1, "b": 2, "c": "word"},
{"a": 1.0, "b": 2, "c": "different"},
[
r"",
r" comparison failed. Mismatched elements: 2 / 3:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected\s*",
r"\s*a\s*\|\s*1\.1\s*\|\s*1\.0\s*±\s*1\.0e\-06\s*",
r"\s*c\s*\|\s*word\s*\|\s*different\s*",
],
verbosity_level=2,
)

def test_mapping_of_only_strings(self, assert_approx_raises_regex) -> None:
"""A mapping whose values are all non-numeric still reports mismatches (#15009)."""
assert_approx_raises_regex(
{"item": "a"},
{"item": "b"},
[
r"",
r" comparison failed. Mismatched elements: 1 / 1:",
r" Max absolute difference: -inf",
r" Max relative difference: -inf",
r" Index \| Obtained\s*\| Expected\s*",
r"\s*item\s*\|\s*a\s*\|\s*b\s*",
],
verbosity_level=2,
)

def test_decimal_nan_tolerance_raises_value_error(self) -> None:
"""A Decimal NaN tolerance must not escape as decimal.InvalidOperation."""
nan_abs = approx(Decimal(1), abs=Decimal("NaN"))
Expand Down