From 732c50b83ca0abddd30654f11d415e4011eb0a20 Mon Sep 17 00:00:00 2001 From: Michael Seifert Date: Thu, 13 Jan 2022 21:40:01 +0100 Subject: [PATCH] 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 --- README.rst | 4 +++ pytest_asyncio/plugin.py | 12 ++++++++- tests/hypothesis/test_base.py | 46 +++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c63f72d2..9d2257a5 100644 --- a/README.rst +++ b/README.rst @@ -256,6 +256,10 @@ or an async framework such as `asynctest `_ + 0.17.0 (22-01-13) ~~~~~~~~~~~~~~~~~~~ - `pytest-asyncio` no longer alters existing event loop policies. `#168 `_, `#188 `_ diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 4f5a0591..04b5e139 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -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)) @@ -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""" diff --git a/tests/hypothesis/test_base.py b/tests/hypothesis/test_base.py index e9273d0e..fbee75bf 100644 --- a/tests/hypothesis/test_base.py +++ b/tests/hypothesis/test_base.py @@ -2,6 +2,7 @@ sync shim for Hypothesis. """ import asyncio +from textwrap import dedent import pytest from hypothesis import given, strategies as st @@ -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)