Skip to content

Commit d757d19

Browse files
committed
Merge remote-tracking branch 'remotes/base/master' into add-clock-event-loop
* remotes/base/master: More specific Hypothesis detection Support async tests which use Hypothesis Move pytest warning config to setup.cfg Fix: Avoid warning on latest Pytest versions
2 parents 98c6bf8 + f366ee9 commit d757d19

File tree

7 files changed

+77
-7
lines changed

7 files changed

+77
-7
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var/
2222
*.egg-info/
2323
.installed.cfg
2424
*.egg
25+
.hypothesis/
2526

2627
# PyInstaller
2728
# Usually these files are written by a python script from a template

README.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ Changelog
178178

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

182185
0.9.0 (2018-07-28)
183186
~~~~~~~~~~~~~~~~~~

pytest_asyncio/plugin.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""pytest-asyncio implementation."""
22
import asyncio
33
import contextlib
4+
import functools
45
import inspect
56
import socket
67

@@ -139,7 +140,8 @@ def pytest_pyfunc_call(pyfuncitem):
139140
function call.
140141
"""
141142
for marker_name, fixture_name in _markers_2_fixtures.items():
142-
if marker_name in pyfuncitem.keywords:
143+
if marker_name in pyfuncitem.keywords \
144+
and not getattr(pyfuncitem.obj, 'is_hypothesis_test', False):
143145
event_loop = pyfuncitem.funcargs[fixture_name]
144146

145147
funcargs = pyfuncitem.funcargs
@@ -152,11 +154,39 @@ def pytest_pyfunc_call(pyfuncitem):
152154
return True
153155

154156

157+
def wrap_in_sync(func):
158+
"""Return a sync wrapper around an async function."""
159+
160+
@functools.wraps(func)
161+
def inner(**kwargs):
162+
loop = asyncio.get_event_loop_policy().new_event_loop()
163+
try:
164+
coro = func(**kwargs)
165+
if coro is not None:
166+
future = asyncio.ensure_future(coro, loop=loop)
167+
loop.run_until_complete(future)
168+
finally:
169+
loop.close()
170+
171+
return inner
172+
173+
155174
def pytest_runtest_setup(item):
156175
for marker, fixture in _markers_2_fixtures.items():
157176
if marker in item.keywords and fixture not in item.fixturenames:
158177
# inject an event loop fixture for all async tests
159178
item.fixturenames.append(fixture)
179+
if item.get_closest_marker("asyncio") is not None:
180+
if hasattr(item.obj, 'hypothesis'):
181+
# If it's a Hypothesis test, we insert the wrap_in_sync decorator
182+
item.obj.hypothesis.inner_test = wrap_in_sync(
183+
item.obj.hypothesis.inner_test
184+
)
185+
elif getattr(item.obj, 'is_hypothesis_test', False):
186+
pytest.fail(
187+
'test function `%r` is using Hypothesis, but pytest-asyncio '
188+
'only works with Hypothesis 3.64.0 or later.' % item
189+
)
160190

161191

162192
class ClockEventLoop(asyncio.new_event_loop().__class__):
@@ -217,25 +247,29 @@ def clock_event_loop(request):
217247
loop.close()
218248

219249

220-
@pytest.fixture
221-
def unused_tcp_port():
250+
def _unused_tcp_port():
222251
"""Find an unused localhost TCP port from 1024-65535 and return it."""
223252
with contextlib.closing(socket.socket()) as sock:
224253
sock.bind(('127.0.0.1', 0))
225254
return sock.getsockname()[1]
226255

227256

257+
@pytest.fixture
258+
def unused_tcp_port():
259+
return _unused_tcp_port()
260+
261+
228262
@pytest.fixture
229263
def unused_tcp_port_factory():
230264
"""A factory function, producing different unused TCP ports."""
231265
produced = set()
232266

233267
def factory():
234268
"""Return an unused port."""
235-
port = unused_tcp_port()
269+
port = _unused_tcp_port()
236270

237271
while port in produced:
238-
port = unused_tcp_port()
272+
port = _unused_tcp_port()
239273

240274
produced.add(port)
241275

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ show_missing = true
77
[tool:pytest]
88
addopts = -rsx --tb=short
99
testpaths = tests
10+
filterwarnings = error
1011

1112
[metadata]
1213
# ensure LICENSE is included in wheel metadata

setup.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ def find_version():
4343
install_requires=["pytest >= 3.0.6"],
4444
extras_require={
4545
':python_version == "3.5"': "async_generator >= 1.3",
46-
"testing": ["coverage", "async_generator >= 1.3"],
46+
"testing": [
47+
"coverage",
48+
"async_generator >= 1.3",
49+
"hypothesis >= 3.64",
50+
],
4751
},
4852
entry_points={"pytest11": ["asyncio = pytest_asyncio.plugin"]},
4953
)

tests/test_hypothesis_integration.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Tests for the Hypothesis integration, which wraps async functions in a
2+
sync shim for Hypothesis.
3+
"""
4+
5+
import pytest
6+
7+
from hypothesis import given, strategies as st
8+
9+
10+
@given(st.integers())
11+
@pytest.mark.asyncio
12+
async def test_mark_inner(n):
13+
assert isinstance(n, int)
14+
15+
16+
@pytest.mark.asyncio
17+
@given(st.integers())
18+
async def test_mark_outer(n):
19+
assert isinstance(n, int)
20+
21+
22+
@pytest.mark.parametrize("y", [1, 2])
23+
@given(x=st.none())
24+
@pytest.mark.asyncio
25+
async def test_mark_and_parametrize(x, y):
26+
assert x is None
27+
assert y in (1, 2)

tests/test_simple.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def mock_unused_tcp_port():
104104
else:
105105
return 10000 + counter
106106

107-
monkeypatch.setattr(pytest_asyncio.plugin, 'unused_tcp_port',
107+
monkeypatch.setattr(pytest_asyncio.plugin, '_unused_tcp_port',
108108
mock_unused_tcp_port)
109109

110110
assert unused_tcp_port_factory() == 10000

0 commit comments

Comments
 (0)