Skip to content

Commit f20adde

Browse files
authored
Merge pull request #73 from Zac-HD/hypothesis-determinism
Deterministic scheduling via Hypothesis
2 parents e828788 + 414ec14 commit f20adde

5 files changed

Lines changed: 50 additions & 5 deletions

File tree

newsfragments/73.feature.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pytest-trio now makes the Trio scheduler deterministic while running
2+
inside a Hypothesis test. Hopefully you won't see any change, but if
3+
you had scheduler-dependent bugs Hypothesis will be more effective now.

pytest_trio/_tests/test_fixture_ordering.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ async def crash_late_agen():
171171
raise RuntimeError("crash_late_agen".upper())
172172
173173
async def crash(when, token):
174-
with trio.open_cancel_scope(shield=True):
174+
with trio.CancelScope(shield=True):
175175
await trio.sleep(when)
176176
raise RuntimeError(token.upper())
177177

pytest_trio/_tests/test_hypothesis_interaction.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import pytest
2+
import trio
3+
from trio.tests.test_scheduler_determinism import (
4+
scheduler_trace, test_the_trio_scheduler_is_not_deterministic,
5+
test_the_trio_scheduler_is_deterministic_if_seeded
6+
)
27
from hypothesis import given, settings, strategies as st
38

9+
from pytest_trio.plugin import _trio_test_runner_factory
10+
411
# deadline=None avoids unpredictable warnings/errors when CI happens to be
512
# slow (example: https://travis-ci.org/python-trio/pytest-trio/jobs/406738296)
613
# max_examples=5 speeds things up a bit
@@ -28,3 +35,23 @@ async def test_mark_outer(n):
2835
async def test_mark_and_parametrize(x, y):
2936
assert x is None
3037
assert y in (1, 2)
38+
39+
40+
def test_the_trio_scheduler_is_deterministic_under_hypothesis():
41+
traces = []
42+
43+
@our_settings
44+
@given(st.integers())
45+
@pytest.mark.trio
46+
async def inner(_):
47+
traces.append(await scheduler_trace())
48+
49+
# The pytest.mark.trio doesn't do it's magic thing to
50+
# inner functions, so we invoke it explicitly here.
51+
inner.hypothesis.inner_test = _trio_test_runner_factory(
52+
None, inner.hypothesis.inner_test
53+
)
54+
inner() # Tada, now it's a sync function!
55+
56+
assert len(traces) >= 5
57+
assert len(set(traces)) == 1

pytest_trio/plugin.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@
2222
# Ordered dict (and **kwargs) not available with Python<3.6
2323
ORDERED_DICTS = False
2424

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

2640
def pytest_addoption(parser):
2741
parser.addini(
@@ -225,7 +239,7 @@ async def run(self, test_ctx, contextvars_ctx):
225239
# should cancel them.
226240
assert not self.user_done_events
227241
func_value = None
228-
with trio.open_cancel_scope() as cancel_scope:
242+
with trio.CancelScope() as cancel_scope:
229243
test_ctx.test_cancel_scope = cancel_scope
230244
assert not test_ctx.crashed
231245
await self._func(**resolved_kwargs)
@@ -270,7 +284,7 @@ async def run(self, test_ctx, contextvars_ctx):
270284
except BaseException as exc:
271285
assert isinstance(exc, trio.Cancelled)
272286
test_ctx.crash(None)
273-
with trio.open_cancel_scope(shield=True):
287+
with trio.CancelScope(shield=True):
274288
for event in self.user_done_events:
275289
await event.wait()
276290

@@ -345,7 +359,8 @@ def pytest_runtest_call(item):
345359
item.obj.hypothesis.inner_test = _trio_test_runner_factory(
346360
item, item.obj.hypothesis.inner_test
347361
)
348-
elif getattr(item.obj, 'is_hypothesis_test', False):
362+
elif getattr(item.obj, 'is_hypothesis_test',
363+
False): # pragma: no cover
349364
pytest.fail(
350365
'test function `%r` is using Hypothesis, but pytest-trio '
351366
'only works with Hypothesis 3.64.0 or later.' % item

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
packages=find_packages(),
1717
entry_points={'pytest11': ['trio = pytest_trio.plugin']},
1818
install_requires=[
19-
"trio",
19+
"trio >= 0.11",
2020
"async_generator >= 1.9",
2121
# For node.get_closest_marker
2222
"pytest >= 3.6"

0 commit comments

Comments
 (0)