diff --git a/Lib/test/support/_hypothesis_stubs/__init__.py b/Lib/test/support/_hypothesis_stubs/__init__.py index 6fa013b55b2ac46..e3cb54aad736d29 100644 --- a/Lib/test/support/_hypothesis_stubs/__init__.py +++ b/Lib/test/support/_hypothesis_stubs/__init__.py @@ -8,13 +8,14 @@ "assume", "reject", "register_random", + "stateful", "strategies", "HealthCheck", "settings", "Verbosity", ] -from . import strategies +from . import stateful, strategies def given(*_args, **_kwargs): diff --git a/Lib/test/support/_hypothesis_stubs/stateful.py b/Lib/test/support/_hypothesis_stubs/stateful.py new file mode 100644 index 000000000000000..53a550c5ccbad3b --- /dev/null +++ b/Lib/test/support/_hypothesis_stubs/stateful.py @@ -0,0 +1,97 @@ +"""Stubs for the subset of ``hypothesis.stateful`` used by the test suite. + +The stateful API has no equivalent of ``@example``, so without Hypothesis +there is nothing to replay: the ``TestCase`` generated for a state machine +is skipped, and ``run_state_machine_as_test`` raises ``unittest.SkipTest``. +The decorators and helpers are accepted so that state machine classes can +be defined at import time. +""" +import unittest + +from ._helpers import stub_factory +from .strategies import StubStrategy + +__all__ = [ + "Bundle", + "MultipleResults", + "RuleBasedStateMachine", + "consumes", + "initialize", + "invariant", + "multiple", + "precondition", + "rule", + "run_state_machine_as_test", +] + +_SKIP_REASON = "Hypothesis required for stateful test" + +Bundle = stub_factory(StubStrategy, "hypothesis.stateful.Bundle") + + +def consumes(bundle): + return bundle + + +class MultipleResults: + def __init__(self, *values): + self.values = values + + +def multiple(*args): + return MultipleResults(*args) + + +def _identity_operator(f): + return f + + +def _identity_decorator(*_args, **_kwargs): + return _identity_operator + + +rule = _identity_decorator +initialize = _identity_decorator +invariant = _identity_decorator +precondition = _identity_decorator + + +class _TestCaseProperty: + """Build (and cache) a skipped ``unittest.TestCase`` per state machine.""" + + def __init__(self): + self._cases = {} + + def __get__(self, obj, owner): + if owner not in self._cases: + + class StateMachineTestCase(unittest.TestCase): + settings = None + + def runTest(self): + raise unittest.SkipTest(_SKIP_REASON) + + StateMachineTestCase.__name__ = f"{owner.__name__}.TestCase" + StateMachineTestCase.__qualname__ = f"{owner.__qualname__}.TestCase" + self._cases[owner] = StateMachineTestCase + return self._cases[owner] + + +class RuleBasedStateMachine: + TestCase = _TestCaseProperty() + + def bundle(self, name): + return Bundle(name) + + def check_invariants(self, settings, output, runtimes): + pass + + def setup_state(self): + pass + + def teardown(self): + pass + + +def run_state_machine_as_test(state_machine_factory, *, settings=None): + raise unittest.SkipTest(_SKIP_REASON) diff --git a/Lib/test/support/_hypothesis_stubs/strategies.py b/Lib/test/support/_hypothesis_stubs/strategies.py index d2b885d41e16d0e..b58e36b41dbaf75 100644 --- a/Lib/test/support/_hypothesis_stubs/strategies.py +++ b/Lib/test/support/_hypothesis_stubs/strategies.py @@ -44,6 +44,7 @@ def __or__(self, other): "frozensets", "functions", "integers", + "ip_addresses", "iterables", "just", "lists", @@ -62,6 +63,8 @@ def __or__(self, other): "slices", "timedeltas", "times", + "timezone_keys", + "timezones", "text", "tuples", "uuids", diff --git a/Lib/test/support/hypothesis_helper.py b/Lib/test/support/hypothesis_helper.py index a99a4963ffecc94..6249ba43f06716a 100644 --- a/Lib/test/support/hypothesis_helper.py +++ b/Lib/test/support/hypothesis_helper.py @@ -5,6 +5,10 @@ except ImportError: from . import _hypothesis_stubs as hypothesis else: + # Real Hypothesis does not import this submodule on its own; do it here + # so that ``hypothesis.stateful`` is available in both modes. + import hypothesis.stateful + # Regrtest changes to use a tempdir as the working directory, so we have # to tell Hypothesis to use the original in order to persist the database. from test.support import has_socket_support diff --git a/Misc/NEWS.d/next/Tests/2026-10-19-17-05-00.gh-issue-107862.qH3sTa.rst b/Misc/NEWS.d/next/Tests/2026-10-19-17-05-00.gh-issue-107862.qH3sTa.rst new file mode 100644 index 000000000000000..624fd82d50041c1 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2026-10-19-17-05-00.gh-issue-107862.qH3sTa.rst @@ -0,0 +1,2 @@ +Add ``hypothesis.stateful`` and the ``timezones()`` and ``ip_addresses()`` +strategies to the Hypothesis stubs used when Hypothesis is not installed.