Skip to content

Commit 693782e

Browse files
committed
add shortcircuit of all-zero for collection shrinker
1 parent 182dde2 commit 693782e

File tree

6 files changed

+35
-11
lines changed

6 files changed

+35
-11
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 improves shrinking involving long strings or byte sequences whose value is not relevant to the failure.

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

+2
Original file line numberDiff line numberDiff line change
@@ -1362,12 +1362,14 @@ def minimize_nodes(self, nodes):
13621362
Bytes.shrink(
13631363
value,
13641364
lambda val: self.try_shrinking_nodes(nodes, val),
1365+
min_size=kwargs["min_size"],
13651366
)
13661367
elif ir_type == "string":
13671368
String.shrink(
13681369
value,
13691370
lambda val: self.try_shrinking_nodes(nodes, val),
13701371
intervals=kwargs["intervals"],
1372+
min_size=kwargs["min_size"],
13711373
)
13721374
else:
13731375
raise NotImplementedError

hypothesis-python/src/hypothesis/internal/conjecture/shrinking/collection.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,23 @@
1414

1515

1616
class Collection(Shrinker):
17-
def setup(self, *, ElementShrinker, to_order=identity, from_order=identity):
17+
def setup(
18+
self, *, ElementShrinker, to_order=identity, from_order=identity, min_size
19+
):
1820
self.ElementShrinker = ElementShrinker
1921
self.to_order = to_order
2022
self.from_order = from_order
23+
self.min_size = min_size
2124

2225
def make_immutable(self, value):
2326
return tuple(value)
2427

28+
def short_circuit(self):
29+
zero = self.from_order(0)
30+
success = self.consider([zero] * len(self.current))
31+
# we could still simplify by deleting elements (unless we're minimal size).
32+
return success and len(self.current) == self.min_size
33+
2534
def left_is_better(self, left, right):
2635
if len(left) < len(right):
2736
return True

hypothesis-python/tests/conjecture/test_engine.py

-7
Original file line numberDiff line numberDiff line change
@@ -200,13 +200,6 @@ def nodes(data):
200200

201201

202202
def test_draw_to_overrun(monkeypatch):
203-
# TODO_BETTER_SHRINK: sometimes we can get unlucky and fail to shrink the
204-
# initial size draw d to 2 before shrinking the 128 * d bytes, but I'm not
205-
# sure why.
206-
#
207-
# If we do get unlucky in such a way then we need more than 500 shrinks to finish.
208-
monkeypatch.setattr(engine_module, "MAX_SHRINKS", 1000)
209-
210203
@run_to_nodes
211204
def nodes(data):
212205
d = (data.draw_bytes(1, 1)[0] - 8) & 0xFF

hypothesis-python/tests/conjecture/test_minimizer.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ def test_can_sort_bytes_by_reordering_partially_not_cross_stationary_element():
7272
],
7373
)
7474
def test_shrink_strings(initial, predicate, intervals, expected):
75-
assert String.shrink(initial, predicate, intervals=intervals) == tuple(expected)
75+
assert String.shrink(
76+
initial, predicate, intervals=intervals, min_size=len(expected)
77+
) == tuple(expected)
7678

7779

7880
@pytest.mark.parametrize(
@@ -85,9 +87,11 @@ def test_shrink_strings(initial, predicate, intervals, expected):
8587
],
8688
)
8789
def test_shrink_bytes(initial, predicate, expected):
88-
assert bytes(Bytes.shrink(initial, predicate)) == expected
90+
assert bytes(Bytes.shrink(initial, predicate, min_size=len(expected))) == expected
8991

9092

9193
def test_collection_left_is_better():
92-
shrinker = Collection([1, 2, 3], lambda v: True, ElementShrinker=Integer)
94+
shrinker = Collection(
95+
[1, 2, 3], lambda v: True, ElementShrinker=Integer, min_size=3
96+
)
9397
assert not shrinker.left_is_better([1, 2, 3], [1, 2, 3])

hypothesis-python/tests/conjecture/test_shrinker.py

+13
Original file line numberDiff line numberDiff line change
@@ -518,3 +518,16 @@ def shrinker(data: ConjectureData):
518518
# shrinking. Since the second draw is forced, this isn't possible to shrink
519519
# with just this pass.
520520
assert shrinker.choices == (15, 10)
521+
522+
523+
@pytest.mark.parametrize("n", [10, 50, 100, 200])
524+
def test_can_quickly_shrink_to_trivial_collection(n):
525+
@shrinking_from(ir(b"\x01" * n))
526+
def shrinker(data: ConjectureData):
527+
b = data.draw_bytes()
528+
if len(b) >= n:
529+
data.mark_interesting()
530+
531+
shrinker.fixate_shrink_passes(["minimize_individual_nodes"])
532+
assert shrinker.choices == (b"\x00" * n,)
533+
assert shrinker.calls < 10

0 commit comments

Comments
 (0)