Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deterministic scheduling via Hypothesis #73

Merged
merged 5 commits into from
Feb 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions newsfragments/73.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest-trio now makes the Trio scheduler deterministic while running
inside a Hypothesis test. Hopefully you won't see any change, but if
you had scheduler-dependent bugs Hypothesis will be more effective now.
2 changes: 1 addition & 1 deletion pytest_trio/_tests/test_fixture_ordering.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ async def crash_late_agen():
raise RuntimeError("crash_late_agen".upper())

async def crash(when, token):
with trio.open_cancel_scope(shield=True):
with trio.CancelScope(shield=True):
await trio.sleep(when)
raise RuntimeError(token.upper())

Expand Down
27 changes: 27 additions & 0 deletions pytest_trio/_tests/test_hypothesis_interaction.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import pytest
import trio
from trio.tests.test_scheduler_determinism import (
scheduler_trace, test_the_trio_scheduler_is_not_deterministic,
test_the_trio_scheduler_is_deterministic_if_seeded
)
from hypothesis import given, settings, strategies as st

from pytest_trio.plugin import _trio_test_runner_factory

# deadline=None avoids unpredictable warnings/errors when CI happens to be
# slow (example: https://travis-ci.org/python-trio/pytest-trio/jobs/406738296)
# max_examples=5 speeds things up a bit
Expand Down Expand Up @@ -28,3 +35,23 @@ async def test_mark_outer(n):
async def test_mark_and_parametrize(x, y):
assert x is None
assert y in (1, 2)


def test_the_trio_scheduler_is_deterministic_under_hypothesis():
traces = []

@our_settings
@given(st.integers())
@pytest.mark.trio
async def inner(_):
traces.append(await scheduler_trace())

# The pytest.mark.trio doesn't do it's magic thing to
# inner functions, so we invoke it explicitly here.
inner.hypothesis.inner_test = _trio_test_runner_factory(
None, inner.hypothesis.inner_test
)
inner() # Tada, now it's a sync function!

assert len(traces) >= 5
assert len(set(traces)) == 1
21 changes: 18 additions & 3 deletions pytest_trio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@
# Ordered dict (and **kwargs) not available with Python<3.6
ORDERED_DICTS = False

try:
from hypothesis import register_random
except ImportError: # pragma: no cover
pass
else:
# On recent versions of Hypothesis, make the Trio scheduler deterministic
# even though it uses a module-scoped Random instance. This works
# regardless of whether or not the random_module strategy is used.
register_random(trio._core._run._r)
# We also have to enable determinism, which is disabled by default
# due to a small performance impact - but fine to enable in testing.
# See https://github.com/python-trio/trio/pull/890/ for details.
trio._core._run._ALLOW_DETERMINISTIC_SCHEDULING = True


def pytest_addoption(parser):
parser.addini(
Expand Down Expand Up @@ -225,7 +239,7 @@ async def run(self, test_ctx, contextvars_ctx):
# should cancel them.
assert not self.user_done_events
func_value = None
with trio.open_cancel_scope() as cancel_scope:
with trio.CancelScope() as cancel_scope:
test_ctx.test_cancel_scope = cancel_scope
assert not test_ctx.crashed
await self._func(**resolved_kwargs)
Expand Down Expand Up @@ -270,7 +284,7 @@ async def run(self, test_ctx, contextvars_ctx):
except BaseException as exc:
assert isinstance(exc, trio.Cancelled)
test_ctx.crash(None)
with trio.open_cancel_scope(shield=True):
with trio.CancelScope(shield=True):
for event in self.user_done_events:
await event.wait()

Expand Down Expand Up @@ -345,7 +359,8 @@ def pytest_runtest_call(item):
item.obj.hypothesis.inner_test = _trio_test_runner_factory(
item, item.obj.hypothesis.inner_test
)
elif getattr(item.obj, 'is_hypothesis_test', False):
elif getattr(item.obj, 'is_hypothesis_test',
False): # pragma: no cover
pytest.fail(
'test function `%r` is using Hypothesis, but pytest-trio '
'only works with Hypothesis 3.64.0 or later.' % item
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
packages=find_packages(),
entry_points={'pytest11': ['trio = pytest_trio.plugin']},
install_requires=[
"trio",
"trio >= 0.11",
"async_generator >= 1.9",
# For node.get_closest_marker
"pytest >= 3.6"
Expand Down