-
Notifications
You must be signed in to change notification settings - Fork 160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ClockEventLoop class with fixture and test (close #95) #96
Add ClockEventLoop class with fixture and test (close #95) #96
Conversation
Hello. |
I am revisiting this, but I am not sure I understand the source of the testing error that occurred. I will try to look into it today (or at least this week) in order to fix that merge conflict. That being said, unfortunately, running pytest did not cause failures on my own machine during the quick development. Lastly, I may rename the event loop here, because I think the name |
FWIW trio also uses "clock" for this: https://trio.readthedocs.io/en/latest/reference-testing.html |
Should the change be a part of |
IMHO yes, this is necessary to test timeouts properly and reliably, the included test is a good example of that.
|
Why a separate pytest plugin cannot be used for testing timeouts properly and reliably? |
Well, it is not a generic implementation that can be used by anyone, it is directly tied to @asvetlov what are the problems you see with introducing this to |
Just because it is a simple isolated fixture it can be extracted into a separate library. If you asking for the PR problems, I can point on.
I think the PR is not mature enough. Better to make a separate library, battle test it on massive real code usage and after all raise the question about adding a new functionality here. |
All good points, thanks for pointing them out. 👍 From your first comment I understood you thought it didn't belong here on philosophy, not on technical merits, that's why I disagreed. |
3. I don't think there is a "clean" way to get the correct class. Alas, the event loop policy has no public method that returns the class itself. The best I could reach so far is creating one, getting a ref to its class type and discarding it: base = asyncio.new_event_loop().__class__
class AdjustableEventLoop(base):
#... 2. Doing away with the 1. instead of setting time manually, the loop could maintain an offset. So altering proposed solution: base = asyncio.new_event_loop().__class__
class ClockEventLoop(base):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._adjustable_offset = 0
def time(self):
return super().time() + self._adjustable_offset
async def advance_time(self, seconds):
if seconds < 0:
raise ValueError('cannot go backwards in time')
await asyncio.sleep(0) # let event loop cycle complete and scheduled callbacks run
self._adjustable_offset += seconds
await asyncio.sleep(0) # trigger callbacks that expired in the interval Note that this will jump in time, not "fast forward" time. This should be fine, since event loop only guarantees minimum delay. That's what I do in my own tests anyway. |
You all have made very interesting points. Let me see if I can reconfigure this to address your concerns. If it can be included in pytest-asyncio instead of a separate library, I think it belongs, but the concerns are not without merit. |
…ndard loop improve documentation of ClockEventLoop
…nt-loop * remotes/base/master: More specific Hypothesis detection Support async tests which use Hypothesis Move pytest warning config to setup.cfg Fix: Avoid warning on latest Pytest versions
…t run unless needed
I've made some adjustments based on your comments:
|
In the current arrangement, assuming |
I made one tweak, I removed the exception raised by |
pytest_asyncio/plugin.py
Outdated
self._offset += seconds | ||
|
||
# ensure waiting callbacks are run before advancing the clock | ||
await asyncio.sleep(0, loop=self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be before clock advance (line 219) then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me take a closer look at these sleep calls. I recall as I was writing it that I had difficulty achieving a predictable number of loop iterations to get the test to react as expected. That's how I settled on sleeping 3 times. It is also the reason I ended up using _run_once
in the earlier version and not using a coroutine function because it's functionality was more clear (even though it was a private method call).
pytest_asyncio/plugin.py
Outdated
# in the next pass through the event loop and advance again for the task | ||
# that calls `advance_time` | ||
await asyncio.sleep(0, loop=self) | ||
await asyncio.sleep(0, loop=self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One should be enough. The point of having two was to have one before clock advance and one after it.
|
||
# process the timeout | ||
await clock_event_loop.advance_time(1) | ||
assert task.done() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make test reliable I tend to await explicitly:
TIMEOUT=1 #define earlier in the file
await asyncio.wait_for(task, TIMEOUT)
(and change short_nap/advance to 10)
I do this because though advance_time marks task's sleep as finished, but the exact number of event loop iterations between this and the actual closing of the task is implementation-dependent.
pytest_asyncio/plugin.py
Outdated
@@ -189,10 +189,51 @@ def pytest_runtest_setup(item): | |||
) | |||
|
|||
|
|||
class ClockEventLoop(asyncio.new_event_loop().__class__): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line picks up the look class at import time.
But the default loop factory can be changed in runtime by installing a custom policy for example.
I recall many projects that change the loop factory in conftest.py
or top-level package's __init__.py
.
For example, the PR cannot test clock-event-loop against proactor event loop on Windows even if the policy was explicitly changed.
Honestly, I don't know how to solve the problem in unambiguous way but want to point on the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the problem here, but the solution is not immediately obvious. Let me take a crack at seeing if I can make a function that produces a class to solve this.
- 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.
Fixes for given questions posed
As for using Note: I had to review |
Additional thoughts I have had regarding user's desires to pick alternative event-loops. I don't see a good way to change after the fixture is called, but there may be an API that could be opened up to users. For example, instead of I also considered trying to make a mark that uses arguments like Lastly, I am again left wondering, should |
I found the source the remaining errors to be in |
- uses a custom policy meta class that uses the existing policy to modify the new loop it creates. - The new loop it creates is modified to provide `advance_time` coroutine for testing - Extends pytest-dev#96 to make ClockEventLoop the default policy for testing
I am closing this in favor of #113 |
No description provided.