Skip to content

Commit cd3742b

Browse files
committed
fix semi-bounded all children computation
1 parent 4e5a249 commit cd3742b

File tree

5 files changed

+68
-22
lines changed

5 files changed

+68
-22
lines changed

hypothesis-python/RELEASE.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
RELEASE_TYPE: patch
2+
3+
This patch fixes a rare internal error when using :func:`~hypothesis.strategies.integers` with a high number of examples and certain ``{min, max}_value`` parameters (:pull:`4059`).

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

+9-3
Original file line numberDiff line numberDiff line change
@@ -2083,11 +2083,17 @@ def draw_integer(
20832083
assert len(weights) == width
20842084

20852085
if forced is not None and (min_value is None or max_value is None):
2086-
# We draw `forced=forced - shrink_towards` here internally. If that
2087-
# grows larger than a 128 bit signed integer, we can't represent it.
2086+
# We draw `forced=forced - shrink_towards` here internally, after clamping.
2087+
# If that grows larger than a 128 bit signed integer, we can't represent it.
20882088
# Disallow this combination for now.
20892089
# Note that bit_length() = 128 -> signed bit size = 129.
2090-
assert (forced - shrink_towards).bit_length() < 128
2090+
_shrink_towards = shrink_towards
2091+
if min_value is not None:
2092+
_shrink_towards = max(min_value, _shrink_towards)
2093+
if max_value is not None:
2094+
_shrink_towards = min(max_value, _shrink_towards)
2095+
2096+
assert (forced - _shrink_towards).bit_length() < 128
20912097
if forced is not None and min_value is not None:
20922098
assert min_value <= forced
20932099
if forced is not None and max_value is not None:

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

+8-12
Original file line numberDiff line numberDiff line change
@@ -285,22 +285,18 @@ def all_children(ir_type, kwargs):
285285
continue
286286
yield n
287287
else:
288-
# hard case: only one bound was specified. Here we probe either upwards
289-
# or downwards with our full 128 bit generation, but only half of these
290-
# (plus one for the case of generating zero) result in a probe in the
291-
# direction we want. ((2**128 - 1) // 2) + 1 == a range of 2 ** 127.
292-
#
293-
# strictly speaking, I think this is not actually true: if
294-
# max_value > shrink_towards then our range is ((-2**127) + 1, max_value),
295-
# and it only narrows when max_value < shrink_towards. But it
296-
# really doesn't matter for this case because (even half) unbounded
297-
# integers generation is hit extremely rarely.
298288
assert (min_value is None) ^ (max_value is None)
289+
# hard case: only one bound was specified. Here we probe in 128 bits
290+
# around shrink_towards, and discard those above max_value or below
291+
# min_value respectively.
292+
shrink_towards = kwargs["shrink_towards"]
299293
if min_value is None:
300-
yield from range(max_value - (2**127) + 1, max_value)
294+
shrink_towards = min(max_value, shrink_towards)
295+
yield from range(shrink_towards - (2**127) + 1, max_value)
301296
else:
302297
assert max_value is None
303-
yield from range(min_value, min_value + (2**127) - 1)
298+
shrink_towards = max(min_value, shrink_towards)
299+
yield from range(min_value, shrink_towards + (2**127) - 1)
304300

305301
if ir_type == "boolean":
306302
p = kwargs["p"]

hypothesis-python/tests/conjecture/test_forced.py

+20
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,23 @@ def test_forced_floats_with_nan(random, sign, min_value, max_value):
225225
# trying to use float clampers that didn't exist when drawing.
226226
data = fresh_data(random=random)
227227
data.draw_float(min_value=min_value, max_value=max_value, forced=sign * math.nan)
228+
229+
230+
@given(st.data())
231+
def test_forced_with_large_magnitude_integers(data):
232+
bound_offset = data.draw(st.integers(min_value=0))
233+
# forced_offset = bound_offset + st.integers(min_value=0) may look cleaner, but
234+
# has subtly different maximum value semantics as it is twice the range of a
235+
# single draw
236+
forced_offset = data.draw(st.integers(min_value=bound_offset))
237+
238+
half_range = 2**127 + 1
239+
cd = fresh_data()
240+
cd.draw_integer(
241+
min_value=half_range + bound_offset, forced=half_range + forced_offset
242+
)
243+
244+
cd = fresh_data()
245+
cd.draw_integer(
246+
max_value=-(half_range + bound_offset), forced=-(half_range + forced_offset)
247+
)

hypothesis-python/tests/conjecture/test_ir.py

+28-7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
from tests.common.debug import minimal
3535
from tests.conjecture.common import (
36+
draw_integer_kwargs,
3637
draw_value,
3738
fresh_data,
3839
ir_nodes,
@@ -272,13 +273,33 @@ def test_compute_max_children_and_all_children_agree(ir_type_and_kwargs):
272273
# compute_max_children, because they by necessity require iterating over 2**127
273274
# or more elements. We do the not great approximation of checking just the first
274275
# element is what we expect.
275-
@pytest.mark.parametrize(
276-
"min_value, max_value, first",
277-
[(None, None, -(2**127) + 1), (None, 42, (-(2**127) + 1) + 42), (42, None, 42)],
278-
)
279-
def test_compute_max_children_unbounded_integer_ranges(min_value, max_value, first):
280-
kwargs = {"min_value": min_value, "max_value": max_value, "weights": None}
281-
assert first == next(all_children("integer", kwargs))
276+
277+
278+
@pytest.mark.parametrize("use_min_value", [True, False])
279+
@pytest.mark.parametrize("use_max_value", [True, False])
280+
def test_compute_max_children_unbounded_integer_ranges(use_min_value, use_max_value):
281+
@given(
282+
draw_integer_kwargs(
283+
use_min_value=use_min_value,
284+
use_max_value=use_max_value,
285+
use_weights=use_min_value and use_max_value,
286+
)
287+
)
288+
def f(kwargs):
289+
if kwargs["min_value"] is not None:
290+
expected = kwargs["min_value"]
291+
else:
292+
offset = (
293+
0
294+
if kwargs["max_value"] is None
295+
else min(kwargs["max_value"], kwargs["shrink_towards"])
296+
)
297+
expected = offset - (2**127) + 1
298+
299+
first = next(all_children("integer", kwargs))
300+
assert expected == first, (expected, first)
301+
302+
f()
282303

283304

284305
@given(st.randoms())

0 commit comments

Comments
 (0)