Skip to content

Commit c79e893

Browse files
authored
Merge pull request #4022 from tybug/post-test-case-hook-fix
Adjust `post_test_case_hook` scope
2 parents e51d473 + 976f559 commit c79e893

File tree

4 files changed

+76
-12
lines changed

4 files changed

+76
-12
lines changed

hypothesis-python/RELEASE.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
RELEASE_TYPE: patch
2+
3+
This patch fixes an issue when realizing symbolics with our experimental :obj:`~hypothesis.settings.backend` setting.

hypothesis-python/src/hypothesis/internal/conjecture/data.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,7 +1211,7 @@ class PrimitiveProvider(abc.ABC):
12111211
def __init__(self, conjecturedata: Optional["ConjectureData"], /) -> None:
12121212
self._cd = conjecturedata
12131213

1214-
def post_test_case_hook(self, value):
1214+
def post_test_case_hook(self, value: IRType) -> IRType:
12151215
# hook for providers to modify values returned by draw_* after a full
12161216
# test case concludes. Originally exposed for crosshair to reify its
12171217
# symbolic values into actual values.
@@ -1966,7 +1966,9 @@ def __init__(
19661966
self.max_depth = 0
19671967
self.has_discards = False
19681968

1969-
self.provider = provider(self) if isinstance(provider, type) else provider
1969+
self.provider: PrimitiveProvider = (
1970+
provider(self) if isinstance(provider, type) else provider
1971+
)
19701972
assert isinstance(self.provider, PrimitiveProvider)
19711973

19721974
self.__result: "Optional[ConjectureResult]" = None

hypothesis-python/src/hypothesis/internal/conjecture/engine.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from hypothesis import HealthCheck, Phase, Verbosity, settings as Settings
3939
from hypothesis._settings import local_settings
4040
from hypothesis.database import ExampleDatabase
41-
from hypothesis.errors import InvalidArgument, StopTest
41+
from hypothesis.errors import Flaky, HypothesisException, InvalidArgument, StopTest
4242
from hypothesis.internal.cache import LRUReusedCache
4343
from hypothesis.internal.compat import (
4444
NotRequired,
@@ -453,6 +453,24 @@ def test_function(self, data: ConjectureData) -> None:
453453
),
454454
}
455455
self.stats_per_test_case.append(call_stats)
456+
if self.settings.backend != "hypothesis":
457+
for node in data.examples.ir_tree_nodes:
458+
value = data.provider.post_test_case_hook(node.value)
459+
expected_type = {
460+
"string": str,
461+
"float": float,
462+
"integer": int,
463+
"boolean": bool,
464+
"bytes": bytes,
465+
}[node.ir_type]
466+
if type(value) is not expected_type:
467+
raise HypothesisException(
468+
f"expected {expected_type} from "
469+
f"{data.provider.post_test_case_hook.__qualname__}, "
470+
f"got {type(value)} ({value!r})"
471+
)
472+
node.value = value
473+
456474
self._cache(data)
457475
if data.invalid_at is not None: # pragma: no branch # coverage bug?
458476
self.misaligned_count += 1
@@ -496,18 +514,23 @@ def test_function(self, data: ConjectureData) -> None:
496514

497515
if data.status == Status.INTERESTING:
498516
if self.settings.backend != "hypothesis":
499-
for node in data.examples.ir_tree_nodes:
500-
value = data.provider.post_test_case_hook(node.value)
501-
# require providers to return something valid here.
502-
assert (
503-
value is not None
504-
), "providers must return a non-null value from post_test_case_hook"
505-
node.value = value
506-
507517
# drive the ir tree through the test function to convert it
508518
# to a buffer
519+
initial_origin = data.interesting_origin
509520
data = ConjectureData.for_ir_tree(data.examples.ir_tree_nodes)
510521
self.__stoppable_test_function(data)
522+
data.freeze()
523+
# we'd like to use expected_failure machinery here from
524+
# StateForActualGivenExecution for better diagnostic reports of eg
525+
# flaky deadlines, but we're too low down in the engine for that.
526+
# for now a worse generic flaky error will have to do.
527+
if data.status != Status.INTERESTING:
528+
raise Flaky(
529+
f"Inconsistent results from replaying a failing test case!\n"
530+
f" last: {Status.INTERESTING.name} from {initial_origin}\n"
531+
f" this: {data.status.name}"
532+
)
533+
511534
self._cache(data)
512535

513536
key = data.interesting_origin

hypothesis-python/tests/conjecture/test_alt_backend.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from hypothesis import given, settings, strategies as st
2020
from hypothesis.database import InMemoryExampleDatabase
21-
from hypothesis.errors import InvalidArgument
21+
from hypothesis.errors import Flaky, HypothesisException, InvalidArgument
2222
from hypothesis.internal.compat import int_to_bytes
2323
from hypothesis.internal.conjecture.data import (
2424
AVAILABLE_PROVIDERS,
@@ -354,3 +354,39 @@ def test_function(n):
354354
<= test_case_lifetime_init_count
355355
<= test_function_count + 10
356356
)
357+
358+
359+
def test_flaky_with_backend():
360+
with temp_register_backend("trivial", TrivialProvider):
361+
362+
calls = 0
363+
364+
@given(st.integers())
365+
@settings(backend="trivial", database=None)
366+
def test_function(n):
367+
nonlocal calls
368+
calls += 1
369+
assert n != calls % 2
370+
371+
with pytest.raises(Flaky):
372+
test_function()
373+
374+
375+
class BadPostTestCaseHookProvider(TrivialProvider):
376+
def post_test_case_hook(self, value):
377+
return None
378+
379+
380+
def test_bad_post_test_case_hook():
381+
with temp_register_backend("bad_hook", BadPostTestCaseHookProvider):
382+
383+
@given(st.integers())
384+
@settings(backend="bad_hook")
385+
def test_function(n):
386+
pass
387+
388+
with pytest.raises(
389+
HypothesisException,
390+
match="expected .* from BadPostTestCaseHookProvider.post_test_case_hook",
391+
):
392+
test_function()

0 commit comments

Comments
 (0)