From ccd5ec28b8e0e84a024e0f13ed4fa79235bfc5aa Mon Sep 17 00:00:00 2001 From: Zac-HD Date: Wed, 27 Jun 2018 23:30:07 +1000 Subject: [PATCH] Support Trio testing with Hypothesis --- .gitignore | 2 ++ newsfragments/42.feature.rst | 2 ++ .../_tests/test_hypothesis_interaction.py | 22 +++++++++++++++ pytest_trio/plugin.py | 28 +++++++++++++++---- test-requirements.txt | 1 + 5 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 newsfragments/42.feature.rst create mode 100644 pytest_trio/_tests/test_hypothesis_interaction.py diff --git a/.gitignore b/.gitignore index afdfef3..79d6635 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,8 @@ htmlcov/ .cache nosetests.xml coverage.xml +.hypothesis/ +.pytest_cache/ # Translations *.mo diff --git a/newsfragments/42.feature.rst b/newsfragments/42.feature.rst new file mode 100644 index 0000000..18f4ff2 --- /dev/null +++ b/newsfragments/42.feature.rst @@ -0,0 +1,2 @@ +pytest-trio now integrates with `Hypothesis `_ +to support ``@given`` on async tests using Trio. \ No newline at end of file diff --git a/pytest_trio/_tests/test_hypothesis_interaction.py b/pytest_trio/_tests/test_hypothesis_interaction.py new file mode 100644 index 0000000..6803d1e --- /dev/null +++ b/pytest_trio/_tests/test_hypothesis_interaction.py @@ -0,0 +1,22 @@ +import pytest +from hypothesis import given, strategies as st + + +@given(st.integers()) +@pytest.mark.trio +async def test_mark_inner(n): + assert isinstance(n, int) + + +@pytest.mark.trio +@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.trio +async def test_mark_and_parametrize(x, y): + assert x is None + assert y in (1, 2) diff --git a/pytest_trio/plugin.py b/pytest_trio/plugin.py index 5d55a3b..29ed325 100644 --- a/pytest_trio/plugin.py +++ b/pytest_trio/plugin.py @@ -25,8 +25,18 @@ def pytest_configure(config): ) -def _trio_test_runner_factory(item): - testfunc = item.function +def _trio_test_runner_factory(item, testfunc=None): + testfunc = testfunc or item.function + + if getattr(testfunc, '_trio_test_runner_wrapped', False): + # We have already wrapped this, perhaps because we combined Hypothesis + # with pytest.mark.parametrize + return testfunc + + if not iscoroutinefunction(testfunc): + pytest.fail( + 'test function `%r` is marked trio but is not async' % item + ) @trio_test async def _bootstrap_fixture_and_run_test(**kwargs): @@ -58,6 +68,7 @@ async def _bootstrap_fixture_and_run_test(**kwargs): if user_exc: raise user_exc + _bootstrap_fixture_and_run_test._trio_test_runner_wrapped = True return _bootstrap_fixture_and_run_test @@ -215,11 +226,18 @@ def _install_async_fixture_if_needed(fixturedef, request): @pytest.hookimpl(hookwrapper=True) def pytest_runtest_call(item): if 'trio' in item.keywords: - if not iscoroutinefunction(item.obj): + if hasattr(item.obj, 'hypothesis'): + # If it's a Hypothesis test, we go in a layer. + item.obj.hypothesis.inner_test = _trio_test_runner_factory( + item, item.obj.hypothesis.inner_test + ) + elif getattr(item.obj, 'is_hypothesis_test', False): pytest.fail( - 'test function `%r` is marked trio but is not async' % item + 'test function `%r` is using Hypothesis, but pytest-trio ' + 'only works with Hypothesis 3.64.0 or later.' % item ) - item.obj = _trio_test_runner_factory(item) + else: + item.obj = _trio_test_runner_factory(item) yield diff --git a/test-requirements.txt b/test-requirements.txt index 9955dec..ffdc000 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,2 +1,3 @@ pytest pytest-cov +hypothesis>=3.64