Skip to content

Commit

Permalink
respect forced for ir tree nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
tybug committed Mar 18, 2024
1 parent fc9c009 commit 2e688b1
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 60 deletions.
75 changes: 26 additions & 49 deletions hypothesis-python/src/hypothesis/internal/conjecture/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1909,10 +1909,11 @@ def draw_integer(
)

if self.ir_tree_nodes is not None and observe:
node = self._pop_ir_tree_node("integer", kwargs, forced=forced)
assert isinstance(node.value, int)
forced = node.value
fake_forced = not node.was_forced
node = self._pop_ir_tree_node("integer", kwargs)
if forced is None:
assert isinstance(node.value, int)
forced = node.value
fake_forced = True

value = self.provider.draw_integer(
**kwargs, forced=forced, fake_forced=fake_forced
Expand Down Expand Up @@ -1965,10 +1966,11 @@ def draw_float(
)

if self.ir_tree_nodes is not None and observe:
node = self._pop_ir_tree_node("float", kwargs, forced=forced)
assert isinstance(node.value, float)
forced = node.value
fake_forced = not node.was_forced
node = self._pop_ir_tree_node("float", kwargs)
if forced is None:
assert isinstance(node.value, float)
forced = node.value
fake_forced = True

value = self.provider.draw_float(
**kwargs, forced=forced, fake_forced=fake_forced
Expand Down Expand Up @@ -2006,10 +2008,11 @@ def draw_string(
},
)
if self.ir_tree_nodes is not None and observe:
node = self._pop_ir_tree_node("string", kwargs, forced=forced)
assert isinstance(node.value, str)
forced = node.value
fake_forced = not node.was_forced
node = self._pop_ir_tree_node("string", kwargs)
if forced is None:
assert isinstance(node.value, str)
forced = node.value
fake_forced = True

value = self.provider.draw_string(
**kwargs, forced=forced, fake_forced=fake_forced
Expand Down Expand Up @@ -2041,10 +2044,11 @@ def draw_bytes(
kwargs: BytesKWargs = self._pooled_kwargs("bytes", {"size": size})

if self.ir_tree_nodes is not None and observe:
node = self._pop_ir_tree_node("bytes", kwargs, forced=forced)
assert isinstance(node.value, bytes)
forced = node.value
fake_forced = not node.was_forced
node = self._pop_ir_tree_node("bytes", kwargs)
if forced is None:
assert isinstance(node.value, bytes)
forced = node.value
fake_forced = True

value = self.provider.draw_bytes(
**kwargs, forced=forced, fake_forced=fake_forced
Expand Down Expand Up @@ -2082,10 +2086,11 @@ def draw_boolean(
kwargs: BooleanKWargs = self._pooled_kwargs("boolean", {"p": p})

if self.ir_tree_nodes is not None and observe:
node = self._pop_ir_tree_node("boolean", kwargs, forced=forced)
assert isinstance(node.value, bool)
forced = node.value
fake_forced = not node.was_forced
node = self._pop_ir_tree_node("boolean", kwargs)
if forced is None:
assert isinstance(node.value, bool)
forced = node.value
fake_forced = True

value = self.provider.draw_boolean(
**kwargs, forced=forced, fake_forced=fake_forced
Expand Down Expand Up @@ -2122,9 +2127,7 @@ def _pooled_kwargs(self, ir_type, kwargs):
POOLED_KWARGS_CACHE[key] = kwargs
return kwargs

def _pop_ir_tree_node(
self, ir_type: IRTypeName, kwargs: IRKWargsType, *, forced: Optional[IRType]
) -> IRNode:
def _pop_ir_tree_node(self, ir_type: IRTypeName, kwargs: IRKWargsType) -> IRNode:
assert self.ir_tree_nodes is not None

if self.ir_tree_nodes == []:
Expand All @@ -2151,32 +2154,6 @@ def _pop_ir_tree_node(
if not ir_value_permitted(node.value, node.ir_type, kwargs):
self.mark_invalid() # pragma: no cover # FIXME @tybug

if forced is not None:
# if we expected a forced node but are instead returning a non-forced
# node, something has gone terribly wrong. If we allowed this combination,
# we risk violating core invariants that rely on forced draws being,
# well, forced to a particular value.
#
# In particular, this can manifest while shrinking. Consider the tree
# [boolean True [forced] {"p": 0.5}]
# [boolean False {"p": 0.5}]
#
# and the shrinker tries to reorder these to
# [boolean False {"p": 0.5}]
# [boolean True [forced] {"p": 0.5}].
#
# However, maybe we got lucky and the non-forced node is returning
# the same value that was expected from the forced draw. We lucked
# into an aligned tree in this case and can let it slide.
if not node.was_forced and not ir_value_equal(ir_type, forced, node.value):
self.mark_invalid()

# similarly, if we expected a forced node with a certain value, and
# are returning a forced node with a different value, this is an
# equally bad misalignment.
if node.was_forced and not ir_value_equal(ir_type, forced, node.value):
self.mark_invalid()

return node

def as_result(self) -> Union[ConjectureResult, _Overrun]:
Expand Down
20 changes: 9 additions & 11 deletions hypothesis-python/tests/conjecture/test_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ def test_data_with_misaligned_ir_tree_is_invalid(data):


@given(st.data())
def test_data_with_changed_was_forced_is_invalid(data):
def test_data_with_changed_was_forced(data):
# we had a normal node and then tried to draw a different forced value from it.
# ir tree: v1 [was_forced=False]
# drawing: [forced=v2]
Expand All @@ -398,18 +398,19 @@ def test_data_with_changed_was_forced_is_invalid(data):
kwargs["forced"] = draw_value(node.ir_type, node.kwargs)
assume(not ir_value_equal(node.ir_type, kwargs["forced"], node.value))

with pytest.raises(StopTest):
draw_func(**kwargs)

assert data.status is Status.INVALID
assert ir_value_equal(node.ir_type, draw_func(**kwargs), kwargs["forced"])


@given(st.data())
@settings(suppress_health_check=[HealthCheck.too_slow])
def test_data_with_changed_forced_value_is_invalid(data):
def test_data_with_changed_forced_value(data):
# we had a forced node and then tried to draw a different forced value from it.
# ir tree: v1 [was_forced=True]
# drawing: [forced=v2]
#
# This is actually fine; we'll just ignore the forced node (v1) and return
# what the draw expects (v2).

node = data.draw(ir_nodes(was_forced=True))
data = ConjectureData.for_ir_tree([node])

Expand All @@ -418,10 +419,7 @@ def test_data_with_changed_forced_value_is_invalid(data):
kwargs["forced"] = draw_value(node.ir_type, node.kwargs)
assume(not ir_value_equal(node.ir_type, kwargs["forced"], node.value))

with pytest.raises(StopTest):
draw_func(**kwargs)

assert data.status is Status.INVALID
assert ir_value_equal(node.ir_type, draw_func(**kwargs), kwargs["forced"])


@given(st.data())
Expand All @@ -436,7 +434,7 @@ def test_data_with_same_forced_value_is_valid(data):

kwargs = deepcopy(node.kwargs)
kwargs["forced"] = node.value
draw_func(**kwargs)
assert ir_value_equal(node.ir_type, draw_func(**kwargs), kwargs["forced"])


@given(ir_types_and_kwargs())
Expand Down

0 comments on commit 2e688b1

Please sign in to comment.