From 965536d732a8b0ecc334856b206f25aad21bcbc5 Mon Sep 17 00:00:00 2001 From: Madan Kumar Date: Wed, 22 Jul 2026 06:48:52 +0530 Subject: [PATCH 1/3] Fix infinite relative tolerance for timedeltas --- AUTHORS | 1 + src/_pytest/approx.py | 13 ++++++++++++- testing/python/approx.py | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 33ee644ea68..d9ab9bab855 100644 --- a/AUTHORS +++ b/AUTHORS @@ -288,6 +288,7 @@ lovetheguitar Lukas Bednar Luke Murphy Maciek Fijalkowski +Madan Kumar Maggie Chung Maho Maik Figura diff --git a/src/_pytest/approx.py b/src/_pytest/approx.py index 9c5cd86a014..f40de0f9b89 100644 --- a/src/_pytest/approx.py +++ b/src/_pytest/approx.py @@ -682,6 +682,10 @@ def __init__( abs_tolerance = abs if rel is None: rel_tolerance = None + elif math.isinf(rel): + if expected == timedelta(0): + raise ValueError("relative tolerance can't be NaN.") + rel_tolerance = timedelta.max else: # Checked above. assert not isinstance(expected, datetime) @@ -693,9 +697,16 @@ def __init__( super().__init__(expected, rel=rel, abs=tolerance, nan_ok=False) def __repr__(self) -> str: - return f"{self.expected} ± {self.abs}" + tolerance = ( + "inf" + if isinstance(self.rel, (int, float)) and math.isinf(self.rel) + else self.abs + ) + return f"{self.expected} ± {tolerance}" def __eq__(self, actual) -> bool: + if isinstance(self.rel, (int, float)) and math.isinf(self.rel): + return isinstance(actual, timedelta) try: return bool(builtins.abs(self.expected - actual) <= self.abs) except (TypeError, OverflowError): diff --git a/testing/python/approx.py b/testing/python/approx.py index fd3533592f5..8d089cfa1e1 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1251,6 +1251,21 @@ def test_timedelta_rel_must_not_be_nan(self): with pytest.raises(ValueError, match="relative tolerance can't be NaN"): approx(timedelta(seconds=1), rel=float("nan")) + def test_timedelta_rel_infinite(self): + from datetime import timedelta + + expected = timedelta(seconds=1) + assert timedelta.max == approx(expected, rel=inf) + assert timedelta.min == approx(expected, rel=inf) + assert "not a timedelta" != approx(expected, rel=inf) + assert repr(approx(expected, rel=inf)) == "0:00:01 ± inf" + + def test_timedelta_rel_infinite_expecting_zero(self): + from datetime import timedelta + + with pytest.raises(ValueError, match="relative tolerance can't be NaN"): + approx(timedelta(0), rel=inf) + def test_timedelta_abs_must_be_non_negative(self): from datetime import timedelta From 90d679d570e3a1010694257c636da7aece86dd07 Mon Sep 17 00:00:00 2001 From: Madan Kumar Date: Wed, 22 Jul 2026 06:53:27 +0530 Subject: [PATCH 2/3] Add changelog for #14754 --- changelog/14754.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/14754.bugfix.rst diff --git a/changelog/14754.bugfix.rst b/changelog/14754.bugfix.rst new file mode 100644 index 00000000000..396dc45cc79 --- /dev/null +++ b/changelog/14754.bugfix.rst @@ -0,0 +1 @@ +``pytest.approx()`` now supports infinite relative tolerances for ``datetime.timedelta`` values instead of raising ``OverflowError``. From f2a8b71ab87ab7ac329e0a86cfba76bca5e9b9d9 Mon Sep 17 00:00:00 2001 From: winklemad Date: Wed, 22 Jul 2026 20:38:10 +0530 Subject: [PATCH 3/3] Reject infinite timedelta relative tolerance --- changelog/14754.bugfix.rst | 2 +- src/_pytest/approx.py | 15 +++------------ testing/python/approx.py | 15 +++------------ 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/changelog/14754.bugfix.rst b/changelog/14754.bugfix.rst index 396dc45cc79..08b4a96a297 100644 --- a/changelog/14754.bugfix.rst +++ b/changelog/14754.bugfix.rst @@ -1 +1 @@ -``pytest.approx()`` now supports infinite relative tolerances for ``datetime.timedelta`` values instead of raising ``OverflowError``. +``pytest.approx()`` now rejects infinite relative tolerances for ``datetime.timedelta`` values with a clear ``ValueError`` instead of raising ``OverflowError``. diff --git a/src/_pytest/approx.py b/src/_pytest/approx.py index f40de0f9b89..34b329df138 100644 --- a/src/_pytest/approx.py +++ b/src/_pytest/approx.py @@ -677,15 +677,13 @@ def __init__( raise ValueError(f"relative tolerance can't be negative: {rel}") if math.isnan(rel): raise ValueError("relative tolerance can't be NaN.") + if math.isinf(rel): + raise ValueError("relative tolerance can't be infinite.") # Compute the effective tolerance. abs_tolerance is a timedelta, rel * expected # gives a timedelta (timedelta * float works in Python). abs_tolerance = abs if rel is None: rel_tolerance = None - elif math.isinf(rel): - if expected == timedelta(0): - raise ValueError("relative tolerance can't be NaN.") - rel_tolerance = timedelta.max else: # Checked above. assert not isinstance(expected, datetime) @@ -697,16 +695,9 @@ def __init__( super().__init__(expected, rel=rel, abs=tolerance, nan_ok=False) def __repr__(self) -> str: - tolerance = ( - "inf" - if isinstance(self.rel, (int, float)) and math.isinf(self.rel) - else self.abs - ) - return f"{self.expected} ± {tolerance}" + return f"{self.expected} ± {self.abs}" def __eq__(self, actual) -> bool: - if isinstance(self.rel, (int, float)) and math.isinf(self.rel): - return isinstance(actual, timedelta) try: return bool(builtins.abs(self.expected - actual) <= self.abs) except (TypeError, OverflowError): diff --git a/testing/python/approx.py b/testing/python/approx.py index 8d089cfa1e1..a32693deba4 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1251,20 +1251,11 @@ def test_timedelta_rel_must_not_be_nan(self): with pytest.raises(ValueError, match="relative tolerance can't be NaN"): approx(timedelta(seconds=1), rel=float("nan")) - def test_timedelta_rel_infinite(self): + def test_timedelta_rel_must_not_be_infinite(self): from datetime import timedelta - expected = timedelta(seconds=1) - assert timedelta.max == approx(expected, rel=inf) - assert timedelta.min == approx(expected, rel=inf) - assert "not a timedelta" != approx(expected, rel=inf) - assert repr(approx(expected, rel=inf)) == "0:00:01 ± inf" - - def test_timedelta_rel_infinite_expecting_zero(self): - from datetime import timedelta - - with pytest.raises(ValueError, match="relative tolerance can't be NaN"): - approx(timedelta(0), rel=inf) + with pytest.raises(ValueError, match="relative tolerance can't be infinite"): + approx(timedelta(seconds=1), rel=inf) def test_timedelta_abs_must_be_non_negative(self): from datetime import timedelta