15
15
import attr
16
16
17
17
from hypothesis .internal .compat import int_from_bytes , int_to_bytes
18
- from hypothesis .internal .conjecture .choice import choice_from_index
18
+ from hypothesis .internal .conjecture .choice import choice_from_index , choice_to_index
19
19
from hypothesis .internal .conjecture .data import (
20
20
ConjectureData ,
21
21
ConjectureResult ,
@@ -80,6 +80,13 @@ def sort_key(buffer: SortKeyT) -> tuple[int, SortKeyT]:
80
80
return (len (buffer ), buffer )
81
81
82
82
83
+ def sort_key_ir (nodes : Sequence [IRNode ]) -> tuple [int , tuple [int , ...]]:
84
+ return (
85
+ len (nodes ),
86
+ tuple (choice_to_index (node .value , node .kwargs ) for node in nodes ),
87
+ )
88
+
89
+
83
90
SHRINK_PASS_DEFINITIONS : dict [str , "ShrinkPassDefinition" ] = {}
84
91
85
92
@@ -305,7 +312,7 @@ def __init__(
305
312
self .__derived_values : dict = {}
306
313
self .__pending_shrink_explanation = None
307
314
308
- self .initial_size = len (initial .buffer )
315
+ self .initial_size = len (initial .choices )
309
316
310
317
# We keep track of the current best example on the shrink_target
311
318
# attribute.
@@ -401,7 +408,7 @@ def consider_new_tree(self, tree: Sequence[IRNode]) -> bool:
401
408
if startswith (tree , self .nodes ):
402
409
return True
403
410
404
- if startswith (self .nodes , tree ):
411
+ if sort_key_ir (self .nodes ) < sort_key_ir ( tree ):
405
412
return False
406
413
407
414
previous = self .shrink_target
@@ -445,7 +452,7 @@ def incorporate_test_data(self, data):
445
452
return
446
453
if (
447
454
self .__predicate (data )
448
- and sort_key (data .buffer ) < sort_key (self .shrink_target .buffer )
455
+ and sort_key_ir (data .ir_nodes ) < sort_key_ir (self .shrink_target .ir_nodes )
449
456
and self .__allow_transition (self .shrink_target , data )
450
457
):
451
458
self .update_shrink_target (data )
@@ -474,28 +481,6 @@ def shrink(self):
474
481
This method is "mostly idempotent" - calling it twice is unlikely to
475
482
have any effect, though it has a non-zero probability of doing so.
476
483
"""
477
- # We assume that if an all-zero block of bytes is an interesting
478
- # example then we're not going to do better than that.
479
- # This might not technically be true: e.g. for integers() | booleans()
480
- # the simplest example is actually [1, 0]. Missing this case is fairly
481
- # harmless and this allows us to make various simplifying assumptions
482
- # about the structure of the data (principally that we're never
483
- # operating on a block of all zero bytes so can use non-zeroness as a
484
- # signpost of complexity).
485
- if not any (self .shrink_target .buffer ) or self .incorporate_new_buffer (
486
- bytes (len (self .shrink_target .buffer ))
487
- ):
488
- self .explain ()
489
- return
490
-
491
- # There are multiple buffers that represent the same counterexample, eg
492
- # n=2 (from the 16 bit integer bucket) and n=2 (from the 32 bit integer
493
- # bucket). Before we start shrinking, we need to normalize to the minimal
494
- # such buffer, else a buffer-smaller but ir-larger value may be chosen
495
- # as the minimal counterexample.
496
- data = self .engine .new_conjecture_data_ir (self .nodes )
497
- self .engine .test_function (data )
498
- self .incorporate_test_data (data .as_result ())
499
484
500
485
try :
501
486
self .greedy_shrink ()
@@ -509,7 +494,7 @@ def shrink(self):
509
494
def s (n ):
510
495
return "s" if n != 1 else ""
511
496
512
- total_deleted = self .initial_size - len (self .shrink_target .buffer )
497
+ total_deleted = self .initial_size - len (self .shrink_target .choices )
513
498
calls = self .engine .call_count - self .initial_calls
514
499
misaligned = self .engine .misaligned_count - self .initial_misaligned
515
500
@@ -518,7 +503,7 @@ def s(n):
518
503
"Shrink pass profiling\n "
519
504
"---------------------\n \n "
520
505
f"Shrinking made a total of { calls } call{ s (calls )} of which "
521
- f"{ self .shrinks } shrank and { misaligned } were misaligned. This deleted { total_deleted } bytes out "
506
+ f"{ self .shrinks } shrank and { misaligned } were misaligned. This deleted { total_deleted } choices out "
522
507
f"of { self .initial_size } ."
523
508
)
524
509
for useful in [True , False ]:
@@ -540,7 +525,7 @@ def s(n):
540
525
self .debug (
541
526
f" * { p .name } made { p .calls } call{ s (p .calls )} of which "
542
527
f"{ p .shrinks } shrank and { p .misaligned } were misaligned, "
543
- f"deleting { p .deletions } byte { s (p .deletions )} ."
528
+ f"deleting { p .deletions } choice { s (p .deletions )} ."
544
529
)
545
530
self .debug ("" )
546
531
self .explain ()
@@ -797,7 +782,7 @@ def fixate_shrink_passes(self, passes):
797
782
# the length are the best.
798
783
if self .shrink_target is before_sp :
799
784
reordering [sp ] = 1
800
- elif len (self .buffer ) < len (before_sp .buffer ):
785
+ elif len (self .choices ) < len (before_sp .choices ):
801
786
reordering [sp ] = - 1
802
787
else :
803
788
reordering [sp ] = 0
@@ -988,7 +973,7 @@ def __changed_nodes(self):
988
973
assert prev_target is not new_target
989
974
prev_nodes = prev_target .ir_nodes
990
975
new_nodes = new_target .ir_nodes
991
- assert sort_key (new_target .buffer ) < sort_key (prev_target .buffer )
976
+ assert sort_key_ir (new_target .ir_nodes ) < sort_key_ir (prev_target .ir_nodes )
992
977
993
978
if len (prev_nodes ) != len (new_nodes ) or any (
994
979
n1 .ir_type != n2 .ir_type for n1 , n2 in zip (prev_nodes , new_nodes )
@@ -1186,11 +1171,11 @@ def remove_discarded(self):
1186
1171
1187
1172
for ex in self .shrink_target .examples :
1188
1173
if (
1189
- ex .length > 0
1174
+ ex .ir_length > 0
1190
1175
and ex .discarded
1191
- and (not discarded or ex .start >= discarded [- 1 ][- 1 ])
1176
+ and (not discarded or ex .ir_start >= discarded [- 1 ][- 1 ])
1192
1177
):
1193
- discarded .append ((ex .start , ex .end ))
1178
+ discarded .append ((ex .ir_start , ex .ir_end ))
1194
1179
1195
1180
# This can happen if we have discards but they are all of
1196
1181
# zero length. This shouldn't happen very often so it's
@@ -1199,11 +1184,11 @@ def remove_discarded(self):
1199
1184
if not discarded :
1200
1185
break
1201
1186
1202
- attempt = bytearray (self .shrink_target . buffer )
1187
+ attempt = list (self .nodes )
1203
1188
for u , v in reversed (discarded ):
1204
1189
del attempt [u :v ]
1205
1190
1206
- if not self .incorporate_new_buffer ( attempt ):
1191
+ if not self .consider_new_tree ( tuple ( attempt ) ):
1207
1192
return False
1208
1193
return True
1209
1194
@@ -1563,7 +1548,9 @@ def test_not_equal(x, y):
1563
1548
],
1564
1549
)
1565
1550
),
1566
- key = lambda i : st .buffer [examples [i ].start : examples [i ].end ],
1551
+ key = lambda i : sort_key_ir (
1552
+ st .ir_nodes [examples [i ].ir_start : examples [i ].ir_end ]
1553
+ ),
1567
1554
)
1568
1555
1569
1556
def run_node_program (self , i , description , original , repeats = 1 ):
@@ -1670,7 +1657,7 @@ def step(self, *, random_order=False):
1670
1657
initial_shrinks = self .shrinker .shrinks
1671
1658
initial_calls = self .shrinker .calls
1672
1659
initial_misaligned = self .shrinker .misaligned
1673
- size = len (self .shrinker .shrink_target .buffer )
1660
+ size = len (self .shrinker .shrink_target .choices )
1674
1661
self .shrinker .engine .explain_next_call_as (self .name )
1675
1662
1676
1663
if random_order :
@@ -1687,7 +1674,7 @@ def step(self, *, random_order=False):
1687
1674
self .calls += self .shrinker .calls - initial_calls
1688
1675
self .misaligned += self .shrinker .misaligned - initial_misaligned
1689
1676
self .shrinks += self .shrinker .shrinks - initial_shrinks
1690
- self .deletions += size - len (self .shrinker .shrink_target .buffer )
1677
+ self .deletions += size - len (self .shrinker .shrink_target .choices )
1691
1678
self .shrinker .engine .clear_call_explanation ()
1692
1679
return True
1693
1680
0 commit comments