Skip to content

Commit

Permalink
Merge pull request #3983 from tybug/all-children-unbounded-integers
Browse files Browse the repository at this point in the history
Implement `all_children` for unbounded integers
  • Loading branch information
Zac-HD committed May 12, 2024
2 parents 89b78c2 + 81898d2 commit 6f12bab
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 14 deletions.
3 changes: 3 additions & 0 deletions hypothesis-python/RELEASE.rst
@@ -0,0 +1,3 @@
RELEASE_TYPE: patch

This patch fixes a rare internal error when using :func:`~hypothesis.strategies.integers` with a high ``max_examples`` setting (:issue:`3974`).
43 changes: 30 additions & 13 deletions hypothesis-python/src/hypothesis/internal/conjecture/datatree.py
Expand Up @@ -265,20 +265,37 @@ def all_children(ir_type, kwargs):
min_value = kwargs["min_value"]
max_value = kwargs["max_value"]
weights = kwargs["weights"]
# it's a bit annoying (but completely feasible) to implement the cases
# other than "both sides bounded" here. We haven't needed to yet because
# in practice we don't struggle with unbounded integer generation.
assert min_value is not None
assert max_value is not None

if weights is None:
yield from range(min_value, max_value + 1)

if min_value is None and max_value is None:
# full 128 bit range.
yield from range(-(2**127) + 1, 2**127 - 1)

elif min_value is not None and max_value is not None:
if weights is None:
yield from range(min_value, max_value + 1)
else:
# skip any values with a corresponding weight of 0 (can never be drawn).
for weight, n in zip(weights, range(min_value, max_value + 1)):
if weight == 0:
continue
yield n
else:
# skip any values with a corresponding weight of 0 (can never be drawn).
for weight, n in zip(weights, range(min_value, max_value + 1)):
if weight == 0:
continue
yield n
# hard case: only one bound was specified. Here we probe either upwards
# or downwards with our full 128 bit generation, but only half of these
# (plus one for the case of generating zero) result in a probe in the
# direction we want. ((2**128 - 1) // 2) + 1 == a range of 2 ** 127.
#
# strictly speaking, I think this is not actually true: if
# max_value > shrink_towards then our range is ((-2**127) + 1, max_value),
# and it only narrows when max_value < shrink_towards. But it
# really doesn't matter for this case because (even half) unbounded
# integers generation is hit extremely rarely.
assert (min_value is None) ^ (max_value is None)
if min_value is None:
yield from range(max_value - (2**127) + 1, max_value)
else:
assert max_value is None
yield from range(min_value, min_value + (2**127) - 1)

if ir_type == "boolean":
p = kwargs["p"]
Expand Down
14 changes: 13 additions & 1 deletion hypothesis-python/tests/conjecture/test_ir.py
Expand Up @@ -255,7 +255,6 @@ def test_draw_string_single_interval_with_equal_bounds(s, n):
"min_value": 1,
"max_value": 2,
"weights": [0, 1],
"smallest_nonzero_magnitude": SMALLEST_SUBNORMAL,
},
)
)
Expand All @@ -273,6 +272,19 @@ def test_compute_max_children_and_all_children_agree(ir_type_and_kwargs):
assert len(list(all_children(ir_type, kwargs))) == max_children


# it's very hard to test that unbounded integer ranges agree with
# compute_max_children, because they by necessity require iterating over 2**127
# or more elements. We do the not great approximation of checking just the first
# element is what we expect.
@pytest.mark.parametrize(
"min_value, max_value, first",
[(None, None, -(2**127) + 1), (None, 42, (-(2**127) + 1) + 42), (42, None, 42)],
)
def test_compute_max_children_unbounded_integer_ranges(min_value, max_value, first):
kwargs = {"min_value": min_value, "max_value": max_value, "weights": None}
assert first == next(all_children("integer", kwargs))


@given(st.randoms())
def test_ir_nodes(random):
data = fresh_data(random=random)
Expand Down

0 comments on commit 6f12bab

Please sign in to comment.