diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst new file mode 100644 index 0000000000..dea9b4765b --- /dev/null +++ b/hypothesis-python/RELEASE.rst @@ -0,0 +1,6 @@ +RELEASE_TYPE: patch + +This patch substantially improves our ability to avoid generating redundant +inputs when choosing between a non-power-of-two number of alternatives. +In certain circumstances, this was causing serious performance problems - +see :issue:`1864`, :issue:`1982`, and :issue:`2027`. diff --git a/hypothesis-python/src/hypothesis/extra/numpy.py b/hypothesis-python/src/hypothesis/extra/numpy.py index 583d924f89..a00e459519 100644 --- a/hypothesis-python/src/hypothesis/extra/numpy.py +++ b/hypothesis-python/src/hypothesis/extra/numpy.py @@ -709,8 +709,8 @@ def valid_tuple_axes(ndim, min_size=0, max_size=None): check_valid_interval(max_size, ndim, "max_size", "ndim") # shrink axis values from negative to positive - axes = st.integers(0, max(0, 2 * ndim - 1)).map( - lambda x: x if x < ndim else x - 2 * ndim + axes = st.tuples(st.booleans(), st.integers(0, ndim - 1)).map( + lambda args: args[1] - ndim if args[0] else args[1] ) return st.lists(axes, min_size, max_size, unique_by=lambda x: x % ndim).map(tuple) diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/utils.py b/hypothesis-python/src/hypothesis/internal/conjecture/utils.py index 4180e02b32..6fa54ffb16 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/utils.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/utils.py @@ -90,7 +90,6 @@ def integer_range(data, lower, upper, center=None): assert gap > 0 bits = bit_length(gap) - probe = gap + 1 if bits > 24 and data.draw_bits(3): # For large ranges, we combine the uniform random distribution from draw_bits @@ -100,10 +99,17 @@ def integer_range(data, lower, upper, center=None): sizes = [8, 16, 32, 64, 128] bits = min(bits, sizes[idx]) - while probe > gap: - data.start_example(INTEGER_RANGE_DRAW_LABEL) - probe = data.draw_bits(bits) - data.stop_example(discard=probe > gap) + data.start_example(INTEGER_RANGE_DRAW_LABEL) + probe = data.draw_bits(bits) + data.stop_example() + # Rejection sampling interacts badly with our prefix tree (see e.g. #1864), + # so we want to adjust this value to fit in the range *without* distorting + # the distribution. + high = (1 << bits) - 1 + if high > gap: + leave = gap - (high - gap) + if probe > leave: + probe = leave + (probe - leave) // 2 if above: result = center + probe diff --git a/hypothesis-python/tests/cover/test_conjecture_engine.py b/hypothesis-python/tests/cover/test_conjecture_engine.py index e345a2bd56..f758175881 100644 --- a/hypothesis-python/tests/cover/test_conjecture_engine.py +++ b/hypothesis-python/tests/cover/test_conjecture_engine.py @@ -43,7 +43,7 @@ ) from hypothesis.internal.conjecture.shrinker import Shrinker, block_program from hypothesis.internal.conjecture.shrinking import Float -from hypothesis.internal.conjecture.utils import Sampler, calc_label_from_name +from hypothesis.internal.conjecture.utils import calc_label_from_name from hypothesis.internal.entropy import deterministic_PRNG from tests.common.strategies import SLOW, HardToShrink from tests.common.utils import counts_calls, no_shrink @@ -1111,27 +1111,6 @@ def accept(f): return accept -def test_dependent_block_pairs_is_up_to_shrinking_integers(): - # Unit test extracted from a failure in tests/nocover/test_integers.py - distribution = Sampler([4.0, 8.0, 1.0, 1.0, 0.5]) - - sizes = [8, 16, 32, 64, 128] - - @shrinking_from(b"\x03\x01\x00\x00\x00\x00\x00\x01\x00\x02\x01") - def shrinker(data): - size = sizes[distribution.sample(data)] - result = data.draw_bits(size) - sign = (-1) ** (result & 1) - result = (result >> 1) * sign - cap = data.draw_bits(8) - - if result >= 32768 and cap == 1: - data.mark_interesting() - - shrinker.minimize_individual_blocks() - assert list(shrinker.shrink_target.buffer) == [1, 1, 0, 1, 0, 0, 1] - - def test_finding_a_minimal_balanced_binary_tree(): # Tests iteration while the shape of the thing being iterated over can # change. In particular the current example can go from trivial to non diff --git a/hypothesis-python/tests/cover/test_simple_characters.py b/hypothesis-python/tests/cover/test_simple_characters.py index 360cc67ddc..be3cccb04b 100644 --- a/hypothesis-python/tests/cover/test_simple_characters.py +++ b/hypothesis-python/tests/cover/test_simple_characters.py @@ -130,7 +130,6 @@ def test_whitelisted_characters_override(): assert_no_examples(st, lambda c: c not in good_characters + "0123456789") -@pytest.mark.skip # temporary skip due to 560 second (!) perf regression; see #1864 def test_blacklisted_characters(): bad_chars = u"te02ั‚ะตัั‚49st" st = characters( diff --git a/hypothesis-python/tests/nocover/test_sampled_from.py b/hypothesis-python/tests/nocover/test_sampled_from.py index ab6a8c0413..3b37c3c1af 100644 --- a/hypothesis-python/tests/nocover/test_sampled_from.py +++ b/hypothesis-python/tests/nocover/test_sampled_from.py @@ -73,3 +73,14 @@ def test_unsat_sets_of_samples(x): @given(st.sets(st.sampled_from(range(50)), min_size=50)) def test_efficient_sets_of_samples(x): assert x == set(range(50)) + + +def test_stops_quickly(): + # https://github.com/HypothesisWorks/hypothesis/issues/2027 + @given(st.sampled_from(range(3))) + def inner(x): + count[0] += 1 + + count = [0] + inner() + assert 2 < count[0] <= 4 diff --git a/hypothesis-python/tests/pandas/test_data_frame.py b/hypothesis-python/tests/pandas/test_data_frame.py index 2376740732..1fef24db9a 100644 --- a/hypothesis-python/tests/pandas/test_data_frame.py +++ b/hypothesis-python/tests/pandas/test_data_frame.py @@ -211,7 +211,7 @@ def test_uniqueness_does_not_affect_other_rows_1(): pdst.column("A", dtype=int, unique=True), pdst.column("B", dtype=int, unique=False), ], - rows=st.tuples(st.integers(0, 10), st.integers(0, 10)), + rows=st.tuples(st.integers(0, 3), st.integers(0, 3)), index=pdst.range_indexes(2, 2), ) find_any(data_frames, lambda x: x["B"][0] == x["B"][1]) diff --git a/hypothesis-python/tests/quality/test_integers.py b/hypothesis-python/tests/quality/test_integers.py index 9faa3819b0..27cef37d18 100644 --- a/hypothesis-python/tests/quality/test_integers.py +++ b/hypothesis-python/tests/quality/test_integers.py @@ -20,16 +20,7 @@ from random import Random import hypothesis.strategies as st -from hypothesis import ( - HealthCheck, - Phase, - Verbosity, - assume, - example, - given, - reject, - settings, -) +from hypothesis import HealthCheck, Phase, Verbosity, assume, given, reject, settings from hypothesis.internal.compat import hbytes from hypothesis.internal.conjecture.data import ConjectureData, Status, StopTest from hypothesis.internal.conjecture.engine import ConjectureRunner @@ -52,9 +43,6 @@ def problems(draw): pass -@example((2, b"\x00\x00\n\x01")) -@example((1, b"\x00\x00\x06\x01")) -@example(problem=(32768, b"\x03\x01\x00\x00\x00\x00\x00\x01\x00\x02\x01")) @settings( suppress_health_check=HealthCheck.all(), deadline=None,