summaryrefslogtreecommitdiffstats
path: root/Lib/test/support
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/support')
-rw-r--r--Lib/test/support/_hypothesis_stubs/__init__.py111
-rw-r--r--Lib/test/support/_hypothesis_stubs/_helpers.py43
-rw-r--r--Lib/test/support/_hypothesis_stubs/strategies.py91
-rw-r--r--Lib/test/support/hypothesis_helper.py4
4 files changed, 249 insertions, 0 deletions
diff --git a/Lib/test/support/_hypothesis_stubs/__init__.py b/Lib/test/support/_hypothesis_stubs/__init__.py
new file mode 100644
index 0000000..6ba5bb8
--- /dev/null
+++ b/Lib/test/support/_hypothesis_stubs/__init__.py
@@ -0,0 +1,111 @@
+from enum import Enum
+import functools
+import unittest
+
+__all__ = [
+ "given",
+ "example",
+ "assume",
+ "reject",
+ "register_random",
+ "strategies",
+ "HealthCheck",
+ "settings",
+ "Verbosity",
+]
+
+from . import strategies
+
+
+def given(*_args, **_kwargs):
+ def decorator(f):
+ if examples := getattr(f, "_examples", []):
+
+ @functools.wraps(f)
+ def test_function(self):
+ for example_args, example_kwargs in examples:
+ with self.subTest(*example_args, **example_kwargs):
+ f(self, *example_args, **example_kwargs)
+
+ else:
+ # If we have found no examples, we must skip the test. If @example
+ # is applied after @given, it will re-wrap the test to remove the
+ # skip decorator.
+ test_function = unittest.skip(
+ "Hypothesis required for property test with no " +
+ "specified examples"
+ )(f)
+
+ test_function._given = True
+ return test_function
+
+ return decorator
+
+
+def example(*args, **kwargs):
+ if bool(args) == bool(kwargs):
+ raise ValueError("Must specify exactly one of *args or **kwargs")
+
+ def decorator(f):
+ base_func = getattr(f, "__wrapped__", f)
+ if not hasattr(base_func, "_examples"):
+ base_func._examples = []
+
+ base_func._examples.append((args, kwargs))
+
+ if getattr(f, "_given", False):
+ # If the given decorator is below all the example decorators,
+ # it would be erroneously skipped, so we need to re-wrap the new
+ # base function.
+ f = given()(base_func)
+
+ return f
+
+ return decorator
+
+
+def assume(condition):
+ if not condition:
+ raise unittest.SkipTest("Unsatisfied assumption")
+ return True
+
+
+def reject():
+ assume(False)
+
+
+def register_random(*args, **kwargs):
+ pass # pragma: no cover
+
+
+def settings(*args, **kwargs):
+ return lambda f: f # pragma: nocover
+
+
+class HealthCheck(Enum):
+ data_too_large = 1
+ filter_too_much = 2
+ too_slow = 3
+ return_value = 5
+ large_base_example = 7
+ not_a_test_method = 8
+
+ @classmethod
+ def all(cls):
+ return list(cls)
+
+
+class Verbosity(Enum):
+ quiet = 0
+ normal = 1
+ verbose = 2
+ debug = 3
+
+
+class Phase(Enum):
+ explicit = 0
+ reuse = 1
+ generate = 2
+ target = 3
+ shrink = 4
+ explain = 5
diff --git a/Lib/test/support/_hypothesis_stubs/_helpers.py b/Lib/test/support/_hypothesis_stubs/_helpers.py
new file mode 100644
index 0000000..3f6244e
--- /dev/null
+++ b/Lib/test/support/_hypothesis_stubs/_helpers.py
@@ -0,0 +1,43 @@
+# Stub out only the subset of the interface that we actually use in our tests.
+class StubClass:
+ def __init__(self, *args, **kwargs):
+ self.__stub_args = args
+ self.__stub_kwargs = kwargs
+ self.__repr = None
+
+ def _with_repr(self, new_repr):
+ new_obj = self.__class__(*self.__stub_args, **self.__stub_kwargs)
+ new_obj.__repr = new_repr
+ return new_obj
+
+ def __repr__(self):
+ if self.__repr is not None:
+ return self.__repr
+
+ argstr = ", ".join(self.__stub_args)
+ kwargstr = ", ".join(f"{kw}={val}" for kw, val in self.__stub_kwargs.items())
+
+ in_parens = argstr
+ if kwargstr:
+ in_parens += ", " + kwargstr
+
+ return f"{self.__class__.__qualname__}({in_parens})"
+
+
+def stub_factory(klass, name, *, with_repr=None, _seen={}):
+ if (klass, name) not in _seen:
+
+ class Stub(klass):
+ def __init__(self, *args, **kwargs):
+ super().__init__()
+ self.__stub_args = args
+ self.__stub_kwargs = kwargs
+
+ Stub.__name__ = name
+ Stub.__qualname__ = name
+ if with_repr is not None:
+ Stub._repr = None
+
+ _seen.setdefault((klass, name, with_repr), Stub)
+
+ return _seen[(klass, name, with_repr)]
diff --git a/Lib/test/support/_hypothesis_stubs/strategies.py b/Lib/test/support/_hypothesis_stubs/strategies.py
new file mode 100644
index 0000000..d2b885d
--- /dev/null
+++ b/Lib/test/support/_hypothesis_stubs/strategies.py
@@ -0,0 +1,91 @@
+import functools
+
+from ._helpers import StubClass, stub_factory
+
+
+class StubStrategy(StubClass):
+ def __make_trailing_repr(self, transformation_name, func):
+ func_name = func.__name__ or repr(func)
+ return f"{self!r}.{transformation_name}({func_name})"
+
+ def map(self, pack):
+ return self._with_repr(self.__make_trailing_repr("map", pack))
+
+ def flatmap(self, expand):
+ return self._with_repr(self.__make_trailing_repr("flatmap", expand))
+
+ def filter(self, condition):
+ return self._with_repr(self.__make_trailing_repr("filter", condition))
+
+ def __or__(self, other):
+ new_repr = f"one_of({self!r}, {other!r})"
+ return self._with_repr(new_repr)
+
+
+_STRATEGIES = {
+ "binary",
+ "booleans",
+ "builds",
+ "characters",
+ "complex_numbers",
+ "composite",
+ "data",
+ "dates",
+ "datetimes",
+ "decimals",
+ "deferred",
+ "dictionaries",
+ "emails",
+ "fixed_dictionaries",
+ "floats",
+ "fractions",
+ "from_regex",
+ "from_type",
+ "frozensets",
+ "functions",
+ "integers",
+ "iterables",
+ "just",
+ "lists",
+ "none",
+ "nothing",
+ "one_of",
+ "permutations",
+ "random_module",
+ "randoms",
+ "recursive",
+ "register_type_strategy",
+ "runner",
+ "sampled_from",
+ "sets",
+ "shared",
+ "slices",
+ "timedeltas",
+ "times",
+ "text",
+ "tuples",
+ "uuids",
+}
+
+__all__ = sorted(_STRATEGIES)
+
+
+def composite(f):
+ strategy = stub_factory(StubStrategy, f.__name__)
+
+ @functools.wraps(f)
+ def inner(*args, **kwargs):
+ return strategy(*args, **kwargs)
+
+ return inner
+
+
+def __getattr__(name):
+ if name not in _STRATEGIES:
+ raise AttributeError(f"Unknown attribute {name}")
+
+ return stub_factory(StubStrategy, f"hypothesis.strategies.{name}")
+
+
+def __dir__():
+ return __all__
diff --git a/Lib/test/support/hypothesis_helper.py b/Lib/test/support/hypothesis_helper.py
new file mode 100644
index 0000000..76bd249
--- /dev/null
+++ b/Lib/test/support/hypothesis_helper.py
@@ -0,0 +1,4 @@
+try:
+ import hypothesis
+except ImportError:
+ from . import _hypothesis_stubs as hypothesis