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 @@ -352,6 +352,7 @@ minbang930
Minh Dau
Miro Hrončok
Mulat Mekonen
Muneeb Sheikh
mrbean-bremen
Nathan Goldbaum
Nathan Rousseau
Expand Down
3 changes: 3 additions & 0 deletions changelog/15165.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed ``pytest.approx`` failure details for sequences and mappings with zero
``timedelta`` values. The mismatch table is preserved and a relative difference
with a zero duration denominator is reported as infinite.
7 changes: 5 additions & 2 deletions src/_pytest/approx.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,10 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]:
# TODO: The type error here seems correct.
abs(approx_value.expected - other_value), # type: ignore[operator]
)
if approx_value.expected == 0:
if (
approx_value.expected == 0
or approx_value.expected == timedelta(0)
):
rel_diff_is_infinite = True
else:
max_rel_diff = _max_diff(
Expand Down Expand Up @@ -443,7 +446,7 @@ def _repr_compare(self, other_side: Sequence[float]) -> list[str]:
except TypeError:
pass
else:
if other_value == 0:
if other_value == 0 or other_value == timedelta(0):
rel_diff_is_infinite = True
else:
max_rel_diff = _max_diff(
Expand Down
41 changes: 28 additions & 13 deletions testing/python/approx.py
Original file line number Diff line number Diff line change
Expand Up @@ -1252,19 +1252,34 @@ def test_decimal_repr_compare_is_float_free(
explanation = approx(expected)._repr_compare(other)
assert explanation[1:3] == wanted

def test_mapping_relative_diff_undefined(self) -> None:
"""A zero expected value that does not compare equal to 0.

timedelta(0) != 0, so the divide-by-zero guard does not catch it and
the relative difference stays unknown.
"""
explanation = approx({"a": datetime.timedelta(0)}, rel=0.01)._repr_compare(
{"a": datetime.timedelta(seconds=1)}
)
assert explanation[1:3] == [
"Max absolute difference: 0:00:01",
"Max relative difference: -inf",
]
@pytest.mark.parametrize(
("actual", "expected"),
(
pytest.param(
[datetime.timedelta(0), datetime.timedelta(seconds=5)],
[datetime.timedelta(seconds=10), datetime.timedelta(seconds=10)],
id="sequence",
),
pytest.param(
{
"a": datetime.timedelta(seconds=10),
"b": datetime.timedelta(seconds=5),
},
{"a": datetime.timedelta(0), "b": datetime.timedelta(seconds=10)},
id="mapping",
),
),
)
def test_zero_timedelta_relative_difference(self, actual, expected) -> None:
"""Zero durations retain the mismatch table and an infinite relative diff."""
with pytest.raises(AssertionError) as exc_info:
assert actual == approx(expected, rel=0.01)

message = str(exc_info.value)
assert "Mismatched elements: 2 / 2" in message
assert "Max absolute difference: 0:00:10" in message
assert "Max relative difference: inf" in message
assert "Index" in message

def test_mixed_decimal_and_float_sequence_does_not_hide_float_operation(
self, monkeypatch
Expand Down
Loading