Skip to content

Commit 34456e8

Browse files
committed
Merge branch 'main' into add_3.10
2 parents fdca75a + 61a27c2 commit 34456e8

File tree

5 files changed

+147
-19
lines changed

5 files changed

+147
-19
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ jobs:
3737
3.9: docker://python:3.9-buster
3838
3.10: docker://python:3.10-buster
3939
pypy2.7: docker://pypy:2.7-buster
40-
pypy3.6: docker://pypy:3.6-buster
4140
pypy3.7: docker://pypy:3.7-buster
41+
pypy3.8: docker://pypy:3.8-buster
4242
- name: 🪟
4343
runs-on: windows-latest
4444
python_platform: win32
@@ -94,18 +94,18 @@ jobs:
9494
docker: pypy2.7
9595
implementation: pypy
9696
major: 2
97-
- name: PyPy 3.6
98-
tox: pypy36
99-
action: pypy-3.6
100-
docker: pypy3.6
101-
implementation: pypy
102-
major: 3
10397
- name: PyPy 3.7
10498
tox: pypy37
10599
action: pypy-3.7
106100
docker: pypy3.7
107101
implementation: pypy
108102
major: 3
103+
- name: PyPy 3.8
104+
tox: pypy38
105+
action: pypy-3.8
106+
docker: pypy3.8
107+
implementation: pypy
108+
major: 3
109109
reactor:
110110
- name: default
111111
tox: default

pytest_twisted.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import itertools
44
import signal
55
import sys
6+
import threading
67
import warnings
78

89
import decorator
@@ -200,7 +201,16 @@ def init_twisted_greenlet():
200201
return
201202

202203
if not _instances.reactor.running:
203-
if signal.getsignal(signal.SIGINT) == signal.default_int_handler:
204+
if not isinstance(threading.current_thread(), threading._MainThread):
205+
warnings.warn(
206+
(
207+
'Will not attempt to block Twisted signal configuration'
208+
' since we are not running in the main thread. See'
209+
' https://github.com/pytest-dev/pytest-twisted/issues/153.'
210+
),
211+
RuntimeWarning,
212+
)
213+
elif signal.getsignal(signal.SIGINT) == signal.default_int_handler:
204214
signal.signal(
205215
signal.SIGINT,
206216
functools.partial(signal.default_int_handler),
@@ -365,27 +375,51 @@ def pytest_pyfunc_call(pyfuncitem):
365375
# TODO: only handle 'our' tests? what is the point of handling others?
366376
# well, because our interface allowed people to return deferreds
367377
# from arbitrary tests so we kinda have to keep this up for now
368-
_run_inline_callbacks(_async_pytest_pyfunc_call, pyfuncitem)
369-
return not None
378+
maybe_hypothesis = getattr(pyfuncitem.obj, "hypothesis", None)
379+
if maybe_hypothesis is None:
380+
_run_inline_callbacks(
381+
_async_pytest_pyfunc_call,
382+
pyfuncitem,
383+
pyfuncitem.obj,
384+
{}
385+
)
386+
result = not None
387+
else:
388+
hypothesis = maybe_hypothesis
389+
f = hypothesis.inner_test
390+
391+
def inner_test(**kwargs):
392+
return _run_inline_callbacks(
393+
_async_pytest_pyfunc_call,
394+
pyfuncitem,
395+
f,
396+
kwargs,
397+
)
398+
399+
pyfuncitem.obj.hypothesis.inner_test = inner_test
400+
result = None
401+
402+
return result
370403

371404

372405
@defer.inlineCallbacks
373-
def _async_pytest_pyfunc_call(pyfuncitem):
406+
def _async_pytest_pyfunc_call(pyfuncitem, f, kwargs):
374407
"""Run test function."""
375-
kwargs = {
408+
fixture_kwargs = {
376409
name: value
377410
for name, value in pyfuncitem.funcargs.items()
378411
if name in pyfuncitem._fixtureinfo.argnames
379412
}
413+
kwargs.update(fixture_kwargs)
380414

381-
maybe_mark = _get_mark(pyfuncitem.obj)
415+
maybe_mark = _get_mark(f)
382416
if maybe_mark == 'async_test':
383-
result = yield defer.ensureDeferred(pyfuncitem.obj(**kwargs))
417+
result = yield defer.ensureDeferred(f(**kwargs))
384418
elif maybe_mark == 'inline_callbacks_test':
385-
result = yield pyfuncitem.obj(**kwargs)
419+
result = yield f(**kwargs)
386420
else:
387421
# TODO: maybe deprecate this
388-
result = yield pyfuncitem.obj(**kwargs)
422+
result = yield f(**kwargs)
389423

390424
defer.returnValue(result)
391425

testing/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import pytest_twisted
33

44

5-
pytest_plugins = "_pytest.pytester"
5+
pytest_plugins = "pytester"
66

77

88
@pytest.hookimpl(tryfirst=True)

testing/test_basic.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,21 @@ def skip_if_no_async_generators():
9393
)
9494

9595

96+
def skip_if_hypothesis_unavailable():
97+
def hypothesis_unavailable():
98+
try:
99+
import hypothesis # noqa: F401
100+
except ImportError:
101+
return True
102+
103+
return False
104+
105+
return pytest.mark.skipif(
106+
hypothesis_unavailable(),
107+
reason="hypothesis not installed",
108+
)
109+
110+
96111
@pytest.fixture
97112
def cmd_opts(request):
98113
reactor = request.config.getoption("reactor", "default")
@@ -1255,3 +1270,81 @@ def test_should_not_run():
12551270
rr.stdout.no_re_match_line(pat=pattern)
12561271
else:
12571272
assert re.match(pattern, rr.stdout.str()) is None
1273+
1274+
1275+
@skip_if_no_async_await()
1276+
@skip_if_hypothesis_unavailable()
1277+
def test_hypothesis_async_passes(testdir, cmd_opts):
1278+
test_file = """
1279+
import hypothesis
1280+
import hypothesis.strategies
1281+
1282+
import pytest_twisted
1283+
1284+
@hypothesis.given(x=hypothesis.strategies.integers())
1285+
@pytest_twisted.ensureDeferred
1286+
async def test_async(x):
1287+
assert isinstance(x, int)
1288+
"""
1289+
testdir.makepyfile(test_file)
1290+
rr = testdir.run(*cmd_opts, timeout=timeout)
1291+
assert_outcomes(rr, {"passed": 1})
1292+
1293+
1294+
@skip_if_hypothesis_unavailable()
1295+
def test_hypothesis_inline_callbacks_passes(testdir, cmd_opts):
1296+
test_file = """
1297+
import hypothesis
1298+
import hypothesis.strategies
1299+
1300+
import pytest_twisted
1301+
1302+
@hypothesis.given(x=hypothesis.strategies.integers())
1303+
@pytest_twisted.inlineCallbacks
1304+
def test_inline_callbacks(x):
1305+
assert isinstance(x, int)
1306+
return
1307+
yield
1308+
"""
1309+
testdir.makepyfile(test_file)
1310+
rr = testdir.run(*cmd_opts, timeout=timeout)
1311+
assert_outcomes(rr, {"passed": 1})
1312+
1313+
1314+
@skip_if_no_async_await()
1315+
@skip_if_hypothesis_unavailable()
1316+
def test_hypothesis_async_fails(testdir, cmd_opts):
1317+
test_file = """
1318+
import hypothesis
1319+
import hypothesis.strategies
1320+
1321+
import pytest_twisted
1322+
1323+
@hypothesis.given(x=hypothesis.strategies.integers())
1324+
@pytest_twisted.ensureDeferred
1325+
async def test_async(x):
1326+
assert isinstance(x, str)
1327+
"""
1328+
testdir.makepyfile(test_file)
1329+
rr = testdir.run(*cmd_opts, timeout=timeout)
1330+
assert_outcomes(rr, {"failed": 1})
1331+
1332+
1333+
@skip_if_hypothesis_unavailable()
1334+
def test_hypothesis_inline_callbacks_fails(testdir, cmd_opts):
1335+
test_file = """
1336+
import hypothesis
1337+
import hypothesis.strategies
1338+
1339+
import pytest_twisted
1340+
1341+
@hypothesis.given(x=hypothesis.strategies.integers())
1342+
@pytest_twisted.inlineCallbacks
1343+
def test_inline_callbacks(x):
1344+
assert isinstance(x, str)
1345+
return
1346+
yield
1347+
"""
1348+
testdir.makepyfile(test_file)
1349+
rr = testdir.run(*cmd_opts, timeout=timeout)
1350+
assert_outcomes(rr, {"failed": 1})

tox.ini

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tox]
22
envlist=
3-
py{27,py27,35,36,37,38,39,310,py36,py37}-defaultreactor
4-
py{35,36,37,38,39,310,py36,py37}-asyncioreactor
3+
py{27,py27,35,36,37,38,39,310,py37,py38}-defaultreactor
4+
py{35,36,37,38,39,310,py37,py38}-asyncioreactor
55
py{35,36,37,38,39,310}-pyqt5reactor
66
py{35,36,37,38,39,310}-pyside2reactor
77
linting
@@ -11,6 +11,7 @@ deps=
1111
greenlet
1212
pytest
1313
twisted
14+
py37,py38,py39,pypy37,pypy38: hypothesis
1415
pyqt5reactor,pyside2reactor: pytest-qt
1516
pyqt5reactor,pyside2reactor: pytest-xvfb
1617
pyqt5reactor,pyside2reactor: pywin32; sys_platform == 'win32'

0 commit comments

Comments
 (0)