Skip to content

Commit 6cc430c

Browse files
authored
Fix auto marking of async hypothesis tests in auto mode (#259)
* refactor: Extracted function determining whether a test function is a Hypothesis test. Signed-off-by: Michael Seifert <[email protected]> * fix: Fixes a bug that prevents async Hypothesis tests from working without explicit "asyncio" marker when "--asyncio-mode=auto" is set. The option --asyncio-mode=auto marks all async functions with the asyncio mark during the collection phase. However, when pytest collects the Hypothesis test, the @given decorator has already been applied and the Hypothesis test function is no longer a coroutine. This commit extends the "pytest_pycollect_makeitem" hook to mark Hypothesis tests whose function body is a coroutine. Closes #258 Signed-off-by: Michael Seifert <[email protected]>
1 parent eb6f3a8 commit 6cc430c

File tree

3 files changed

+66
-2
lines changed

3 files changed

+66
-2
lines changed

README.rst

+4
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ or an async framework such as `asynctest <https://asynctest.readthedocs.io/en/la
256256

257257
Changelog
258258
---------
259+
0.17.1 (UNRELEASED)
260+
~~~~~~~~~~~~~~~~~~~
261+
- Fixes a bug that prevents async Hypothesis tests from working without explicit ``asyncio`` marker when ``--asyncio-mode=auto`` is set. `#258 <https://github.com/pytest-dev/pytest-asyncio/issues/258>`_
262+
259263
0.17.0 (22-01-13)
260264
~~~~~~~~~~~~~~~~~~~
261265
- `pytest-asyncio` no longer alters existing event loop policies. `#168 <https://github.com/pytest-dev/pytest-asyncio/issues/168>`_, `#188 <https://github.com/pytest-dev/pytest-asyncio/issues/168>`_

pytest_asyncio/plugin.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,13 @@ def pytest_configure(config):
115115
@pytest.mark.tryfirst
116116
def pytest_pycollect_makeitem(collector, name, obj):
117117
"""A pytest hook to collect asyncio coroutines."""
118-
if collector.funcnamefilter(name) and _is_coroutine(obj):
118+
if not collector.funcnamefilter(name):
119+
return
120+
if (
121+
_is_coroutine(obj)
122+
or _is_hypothesis_test(obj)
123+
and _hypothesis_test_wraps_coroutine(obj)
124+
):
119125
item = pytest.Function.from_parent(collector, name=name)
120126
if "asyncio" in item.keywords:
121127
return list(collector._genfunctions(name, obj))
@@ -128,6 +134,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
128134
return ret
129135

130136

137+
def _hypothesis_test_wraps_coroutine(function):
138+
return _is_coroutine(function.hypothesis.inner_test)
139+
140+
131141
class FixtureStripper:
132142
"""Include additional Fixture, and then strip them"""
133143

@@ -288,7 +298,7 @@ def pytest_pyfunc_call(pyfuncitem):
288298
where the wrapped test coroutine is executed in an event loop.
289299
"""
290300
if "asyncio" in pyfuncitem.keywords:
291-
if getattr(pyfuncitem.obj, "is_hypothesis_test", False):
301+
if _is_hypothesis_test(pyfuncitem.obj):
292302
pyfuncitem.obj.hypothesis.inner_test = wrap_in_sync(
293303
pyfuncitem.obj.hypothesis.inner_test,
294304
_loop=pyfuncitem.funcargs["event_loop"],
@@ -300,6 +310,10 @@ def pytest_pyfunc_call(pyfuncitem):
300310
yield
301311

302312

313+
def _is_hypothesis_test(function) -> bool:
314+
return getattr(function, "is_hypothesis_test", False)
315+
316+
303317
def wrap_in_sync(func, _loop):
304318
"""Return a sync wrapper around an async function executing it in the
305319
current event loop."""

tests/hypothesis/test_base.py

+46
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
sync shim for Hypothesis.
33
"""
44
import asyncio
5+
from textwrap import dedent
56

67
import pytest
78
from hypothesis import given, strategies as st
@@ -40,3 +41,48 @@ async def test_can_use_fixture_provided_event_loop(event_loop, n):
4041
semaphore = asyncio.Semaphore(value=0)
4142
event_loop.call_soon(semaphore.release)
4243
await semaphore.acquire()
44+
45+
46+
def test_async_auto_marked(pytester):
47+
pytester.makepyfile(
48+
dedent(
49+
"""\
50+
import asyncio
51+
import pytest
52+
from hypothesis import given
53+
import hypothesis.strategies as st
54+
55+
pytest_plugins = 'pytest_asyncio'
56+
57+
@given(n=st.integers())
58+
async def test_hypothesis(n: int):
59+
assert isinstance(n, int)
60+
"""
61+
)
62+
)
63+
result = pytester.runpytest("--asyncio-mode=auto")
64+
result.assert_outcomes(passed=1)
65+
66+
67+
def test_sync_not_auto_marked(pytester):
68+
"""Assert that synchronous Hypothesis functions are not marked with asyncio"""
69+
pytester.makepyfile(
70+
dedent(
71+
"""\
72+
import asyncio
73+
import pytest
74+
from hypothesis import given
75+
import hypothesis.strategies as st
76+
77+
pytest_plugins = 'pytest_asyncio'
78+
79+
@given(n=st.integers())
80+
def test_hypothesis(request, n: int):
81+
markers = [marker.name for marker in request.node.own_markers]
82+
assert "asyncio" not in markers
83+
assert isinstance(n, int)
84+
"""
85+
)
86+
)
87+
result = pytester.runpytest("--asyncio-mode=auto")
88+
result.assert_outcomes(passed=1)

0 commit comments

Comments
 (0)