Skip to content

Commit 10a29d7

Browse files
authored
Use a float for the tolerance in the timer tests (#347)
Hopefully this finally fixes the flaky hypothesis tests. - **Remove deprecated uses of pytest-asyncio** - **Use a `float` for the tolerance in the timer tests** - **Revert "Use less extreme values for min and max timedelta in tests"**
2 parents e0d0cd5 + e03c41a commit 10a29d7

File tree

2 files changed

+72
-36
lines changed

2 files changed

+72
-36
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ disable = [
148148
[tool.pytest.ini_options]
149149
testpaths = ["tests", "src"]
150150
asyncio_mode = "auto"
151+
asyncio_default_fixture_loop_scope = "function"
151152
required_plugins = ["pytest-asyncio", "pytest-mock"]
152153
markers = [
153154
"integration: integration tests (deselect with '-m \"not integration\"')",

tests/test_timer.py

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import asyncio
88
import enum
9-
from collections.abc import Iterator
109
from datetime import timedelta
1110

1211
import async_solipsism
@@ -22,21 +21,25 @@
2221
)
2322

2423

25-
# Setting 'autouse' has no effect as this method replaces the event loop for all tests in the file.
26-
@pytest.fixture()
27-
def event_loop() -> Iterator[async_solipsism.EventLoop]:
28-
"""Replace the loop with one that doesn't interact with the outside world."""
29-
loop = async_solipsism.EventLoop()
30-
yield loop
31-
loop.close()
24+
@pytest.fixture(autouse=True)
25+
def event_loop_policy() -> async_solipsism.EventLoopPolicy:
26+
"""Return an event loop policy that uses the async solipsism event loop."""
27+
return async_solipsism.EventLoopPolicy()
3228

3329

34-
# We give some extra room (dividing by 10) to the max and min to avoid flaky errors
35-
# failing when getting too close to the limits, as these are not realistic scenarios and
36-
# weird things can happen.
37-
_max_timedelta_microseconds = int(timedelta.max.total_seconds() * 1_000_000 / 10)
30+
_max_timedelta_microseconds = (
31+
int(
32+
timedelta.max.total_seconds() * 1_000_000,
33+
)
34+
- 1
35+
)
3836

39-
_min_timedelta_microseconds = int(timedelta.min.total_seconds() * 1_000_000 / 10)
37+
_min_timedelta_microseconds = (
38+
int(
39+
timedelta.min.total_seconds() * 1_000_000,
40+
)
41+
+ 1
42+
)
4043

4144
_calculate_next_tick_time_args = {
4245
"now": st.integers(),
@@ -136,20 +139,51 @@ def test_policy_skip_missed_and_resync_examples() -> None:
136139

137140

138141
@hypothesis.given(
139-
tolerance=st.integers(min_value=_min_timedelta_microseconds, max_value=-1)
142+
tolerance=st.floats(
143+
min_value=timedelta.min.total_seconds(),
144+
max_value=-1,
145+
exclude_max=False,
146+
allow_nan=False,
147+
allow_infinity=False,
148+
),
140149
)
141-
def test_policy_skip_missed_and_drift_invalid_tolerance(tolerance: int) -> None:
150+
def test_policy_skip_missed_and_drift_invalid_tolerance(tolerance: float) -> None:
142151
"""Test the SkipMissedAndDrift policy raises an error for invalid tolerances."""
143152
with pytest.raises(ValueError, match="delay_tolerance must be positive"):
144153
SkipMissedAndDrift(delay_tolerance=timedelta(microseconds=tolerance))
145154

146155

147156
@hypothesis.given(
148-
tolerance=st.integers(min_value=0, max_value=_max_timedelta_microseconds),
157+
tolerance=st.floats(
158+
min_value=0,
159+
max_value=timedelta.max.total_seconds(),
160+
allow_nan=False,
161+
allow_infinity=False,
162+
),
149163
**_calculate_next_tick_time_args,
150164
)
165+
# We add some particular tests cases that were problematic in the past. See:
166+
# https://github.com/frequenz-floss/frequenz-channels-python/pull/347
167+
@hypothesis.example(
168+
tolerance=171726190479152832.0,
169+
now=171_726_190_479_152_817,
170+
scheduled_tick_time=-1,
171+
interval=1,
172+
)
173+
@hypothesis.example(
174+
tolerance=171726190479152830.0,
175+
now=171_726_190_479_152_817,
176+
scheduled_tick_time=-1,
177+
interval=1,
178+
)
179+
@hypothesis.example(
180+
tolerance=171726190479152831.0,
181+
now=171_726_190_479_152_817,
182+
scheduled_tick_time=-1,
183+
interval=1,
184+
)
151185
def test_policy_skip_missed_and_drift(
152-
tolerance: int, now: int, scheduled_tick_time: int, interval: int
186+
tolerance: float, now: int, scheduled_tick_time: int, interval: int
153187
) -> None:
154188
"""Test the SkipMissedAndDrift policy."""
155189
hypothesis.assume(now >= scheduled_tick_time)
@@ -297,10 +331,10 @@ async def test_timer_construction_wrong_args() -> None:
297331
)
298332

299333

300-
async def test_timer_autostart(
301-
event_loop: async_solipsism.EventLoop, # pylint: disable=redefined-outer-name
302-
) -> None:
334+
async def test_timer_autostart() -> None:
303335
"""Test the autostart of a periodic timer."""
336+
event_loop = asyncio.get_running_loop()
337+
304338
timer = Timer(timedelta(seconds=1.0), TriggerAllMissed())
305339

306340
# We sleep some time, less than the interval, and then receive from the
@@ -312,10 +346,10 @@ async def test_timer_autostart(
312346
assert event_loop.time() == pytest.approx(1.0)
313347

314348

315-
async def test_timer_autostart_with_delay(
316-
event_loop: async_solipsism.EventLoop, # pylint: disable=redefined-outer-name
317-
) -> None:
349+
async def test_timer_autostart_with_delay() -> None:
318350
"""Test the autostart of a periodic timer with a delay."""
351+
event_loop = asyncio.get_running_loop()
352+
319353
timer = Timer(
320354
timedelta(seconds=1.0), TriggerAllMissed(), start_delay=timedelta(seconds=0.5)
321355
)
@@ -344,9 +378,10 @@ class _StartMethod(enum.Enum):
344378
@pytest.mark.parametrize("start_method", list(_StartMethod))
345379
async def test_timer_no_autostart(
346380
start_method: _StartMethod,
347-
event_loop: async_solipsism.EventLoop, # pylint: disable=redefined-outer-name
348381
) -> None:
349382
"""Test a periodic timer when it is not automatically started."""
383+
event_loop = asyncio.get_running_loop()
384+
350385
timer = Timer(
351386
timedelta(seconds=1.0),
352387
TriggerAllMissed(),
@@ -377,10 +412,10 @@ async def test_timer_no_autostart(
377412
assert event_loop.time() == pytest.approx(1.5)
378413

379414

380-
async def test_timer_trigger_all_missed(
381-
event_loop: async_solipsism.EventLoop, # pylint: disable=redefined-outer-name
382-
) -> None:
415+
async def test_timer_trigger_all_missed() -> None:
383416
"""Test a timer using the TriggerAllMissed policy."""
417+
event_loop = asyncio.get_running_loop()
418+
384419
interval = 1.0
385420
timer = Timer(timedelta(seconds=interval), TriggerAllMissed())
386421

@@ -438,10 +473,10 @@ async def test_timer_trigger_all_missed(
438473
assert drift == pytest.approx(timedelta(seconds=0.0))
439474

440475

441-
async def test_timer_skip_missed_and_resync(
442-
event_loop: async_solipsism.EventLoop, # pylint: disable=redefined-outer-name
443-
) -> None:
476+
async def test_timer_skip_missed_and_resync() -> None:
444477
"""Test a timer using the SkipMissedAndResync policy."""
478+
event_loop = asyncio.get_running_loop()
479+
445480
interval = 1.0
446481
timer = Timer(timedelta(seconds=interval), SkipMissedAndResync())
447482

@@ -489,10 +524,10 @@ async def test_timer_skip_missed_and_resync(
489524
assert drift == pytest.approx(timedelta(seconds=0.0))
490525

491526

492-
async def test_timer_skip_missed_and_drift(
493-
event_loop: async_solipsism.EventLoop, # pylint: disable=redefined-outer-name
494-
) -> None:
527+
async def test_timer_skip_missed_and_drift() -> None:
495528
"""Test a timer using the SkipMissedAndDrift policy."""
529+
event_loop = asyncio.get_running_loop()
530+
496531
interval = 1.0
497532
tolerance = 0.1
498533
timer = Timer(
@@ -553,10 +588,10 @@ async def test_timer_skip_missed_and_drift(
553588
assert drift == pytest.approx(timedelta(seconds=0.0))
554589

555590

556-
async def test_timer_reset_with_new_interval(
557-
event_loop: async_solipsism.EventLoop, # pylint: disable=redefined-outer-name
558-
) -> None:
591+
async def test_timer_reset_with_new_interval() -> None:
559592
"""Test resetting the timer with a new interval."""
593+
event_loop = asyncio.get_running_loop()
594+
560595
initial_interval = timedelta(seconds=1.0)
561596
new_interval = timedelta(seconds=2.0)
562597
timer = Timer(initial_interval, TriggerAllMissed())

0 commit comments

Comments
 (0)