Skip to content

Commit 8452d41

Browse files
committed
Fixes for given questions posed
- ClockEventLoop is developed in a function to allow for later changes to event loop policies - rework `advance_time` method to better symbolize the needed loop iterations. This is marked by a change to a function that returns an awaitable.
1 parent f3efb64 commit 8452d41

File tree

2 files changed

+59
-49
lines changed

2 files changed

+59
-49
lines changed

pytest_asyncio/plugin.py

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -189,46 +189,6 @@ def pytest_runtest_setup(item):
189189
)
190190

191191

192-
class ClockEventLoop(asyncio.new_event_loop().__class__):
193-
"""
194-
A custom event loop that explicitly advances time when requested. Otherwise,
195-
this event loop advances time as expected.
196-
"""
197-
def __init__(self, *args, **kwargs):
198-
super().__init__(*args, **kwargs)
199-
self._offset = 0
200-
201-
def time(self):
202-
"""
203-
Return the time according the event loop's clock.
204-
205-
This time is adjusted by the stored offset that allows for advancement
206-
with `advance_time`.
207-
"""
208-
return super().time() + self._offset
209-
210-
async def advance_time(self, seconds):
211-
'''
212-
Advance time by a given offset in seconds.
213-
'''
214-
if seconds < 0:
215-
# cannot go backwards in time, so return immediately
216-
return
217-
218-
# advance the clock by the given offset
219-
self._offset += seconds
220-
221-
# ensure waiting callbacks are run before advancing the clock
222-
await asyncio.sleep(0, loop=self)
223-
224-
if seconds > 0:
225-
# Once the clock is adjusted, new tasks may have just been scheduled for running
226-
# in the next pass through the event loop and advance again for the task
227-
# that calls `advance_time`
228-
await asyncio.sleep(0, loop=self)
229-
await asyncio.sleep(0, loop=self)
230-
231-
232192
# maps marker to the name of the event loop fixture that will be available
233193
# to marked test functions
234194
_markers_2_fixtures = {
@@ -245,10 +205,62 @@ def event_loop(request):
245205
loop.close()
246206

247207

208+
def _clock_event_loop_class():
209+
"""
210+
Create a new class for ClockEventLoop based on the current
211+
class-type produced by `asyncio.new_event_loop()`. This is important
212+
for instances in which the enent-loop-policy has been changed.
213+
"""
214+
class ClockEventLoop(asyncio.new_event_loop().__class__):
215+
"""
216+
A custom event loop that explicitly advances time when requested. Otherwise,
217+
this event loop advances time as expected.
218+
"""
219+
def __init__(self, *args, **kwargs):
220+
super().__init__(*args, **kwargs)
221+
self._offset = 0
222+
223+
def time(self):
224+
"""
225+
Return the time according the event loop's clock.
226+
227+
This time is adjusted by the stored offset that allows for advancement
228+
with `advance_time`.
229+
"""
230+
return super().time() + self._offset
231+
232+
def advance_time(self, seconds):
233+
'''
234+
Advance time by a given offset in seconds. Returns an awaitable
235+
that will complete after all tasks scheduled for after advancement
236+
of time are proceeding.
237+
'''
238+
if seconds <= 0:
239+
# cannot go backwards in time, so return after one iteration of a loop
240+
return asyncio.sleep(0)
241+
242+
# Add a task associated with iterating the currently "ready" tasks and handles
243+
#
244+
# NOTE: This can actually take place after the offset changed, but
245+
# it is here to highlight that the loop is for currently ready
246+
# items before offset is applied
247+
self.create_task(asyncio.sleep(0))
248+
249+
# advance the clock by the given offset
250+
self._offset += seconds
251+
252+
# Once the clock is adjusted, new tasks may have just been
253+
# scheduled for running in the next pass through the event loop and
254+
# advance again for the newly ready tasks
255+
return self.create_task(asyncio.sleep(0))
256+
257+
return ClockEventLoop
258+
259+
248260
@pytest.yield_fixture
249261
def clock_event_loop(request):
250262
"""Create an instance of the default event loop for each test case."""
251-
loop = ClockEventLoop()
263+
loop = _clock_event_loop_class()()
252264
asyncio.get_event_loop_policy().set_event_loop(loop)
253265
yield loop
254266
loop.close()

tests/test_simple_35.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,14 @@ async def test_mark_asyncio_clock():
9393
"""
9494
Test that coroutines marked with asyncio_clock are run with a ClockEventLoop
9595
"""
96-
import pytest_asyncio
97-
assert isinstance(asyncio.get_event_loop(), pytest_asyncio.plugin.ClockEventLoop)
96+
assert hasattr(asyncio.get_event_loop(), 'advance_time')
9897

9998

10099
def test_clock_loop_loop_fixture(clock_event_loop):
101100
"""
102101
Test that the clock_event_loop fixture returns a proper instance of the loop
103102
"""
104-
import pytest_asyncio
105-
assert isinstance(asyncio.get_event_loop(), pytest_asyncio.plugin.ClockEventLoop)
103+
assert hasattr(asyncio.get_event_loop(), 'advance_time')
106104
clock_event_loop.close()
107105
return 'ok'
108106

@@ -112,17 +110,17 @@ async def test_clock_loop_advance_time(clock_event_loop):
112110
"""
113111
Test the sliding time event loop fixture
114112
"""
115-
async def short_nap():
116-
await asyncio.sleep(1)
113+
# a timeout for operations using advance_time
114+
NAP_TIME = 10
117115

118116
# create the task
119-
task = clock_event_loop.create_task(short_nap())
117+
task = clock_event_loop.create_task(asyncio.sleep(NAP_TIME))
120118
assert not task.done()
121119

122120
# start the task
123121
await clock_event_loop.advance_time(0)
124122
assert not task.done()
125123

126124
# process the timeout
127-
await clock_event_loop.advance_time(1)
125+
await clock_event_loop.advance_time(NAP_TIME)
128126
assert task.done()

0 commit comments

Comments
 (0)