Skip to content

Commit aeb677f

Browse files
committed
gh-107862: Add stateful, timezones and ip_addresses to the Hypothesis stubs
The stubs in test.support._hypothesis_stubs only cover the strategies and decorators the existing property tests happen to use, so a test that wants hypothesis.stateful, or the timezones() and ip_addresses() strategies, fails to import when Hypothesis is not installed. This adds those names. The stateful API has no equivalent of @example, so there is nothing for the stub to replay: a state machine's TestCase is skipped and run_state_machine_as_test raises SkipTest. The decorators are accepted so that state machine classes can be defined at import time. Real Hypothesis only exposes hypothesis.stateful once the submodule has been imported, so hypothesis_helper now imports it, making the attribute available in both modes.
1 parent baec764 commit aeb677f

5 files changed

Lines changed: 108 additions & 1 deletion

File tree

‎Lib/test/support/_hypothesis_stubs/__init__.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
"assume",
99
"reject",
1010
"register_random",
11+
"stateful",
1112
"strategies",
1213
"HealthCheck",
1314
"settings",
1415
"Verbosity",
1516
]
1617

17-
from . import strategies
18+
from . import stateful, strategies
1819

1920

2021
def given(*_args, **_kwargs):
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""Stubs for the subset of ``hypothesis.stateful`` used by the test suite.
2+
3+
The stateful API has no equivalent of ``@example``, so without Hypothesis
4+
there is nothing to replay: the ``TestCase`` generated for a state machine
5+
is skipped, and ``run_state_machine_as_test`` raises ``unittest.SkipTest``.
6+
The decorators and helpers are accepted so that state machine classes can
7+
be defined at import time.
8+
"""
9+
import unittest
10+
11+
from ._helpers import stub_factory
12+
from .strategies import StubStrategy
13+
14+
__all__ = [
15+
"Bundle",
16+
"MultipleResults",
17+
"RuleBasedStateMachine",
18+
"consumes",
19+
"initialize",
20+
"invariant",
21+
"multiple",
22+
"precondition",
23+
"rule",
24+
"run_state_machine_as_test",
25+
]
26+
27+
_SKIP_REASON = "Hypothesis required for stateful test"
28+
29+
Bundle = stub_factory(StubStrategy, "hypothesis.stateful.Bundle")
30+
31+
32+
def consumes(bundle):
33+
return bundle
34+
35+
36+
class MultipleResults:
37+
def __init__(self, *values):
38+
self.values = values
39+
40+
41+
def multiple(*args):
42+
return MultipleResults(*args)
43+
44+
45+
def _identity_operator(f):
46+
return f
47+
48+
49+
def _identity_decorator(*_args, **_kwargs):
50+
return _identity_operator
51+
52+
53+
rule = _identity_decorator
54+
initialize = _identity_decorator
55+
invariant = _identity_decorator
56+
precondition = _identity_decorator
57+
58+
59+
class _TestCaseProperty:
60+
"""Build (and cache) a skipped ``unittest.TestCase`` per state machine."""
61+
62+
def __init__(self):
63+
self._cases = {}
64+
65+
def __get__(self, obj, owner):
66+
if owner not in self._cases:
67+
68+
class StateMachineTestCase(unittest.TestCase):
69+
settings = None
70+
71+
def runTest(self):
72+
raise unittest.SkipTest(_SKIP_REASON)
73+
74+
StateMachineTestCase.__name__ = f"{owner.__name__}.TestCase"
75+
StateMachineTestCase.__qualname__ = f"{owner.__qualname__}.TestCase"
76+
self._cases[owner] = StateMachineTestCase
77+
return self._cases[owner]
78+
79+
80+
class RuleBasedStateMachine:
81+
TestCase = _TestCaseProperty()
82+
83+
def bundle(self, name):
84+
return Bundle(name)
85+
86+
def check_invariants(self, settings, output, runtimes):
87+
pass
88+
89+
def setup_state(self):
90+
pass
91+
92+
def teardown(self):
93+
pass
94+
95+
96+
def run_state_machine_as_test(state_machine_factory, *, settings=None):
97+
raise unittest.SkipTest(_SKIP_REASON)

‎Lib/test/support/_hypothesis_stubs/strategies.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __or__(self, other):
4444
"frozensets",
4545
"functions",
4646
"integers",
47+
"ip_addresses",
4748
"iterables",
4849
"just",
4950
"lists",
@@ -62,6 +63,8 @@ def __or__(self, other):
6263
"slices",
6364
"timedeltas",
6465
"times",
66+
"timezone_keys",
67+
"timezones",
6568
"text",
6669
"tuples",
6770
"uuids",

‎Lib/test/support/hypothesis_helper.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
except ImportError:
66
from . import _hypothesis_stubs as hypothesis
77
else:
8+
# Real Hypothesis does not import this submodule on its own; do it here
9+
# so that ``hypothesis.stateful`` is available in both modes.
10+
import hypothesis.stateful
11+
812
# Regrtest changes to use a tempdir as the working directory, so we have
913
# to tell Hypothesis to use the original in order to persist the database.
1014
from test.support import has_socket_support
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``hypothesis.stateful`` and the ``timezones()`` and ``ip_addresses()``
2+
strategies to the Hypothesis stubs used when Hypothesis is not installed.

0 commit comments

Comments
 (0)