From 833eb15e5f6fda0c3eb57316c8c2fbdb16375c53 Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 27 Oct 2026 11:22:37 +0530 Subject: [PATCH 1/3] Reject NaN as a MockClock rate --- src/trio/_core/_mock_clock.py | 2 +- src/trio/_core/_tests/test_mock_clock.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/trio/_core/_mock_clock.py b/src/trio/_core/_mock_clock.py index e437464b65..f0bb5a3ce1 100644 --- a/src/trio/_core/_mock_clock.py +++ b/src/trio/_core/_mock_clock.py @@ -87,7 +87,7 @@ def rate(self) -> float: @rate.setter def rate(self, new_rate: float) -> None: - if new_rate < 0: + if not new_rate >= 0: # also rejects NaN raise ValueError("rate must be >= 0") else: real = self._real_clock() diff --git a/src/trio/_core/_tests/test_mock_clock.py b/src/trio/_core/_tests/test_mock_clock.py index 6131e7ba3f..fa98f64a46 100644 --- a/src/trio/_core/_tests/test_mock_clock.py +++ b/src/trio/_core/_tests/test_mock_clock.py @@ -32,6 +32,13 @@ def test_mock_clock() -> None: c.rate = -1 assert c.rate == 0 + with pytest.raises(ValueError, match=r"^rate must be >= 0$"): + c.rate = float("nan") + assert c.rate == 0 + + with pytest.raises(ValueError, match=r"^rate must be >= 0$"): + MockClock(rate=float("nan")) + c.rate = 2 assert c.current_time() == 1.2 REAL_NOW += 1 From 1ae91a08e3242925362fb37f23aed61c6aa9c93a Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 27 Oct 2026 11:22:49 +0530 Subject: [PATCH 2/3] Add newsfragment --- newsfragments/3562.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/3562.bugfix.rst diff --git a/newsfragments/3562.bugfix.rst b/newsfragments/3562.bugfix.rst new file mode 100644 index 0000000000..1d9a0e84ad --- /dev/null +++ b/newsfragments/3562.bugfix.rst @@ -0,0 +1 @@ +Passing a NaN ``rate`` to :class:`trio.testing.MockClock` now raises :exc:`ValueError`, like a negative rate, instead of silently making the clock return NaN. From f1c7da7a6c3c8502008f39c5c89f39132b5c4bee Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 27 Oct 2026 11:58:14 +0530 Subject: [PATCH 3/3] Make MockClock rate check explicit with a clearer message --- src/trio/_core/_mock_clock.py | 6 +++--- src/trio/_core/_tests/test_mock_clock.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/trio/_core/_mock_clock.py b/src/trio/_core/_mock_clock.py index f0bb5a3ce1..bc530115f5 100644 --- a/src/trio/_core/_mock_clock.py +++ b/src/trio/_core/_mock_clock.py @@ -1,5 +1,5 @@ import time -from math import inf +from math import inf, isnan from .. import _core from .._abc import Clock @@ -87,8 +87,8 @@ def rate(self) -> float: @rate.setter def rate(self, new_rate: float) -> None: - if not new_rate >= 0: # also rejects NaN - raise ValueError("rate must be >= 0") + if isnan(new_rate) or new_rate < 0: + raise ValueError(f"rate must be a non-negative number, not {new_rate!r}") else: real = self._real_clock() virtual = self._real_to_virtual(real) diff --git a/src/trio/_core/_tests/test_mock_clock.py b/src/trio/_core/_tests/test_mock_clock.py index fa98f64a46..47ee1ff2a4 100644 --- a/src/trio/_core/_tests/test_mock_clock.py +++ b/src/trio/_core/_tests/test_mock_clock.py @@ -28,15 +28,21 @@ def test_mock_clock() -> None: assert c.deadline_to_sleep_time(1.2) == 0 assert c.deadline_to_sleep_time(1.3) > 999999 - with pytest.raises(ValueError, match=r"^rate must be >= 0$"): + with pytest.raises( + ValueError, match=r"^rate must be a non-negative number, not -1$" + ): c.rate = -1 assert c.rate == 0 - with pytest.raises(ValueError, match=r"^rate must be >= 0$"): + with pytest.raises( + ValueError, match=r"^rate must be a non-negative number, not nan$" + ): c.rate = float("nan") assert c.rate == 0 - with pytest.raises(ValueError, match=r"^rate must be >= 0$"): + with pytest.raises( + ValueError, match=r"^rate must be a non-negative number, not nan$" + ): MockClock(rate=float("nan")) c.rate = 2