Skip to content

Sync wrapper for hypothesis tests uses the event loop provided by pytest-asyncio #122

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

Merged
merged 4 commits into from
May 7, 2019

Conversation

seifertm
Copy link
Contributor

@seifertm seifertm commented May 4, 2019

Async test functions marked for execution by Hypothesis are currently wrapped in a synchronous function during the test discovery phase of pytest. At this point, the event_loop fixture provided by pytest-asyncio has not been evaluated and the wrapper is forced to create a new loop for each hypothesis test. This may result is problems when using async-related code in fixtures as in #117.

This PR moves the wrapping code to the actual test execution. At this point the event_loop fixture is evaluated and there is no need to create a new loop for the test.

What's missing from this PR is a proper automated test case. Since I have run into this issue using aiohttp, I was using the following code to test it:

import aiohttp
import pytest
from hypothesis import given
from hypothesis.strategies import integers


@pytest.fixture
async def http():
    async with aiohttp.ClientSession() as session:
        yield session


@pytest.mark.asyncio
@given(integers())
async def test_connect(http, n):
    await http.get('http://127.0.0.1:8080', timeout=None)

The test succeeds with the patch and fails otherwise.

Currently, one of the test cases still fails, so this cannot be merged. I would like to hear your comments on this, though! Especially from @Zac-HD as the original author of the hypothesis integration and @VladimirWork to see if this patch also solves his problem described in #117.

)
# It is important to yield, not return. Otherwise the event_loop fixture will get cleaned up too early
# when using a combination of hypothesis and pytest.mark.parametrize
yield None
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the comment says, return None will cause problems with parametrized hypothesis tests. However, changing return None to yield None breaks test_simple::test_asyncio_marker_fail for no apparent reason. Do you have any idea why this might cause xfail to break?

@Zac-HD
Copy link
Member

Zac-HD commented May 4, 2019

I can't put my finger on the exact problem, but I have a bad feeling about isolation and the timing of cleanup. That is, that the event_loop might live too long and there might be cases where it gets closed between runs of the test case. The yield-not-return thing makes me super suspicious for this reason.

@seifertm seifertm force-pushed the hypothesis-no-new-event-loop branch from f35c2c3 to dee505e Compare May 4, 2019 15:30
@seifertm
Copy link
Contributor Author

seifertm commented May 4, 2019

You are right: The "yield-not-return" thing is smelly and affects other pytest markers, so I got rid of it. At this point, the parametrization is broken, because the event loop is already closed after the first parameter.

This is weird, because pytest -vv --setup-show -k mark_and_parametrize tests/ shows that the event_loop is correctly set up and torn down before and after each test item. I spent quite some time debuggin, but can't seem to find the issue here…

Thanks to @TimothyFitz who added a test case that reproduces the issue!

@seifertm seifertm force-pushed the hypothesis-no-new-event-loop branch from 2f02fbd to ee348b3 Compare May 5, 2019 14:29
@seifertm
Copy link
Contributor Author

seifertm commented May 5, 2019

All tests run successfully now and the issue with the prematurely closed event loop seems to be fixed.

I did this by simplifying the wrapper for async functions: pytest_runtest_setup ensures that all tests marked with asyncio require the event_loop fixture and pytest_fixture_setup replaces the current event loop with the loop provided by the fixture. This means if we call get_event_loop(), we can assume that we get the loop provided by the pytest-asyncio fixture. I did exactly that in the async wrapper.

seifertm added 2 commits May 5, 2019 16:38
There is only one marker and one fixture provided by pytest-asyncio. Retrieving these from a dictionary seems unnecessary complicated.
…eturns the loop provided by the event_loop fixture of pytest-asyncio.
@seifertm
Copy link
Contributor Author

seifertm commented May 5, 2019

I also added two refactoring commits that hopefully make the code easier to understand.

@Zac-HD
Copy link
Member

Zac-HD commented May 5, 2019

Congratulations! This looks great to me, and the refactoring makes it much easier to follow.

1 similar comment
@Zac-HD
Copy link
Member

Zac-HD commented May 5, 2019

Congratulations! This looks great to me, and the refactoring makes it much easier to follow.

@Tinche
Copy link
Member

Tinche commented May 7, 2019

LGTM!

@Tinche Tinche merged commit a9e2213 into pytest-dev:master May 7, 2019
@seifertm seifertm deleted the hypothesis-no-new-event-loop branch March 7, 2022 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants