Skip to content

Commit

Permalink
Fix auto marking of async hypothesis tests in auto mode (#259)
Browse files Browse the repository at this point in the history
* refactor: Extracted function determining whether a test function is a Hypothesis test.

Signed-off-by: Michael Seifert <m.seifert@digitalernachschub.de>

* 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 <m.seifert@digitalernachschub.de>
  • Loading branch information
seifertm committed Jan 14, 2022
1 parent eb6f3a8 commit 6cc430c
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 2 deletions.
4 changes: 4 additions & 0 deletions README.rst
Expand Up @@ -256,6 +256,10 @@ or an async framework such as `asynctest <https://asynctest.readthedocs.io/en/la

Changelog
---------
0.17.1 (UNRELEASED)
~~~~~~~~~~~~~~~~~~~
- 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>`_

0.17.0 (22-01-13)
~~~~~~~~~~~~~~~~~~~
- `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>`_
Expand Down
18 changes: 16 additions & 2 deletions pytest_asyncio/plugin.py
Expand Up @@ -115,7 +115,13 @@ def pytest_configure(config):
@pytest.mark.tryfirst
def pytest_pycollect_makeitem(collector, name, obj):
"""A pytest hook to collect asyncio coroutines."""
if collector.funcnamefilter(name) and _is_coroutine(obj):
if not collector.funcnamefilter(name):
return
if (
_is_coroutine(obj)
or _is_hypothesis_test(obj)
and _hypothesis_test_wraps_coroutine(obj)
):
item = pytest.Function.from_parent(collector, name=name)
if "asyncio" in item.keywords:
return list(collector._genfunctions(name, obj))
Expand All @@ -128,6 +134,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
return ret


def _hypothesis_test_wraps_coroutine(function):
return _is_coroutine(function.hypothesis.inner_test)


class FixtureStripper:
"""Include additional Fixture, and then strip them"""

Expand Down Expand Up @@ -288,7 +298,7 @@ def pytest_pyfunc_call(pyfuncitem):
where the wrapped test coroutine is executed in an event loop.
"""
if "asyncio" in pyfuncitem.keywords:
if getattr(pyfuncitem.obj, "is_hypothesis_test", False):
if _is_hypothesis_test(pyfuncitem.obj):
pyfuncitem.obj.hypothesis.inner_test = wrap_in_sync(
pyfuncitem.obj.hypothesis.inner_test,
_loop=pyfuncitem.funcargs["event_loop"],
Expand All @@ -300,6 +310,10 @@ def pytest_pyfunc_call(pyfuncitem):
yield


def _is_hypothesis_test(function) -> bool:
return getattr(function, "is_hypothesis_test", False)


def wrap_in_sync(func, _loop):
"""Return a sync wrapper around an async function executing it in the
current event loop."""
Expand Down
46 changes: 46 additions & 0 deletions tests/hypothesis/test_base.py
Expand Up @@ -2,6 +2,7 @@
sync shim for Hypothesis.
"""
import asyncio
from textwrap import dedent

import pytest
from hypothesis import given, strategies as st
Expand Down Expand Up @@ -40,3 +41,48 @@ async def test_can_use_fixture_provided_event_loop(event_loop, n):
semaphore = asyncio.Semaphore(value=0)
event_loop.call_soon(semaphore.release)
await semaphore.acquire()


def test_async_auto_marked(pytester):
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest
from hypothesis import given
import hypothesis.strategies as st
pytest_plugins = 'pytest_asyncio'
@given(n=st.integers())
async def test_hypothesis(n: int):
assert isinstance(n, int)
"""
)
)
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)


def test_sync_not_auto_marked(pytester):
"""Assert that synchronous Hypothesis functions are not marked with asyncio"""
pytester.makepyfile(
dedent(
"""\
import asyncio
import pytest
from hypothesis import given
import hypothesis.strategies as st
pytest_plugins = 'pytest_asyncio'
@given(n=st.integers())
def test_hypothesis(request, n: int):
markers = [marker.name for marker in request.node.own_markers]
assert "asyncio" not in markers
assert isinstance(n, int)
"""
)
)
result = pytester.runpytest("--asyncio-mode=auto")
result.assert_outcomes(passed=1)

0 comments on commit 6cc430c

Please sign in to comment.