Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Support async tests which use Hypothesis
  • Loading branch information
Zac-HD committed Dec 12, 2018
1 parent 923fe9a commit d4c1b92
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -22,6 +22,7 @@ var/
*.egg-info/
.installed.cfg
*.egg
.hypothesis/

# PyInstaller
# Usually these files are written by a python script from a template
Expand Down
3 changes: 3 additions & 0 deletions README.rst
Expand Up @@ -178,6 +178,9 @@ Changelog

0.10.0. (UNRELEASED)
~~~~~~~~~~~~~~~~~~~~
- ``pytest-asyncio`` integrates with `Hypothesis <https://hypothesis.readthedocs.io>`_
to support ``@given`` on async test functions using ``asyncio``.
`#102` <https://github.com/pytest-dev/pytest-asyncio/pull/102>

0.9.0 (2018-07-28)
~~~~~~~~~~~~~~~~~~
Expand Down
32 changes: 31 additions & 1 deletion pytest_asyncio/plugin.py
@@ -1,6 +1,7 @@
"""pytest-asyncio implementation."""
import asyncio
import contextlib
import functools
import inspect
import socket

Expand Down Expand Up @@ -139,7 +140,8 @@ def pytest_pyfunc_call(pyfuncitem):
function call.
"""
for marker_name, fixture_name in _markers_2_fixtures.items():
if marker_name in pyfuncitem.keywords:
if isasyncgenfunction(pyfuncitem.obj) \
and marker_name in pyfuncitem.keywords:
event_loop = pyfuncitem.funcargs[fixture_name]

funcargs = pyfuncitem.funcargs
Expand All @@ -152,11 +154,39 @@ def pytest_pyfunc_call(pyfuncitem):
return True


def wrap_in_sync(func):
"""Return a sync wrapper around an async function."""

@functools.wraps(func)
def inner(**kwargs):
loop = asyncio.get_event_loop_policy().new_event_loop()
try:
coro = func(**kwargs)
if coro is not None:
future = asyncio.ensure_future(coro, loop=loop)
loop.run_until_complete(future)
finally:
loop.close()

return inner


def pytest_runtest_setup(item):
for marker, fixture in _markers_2_fixtures.items():
if marker in item.keywords and fixture not in item.fixturenames:
# inject an event loop fixture for all async tests
item.fixturenames.append(fixture)
if item.get_closest_marker("asyncio") is not None:
if hasattr(item.obj, 'hypothesis'):
# If it's a Hypothesis test, we insert the wrap_in_sync decorator
item.obj.hypothesis.inner_test = wrap_in_sync(
item.obj.hypothesis.inner_test
)
elif getattr(item.obj, 'is_hypothesis_test', False):
pytest.fail(
'test function `%r` is using Hypothesis, but pytest-asyncio '
'only works with Hypothesis 3.64.0 or later.' % item
)


# maps marker to the name of the event loop fixture that will be available
Expand Down
6 changes: 5 additions & 1 deletion setup.py
Expand Up @@ -43,7 +43,11 @@ def find_version():
install_requires=["pytest >= 3.0.6"],
extras_require={
':python_version == "3.5"': "async_generator >= 1.3",
"testing": ["coverage", "async_generator >= 1.3"],
"testing": [
"coverage",
"async_generator >= 1.3",
"hypothesis >= 3.64",
],
},
entry_points={"pytest11": ["asyncio = pytest_asyncio.plugin"]},
)
27 changes: 27 additions & 0 deletions tests/test_hypothesis_integration.py
@@ -0,0 +1,27 @@
"""Tests for the Hypothesis integration, which wraps async functions in a
sync shim for Hypothesis.
"""

import pytest

from hypothesis import given, strategies as st


@given(st.integers())
@pytest.mark.asyncio
async def test_mark_inner(n):
assert isinstance(n, int)


@pytest.mark.asyncio
@given(st.integers())
async def test_mark_outer(n):
assert isinstance(n, int)


@pytest.mark.parametrize("y", [1, 2])
@given(x=st.none())
@pytest.mark.asyncio
async def test_mark_and_parametrize(x, y):
assert x is None
assert y in (1, 2)

0 comments on commit d4c1b92

Please sign in to comment.