Skip to content

Commit 61b2940

Browse files
authored
Merge pull request #3751 from nickcollins/no-assume-outside-context-4
2 parents f73dab7 + d4665b9 commit 61b2940

13 files changed

Lines changed: 174 additions & 162 deletions

File tree

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ their individual contributions.
127127
* `Munir Abdinur <https://www.github.com/mabdinur>`_
128128
* `Nicholas Chammas <https://www.github.com/nchammas>`_
129129
* `Nick Anyos <https://www.github.com/NickAnyos>`_
130+
* `Nick Collins <https://github.com/nickcollins>` _
130131
* `Nick Muoh <https://github.com/OdinTech3>`_ ([email protected])
131132
* `Nicolas Erni <https://www.github.com/ThunderKey>`_
132133
* `Nikita Sobolev <https://github.com/sobolevn>`_ ([email protected])

hypothesis-python/RELEASE.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
RELEASE_TYPE: minor
2+
3+
This release deprecates use of :func:`~hypothesis.assume` and ``reject()``
4+
outside of property-based tests, because these functions work by raising a
5+
special exception (:issue:`3743`). It also fixes some type annotations
6+
(:issue:`3753`).

hypothesis-python/src/hypothesis/control.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from typing import NoReturn, Union
1414

1515
from hypothesis import Verbosity, settings
16+
from hypothesis._settings import note_deprecation
1617
from hypothesis.errors import InvalidArgument, UnsatisfiedAssumption
1718
from hypothesis.internal.compat import BaseExceptionGroup
1819
from hypothesis.internal.conjecture.data import ConjectureData
@@ -24,6 +25,12 @@
2425

2526

2627
def reject() -> NoReturn:
28+
if _current_build_context.value is None:
29+
note_deprecation(
30+
"Using `reject` outside a property-based test is deprecated",
31+
since="RELEASEDAY",
32+
has_codemod=False,
33+
)
2734
raise UnsatisfiedAssumption
2835

2936

@@ -34,6 +41,12 @@ def assume(condition: object) -> bool:
3441
This allows you to specify properties that you *assume* will be
3542
true, and let Hypothesis try to avoid similar examples in future.
3643
"""
44+
if _current_build_context.value is None:
45+
note_deprecation(
46+
"Using `assume` outside a property-based test is deprecated",
47+
since="RELEASEDAY",
48+
has_codemod=False,
49+
)
3750
if not condition:
3851
raise UnsatisfiedAssumption
3952
return True

hypothesis-python/src/hypothesis/core.py

Lines changed: 105 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
Unsatisfiable,
6767
UnsatisfiedAssumption,
6868
)
69-
from hypothesis.executors import default_new_style_executor, new_style_executor
7069
from hypothesis.internal.compat import (
7170
PYPY,
7271
BaseExceptionGroup,
@@ -635,8 +634,6 @@ def process_arguments_to_given(wrapped_test, arguments, kwargs, given_kwargs, pa
635634
if is_mock(selfy):
636635
selfy = None
637636

638-
test_runner = new_style_executor(selfy)
639-
640637
arguments = tuple(arguments)
641638

642639
with ensure_free_stackframes():
@@ -646,7 +643,7 @@ def process_arguments_to_given(wrapped_test, arguments, kwargs, given_kwargs, pa
646643

647644
stuff = Stuff(selfy=selfy, args=arguments, kwargs=kwargs, given_kwargs=given_kwargs)
648645

649-
return arguments, kwargs, test_runner, stuff
646+
return arguments, kwargs, stuff
650647

651648

652649
def skip_exceptions_to_reraise():
@@ -704,9 +701,38 @@ def new_given_signature(original_sig, given_kwargs):
704701
)
705702

706703

704+
def default_executor(data, function):
705+
return function(data)
706+
707+
708+
def get_executor(runner):
709+
try:
710+
execute_example = runner.execute_example
711+
except AttributeError:
712+
pass
713+
else:
714+
return lambda data, function: execute_example(partial(function, data))
715+
716+
if hasattr(runner, "setup_example") or hasattr(runner, "teardown_example"):
717+
setup = getattr(runner, "setup_example", None) or (lambda: None)
718+
teardown = getattr(runner, "teardown_example", None) or (lambda ex: None)
719+
720+
def execute(data, function):
721+
token = None
722+
try:
723+
token = setup()
724+
return function(data)
725+
finally:
726+
teardown(token)
727+
728+
return execute
729+
730+
return default_executor
731+
732+
707733
class StateForActualGivenExecution:
708-
def __init__(self, test_runner, stuff, test, settings, random, wrapped_test):
709-
self.test_runner = test_runner
734+
def __init__(self, stuff, test, settings, random, wrapped_test):
735+
self.test_runner = get_executor(stuff.selfy)
710736
self.stuff = stuff
711737
self.settings = settings
712738
self.last_exception = None
@@ -780,63 +806,65 @@ def test(*args, **kwargs):
780806

781807
def run(data):
782808
# Set up dynamic context needed by a single test run.
783-
with local_settings(self.settings):
784-
with deterministic_PRNG():
785-
with BuildContext(data, is_final=is_final) as context:
786-
if self.stuff.selfy is not None:
787-
data.hypothesis_runner = self.stuff.selfy
788-
# Generate all arguments to the test function.
789-
args = self.stuff.args
790-
kwargs = dict(self.stuff.kwargs)
791-
if example_kwargs is None:
792-
a, kw, argslices = context.prep_args_kwargs_from_strategies(
793-
(), self.stuff.given_kwargs
794-
)
795-
assert not a, "strategies all moved to kwargs by now"
796-
else:
797-
kw = example_kwargs
798-
argslices = {}
799-
kwargs.update(kw)
800-
if expected_failure is not None:
801-
nonlocal text_repr
802-
text_repr = repr_call(test, args, kwargs)
803-
if text_repr in self.xfail_example_reprs:
804-
warnings.warn(
805-
f"We generated {text_repr}, which seems identical "
806-
"to one of your `@example(...).xfail()` cases. "
807-
"Revise the strategy to avoid this overlap?",
808-
HypothesisWarning,
809-
# Checked in test_generating_xfailed_examples_warns!
810-
stacklevel=6,
811-
)
812-
813-
if print_example or current_verbosity() >= Verbosity.verbose:
814-
printer = RepresentationPrinter(context=context)
815-
if print_example:
816-
printer.text("Falsifying example:")
817-
else:
818-
printer.text("Trying example:")
819-
820-
if self.print_given_args:
821-
printer.text(" ")
822-
printer.repr_call(
823-
test.__name__,
824-
args,
825-
kwargs,
826-
force_split=True,
827-
arg_slices=argslices,
828-
leading_comment=(
829-
"# " + context.data.slice_comments[(0, 0)]
830-
if (0, 0) in context.data.slice_comments
831-
else None
832-
),
833-
)
834-
report(printer.getvalue())
835-
return test(*args, **kwargs)
809+
if self.stuff.selfy is not None:
810+
data.hypothesis_runner = self.stuff.selfy
811+
# Generate all arguments to the test function.
812+
args = self.stuff.args
813+
kwargs = dict(self.stuff.kwargs)
814+
if example_kwargs is None:
815+
a, kw, argslices = context.prep_args_kwargs_from_strategies(
816+
(), self.stuff.given_kwargs
817+
)
818+
assert not a, "strategies all moved to kwargs by now"
819+
else:
820+
kw = example_kwargs
821+
argslices = {}
822+
kwargs.update(kw)
823+
if expected_failure is not None:
824+
nonlocal text_repr
825+
text_repr = repr_call(test, args, kwargs)
826+
if text_repr in self.xfail_example_reprs:
827+
warnings.warn(
828+
f"We generated {text_repr}, which seems identical "
829+
"to one of your `@example(...).xfail()` cases. "
830+
"Revise the strategy to avoid this overlap?",
831+
HypothesisWarning,
832+
# Checked in test_generating_xfailed_examples_warns!
833+
stacklevel=6,
834+
)
836835

837-
# Run the test function once, via the executor hook.
838-
# In most cases this will delegate straight to `run(data)`.
839-
result = self.test_runner(data, run)
836+
if print_example or current_verbosity() >= Verbosity.verbose:
837+
printer = RepresentationPrinter(context=context)
838+
if print_example:
839+
printer.text("Falsifying example:")
840+
else:
841+
printer.text("Trying example:")
842+
843+
if self.print_given_args:
844+
printer.text(" ")
845+
printer.repr_call(
846+
test.__name__,
847+
args,
848+
kwargs,
849+
force_split=True,
850+
arg_slices=argslices,
851+
leading_comment=(
852+
"# " + context.data.slice_comments[(0, 0)]
853+
if (0, 0) in context.data.slice_comments
854+
else None
855+
),
856+
)
857+
report(printer.getvalue())
858+
return test(*args, **kwargs)
859+
860+
# self.test_runner can include the execute_example method, or setup/teardown
861+
# _example, so it's important to get the PRNG and build context in place first.
862+
with local_settings(self.settings):
863+
with deterministic_PRNG():
864+
with BuildContext(data, is_final=is_final) as context:
865+
# Run the test function once, via the executor hook.
866+
# In most cases this will delegate straight to `run(data)`.
867+
result = self.test_runner(data, run)
840868

841869
# If a failure was expected, it should have been raised already, so
842870
# instead raise an appropriate diagnostic error.
@@ -1146,7 +1174,16 @@ def fuzz_one_input(
11461174

11471175
@overload
11481176
def given(
1149-
*_given_arguments: Union[SearchStrategy[Any], EllipsisType],
1177+
_: EllipsisType, /
1178+
) -> Callable[
1179+
[Callable[..., Optional[Coroutine[Any, Any, None]]]], Callable[[], None]
1180+
]: # pragma: no cover
1181+
...
1182+
1183+
1184+
@overload
1185+
def given(
1186+
*_given_arguments: SearchStrategy[Any],
11501187
) -> Callable[
11511188
[Callable[..., Optional[Coroutine[Any, Any, None]]]], Callable[..., None]
11521189
]: # pragma: no cover
@@ -1253,14 +1290,13 @@ def wrapped_test(*arguments, **kwargs):
12531290

12541291
random = get_random_for_wrapped_test(test, wrapped_test)
12551292

1256-
processed_args = process_arguments_to_given(
1293+
arguments, kwargs, stuff = process_arguments_to_given(
12571294
wrapped_test, arguments, kwargs, given_kwargs, new_signature.parameters
12581295
)
1259-
arguments, kwargs, test_runner, stuff = processed_args
12601296

12611297
if (
12621298
inspect.iscoroutinefunction(test)
1263-
and test_runner is default_new_style_executor
1299+
and get_executor(stuff.selfy) is default_executor
12641300
):
12651301
# See https://github.com/HypothesisWorks/hypothesis/issues/3054
12661302
# If our custom executor doesn't handle coroutines, or we return an
@@ -1311,7 +1347,7 @@ def wrapped_test(*arguments, **kwargs):
13111347
fail_health_check(settings, msg, HealthCheck.differing_executors)
13121348

13131349
state = StateForActualGivenExecution(
1314-
test_runner, stuff, test, settings, random, wrapped_test
1350+
stuff, test, settings, random, wrapped_test
13151351
)
13161352

13171353
reproduce_failure = wrapped_test._hypothesis_internal_use_reproduce_failure
@@ -1454,13 +1490,13 @@ def _get_fuzz_target() -> (
14541490
parent=wrapped_test._hypothesis_internal_use_settings, deadline=None
14551491
)
14561492
random = get_random_for_wrapped_test(test, wrapped_test)
1457-
_args, _kwargs, test_runner, stuff = process_arguments_to_given(
1493+
_args, _kwargs, stuff = process_arguments_to_given(
14581494
wrapped_test, (), {}, given_kwargs, new_signature.parameters
14591495
)
14601496
assert not _args
14611497
assert not _kwargs
14621498
state = StateForActualGivenExecution(
1463-
test_runner, stuff, test, settings, random, wrapped_test
1499+
stuff, test, settings, random, wrapped_test
14641500
)
14651501
digest = function_digest(test)
14661502
# We track the minimal-so-far example for each distinct origin, so

hypothesis-python/src/hypothesis/executors.py

Lines changed: 0 additions & 65 deletions
This file was deleted.

hypothesis-python/src/hypothesis/strategies/_internal/core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
from hypothesis.strategies._internal.shared import SharedStrategy
123123
from hypothesis.strategies._internal.strategies import (
124124
Ex,
125+
Ex_Inv,
125126
SampledFromStrategy,
126127
T,
127128
one_of,
@@ -1136,7 +1137,7 @@ def builds(
11361137

11371138
@cacheable
11381139
@defines_strategy(never_lazy=True)
1139-
def from_type(thing: Type[Ex]) -> SearchStrategy[Ex]:
1140+
def from_type(thing: Type[Ex_Inv]) -> SearchStrategy[Ex_Inv]:
11401141
"""Looks up the appropriate search strategy for the given type.
11411142
11421143
``from_type`` is used internally to fill in missing arguments to

0 commit comments

Comments
 (0)