34
34
35
35
from hypothesis import HealthCheck , Phase , Verbosity , settings as Settings
36
36
from hypothesis ._settings import local_settings
37
- from hypothesis .database import ExampleDatabase
37
+ from hypothesis .database import ExampleDatabase , ir_from_bytes , ir_to_bytes
38
38
from hypothesis .errors import (
39
39
BackendCannotProceed ,
40
40
FlakyReplay ,
44
44
)
45
45
from hypothesis .internal .cache import LRUReusedCache
46
46
from hypothesis .internal .compat import NotRequired , TypeAlias , TypedDict , ceil , override
47
- from hypothesis .internal .conjecture .choice import ChoiceKwargsT , ChoiceT , choices_key
47
+ from hypothesis .internal .conjecture .choice import (
48
+ ChoiceKeyT ,
49
+ ChoiceKwargsT ,
50
+ ChoiceT ,
51
+ choices_key ,
52
+ )
48
53
from hypothesis .internal .conjecture .data import (
49
54
AVAILABLE_PROVIDERS ,
50
55
ConjectureData ,
69
74
startswith ,
70
75
)
71
76
from hypothesis .internal .conjecture .pareto import NO_SCORE , ParetoFront , ParetoOptimiser
72
- from hypothesis .internal .conjecture .shrinker import Shrinker , sort_key , sort_key_ir
77
+ from hypothesis .internal .conjecture .shrinker import Shrinker , sort_key_ir
73
78
from hypothesis .internal .escalation import InterestingOrigin
74
79
from hypothesis .internal .healthcheck import fail_health_check
75
80
from hypothesis .reporting import base_report , report
91
96
Ls : TypeAlias = list ["Ls | int" ]
92
97
93
98
99
+ def shortlex (s ):
100
+ return (len (s ), s )
101
+
102
+
94
103
@attr .s
95
104
class HealthCheckState :
96
105
valid_examples : int = attr .ib (default = 0 )
@@ -467,7 +476,7 @@ def test_function(self, data: ConjectureData) -> None:
467
476
data .freeze ()
468
477
return
469
478
except BaseException :
470
- self .save_buffer (data .buffer )
479
+ self .save_choices (data .choices )
471
480
raise
472
481
finally :
473
482
# No branch, because if we're interrupted we always raise
@@ -522,7 +531,7 @@ def test_function(self, data: ConjectureData) -> None:
522
531
and self .pareto_front is not None
523
532
and self .pareto_front .add (data .as_result ())
524
533
):
525
- self .save_buffer (data .buffer , sub_key = b"pareto" )
534
+ self .save_choices (data .choices , sub_key = b"pareto" )
526
535
527
536
assert len (data .buffer ) <= BUFFER_SIZE
528
537
@@ -601,12 +610,12 @@ def test_function(self, data: ConjectureData) -> None:
601
610
else :
602
611
if sort_key_ir (data .ir_nodes ) < sort_key_ir (existing .ir_nodes ):
603
612
self .shrinks += 1
604
- self .downgrade_buffer (existing .buffer )
613
+ self .downgrade_buffer (ir_to_bytes ( existing .choices ) )
605
614
self .__data_cache .unpin (existing .buffer )
606
615
changed = True
607
616
608
617
if changed :
609
- self .save_buffer (data .buffer )
618
+ self .save_choices (data .choices )
610
619
self .interesting_examples [key ] = data .as_result () # type: ignore
611
620
self .__data_cache .pin (data .buffer , data .as_result ())
612
621
self .shrunk_examples .discard (key )
@@ -651,7 +660,7 @@ def test_function(self, data: ConjectureData) -> None:
651
660
self .record_for_health_check (data )
652
661
653
662
def on_pareto_evict (self , data : ConjectureData ) -> None :
654
- self .settings .database .delete (self .pareto_key , data .buffer )
663
+ self .settings .database .delete (self .pareto_key , ir_to_bytes ( data .choices ) )
655
664
656
665
def generate_novel_prefix (self ) -> tuple [ChoiceT , ...]:
657
666
"""Uses the tree to proactively generate a starting sequence of bytes
@@ -735,14 +744,14 @@ def record_for_health_check(self, data: ConjectureData) -> None:
735
744
HealthCheck .too_slow ,
736
745
)
737
746
738
- def save_buffer (
739
- self , buffer : Union [ bytes , bytearray ], sub_key : Optional [bytes ] = None
747
+ def save_choices (
748
+ self , choices : Sequence [ ChoiceT ], sub_key : Optional [bytes ] = None
740
749
) -> None :
741
750
if self .settings .database is not None :
742
751
key = self .sub_key (sub_key )
743
752
if key is None :
744
753
return
745
- self .settings .database .save (key , bytes ( buffer ))
754
+ self .settings .database .save (key , ir_to_bytes ( choices ))
746
755
747
756
def downgrade_buffer (self , buffer : Union [bytes , bytearray ]) -> None :
748
757
if self .settings .database is not None and self .database_key is not None :
@@ -832,7 +841,7 @@ def reuse_existing_examples(self) -> None:
832
841
# sample the secondary corpus to a more manageable size.
833
842
834
843
corpus = sorted (
835
- self .settings .database .fetch (self .database_key ), key = sort_key
844
+ self .settings .database .fetch (self .database_key ), key = shortlex
836
845
)
837
846
factor = 0.1 if (Phase .generate in self .settings .phases ) else 1
838
847
desired_size = max (2 , ceil (factor * self .settings .max_examples ))
@@ -847,7 +856,7 @@ def reuse_existing_examples(self) -> None:
847
856
extra = extra_corpus
848
857
else :
849
858
extra = self .random .sample (extra_corpus , shortfall )
850
- extra .sort (key = sort_key )
859
+ extra .sort (key = shortlex )
851
860
corpus .extend (extra )
852
861
853
862
# We want a fast path where every primary entry in the database was
@@ -858,15 +867,20 @@ def reuse_existing_examples(self) -> None:
858
867
for i , existing in enumerate (corpus ):
859
868
if i >= primary_corpus_size and found_interesting_in_primary :
860
869
break
861
- data = self .cached_test_function (existing , extend = BUFFER_SIZE )
870
+ choices = ir_from_bytes (existing )
871
+ if choices is None :
872
+ # clear out any keys which fail deserialization
873
+ self .settings .database .delete (self .database_key , existing )
874
+ continue
875
+ data = self .cached_test_function_ir (choices , extend = BUFFER_SIZE )
862
876
if data .status != Status .INTERESTING :
863
877
self .settings .database .delete (self .database_key , existing )
864
878
self .settings .database .delete (self .secondary_key , existing )
865
879
else :
866
880
if i < primary_corpus_size :
867
881
found_interesting_in_primary = True
868
882
assert not isinstance (data , _Overrun )
869
- if existing != data .buffer :
883
+ if choices_key ( choices ) != choices_key ( data .choices ) :
870
884
all_interesting_in_primary_were_exact = False
871
885
if not self .settings .report_multiple_bugs :
872
886
break
@@ -886,10 +900,14 @@ def reuse_existing_examples(self) -> None:
886
900
pareto_corpus = list (self .settings .database .fetch (self .pareto_key ))
887
901
if len (pareto_corpus ) > desired_extra :
888
902
pareto_corpus = self .random .sample (pareto_corpus , desired_extra )
889
- pareto_corpus .sort (key = sort_key )
903
+ pareto_corpus .sort (key = shortlex )
890
904
891
905
for existing in pareto_corpus :
892
- data = self .cached_test_function (existing , extend = BUFFER_SIZE )
906
+ choices = ir_from_bytes (existing )
907
+ if choices is None :
908
+ self .settings .database .delete (self .pareto_key , existing )
909
+ continue
910
+ data = self .cached_test_function_ir (choices , extend = BUFFER_SIZE )
893
911
if data not in self .pareto_front :
894
912
self .settings .database .delete (self .pareto_key , existing )
895
913
if data .status == Status .INTERESTING :
@@ -1371,9 +1389,9 @@ def shrink_interesting_examples(self) -> None:
1371
1389
for k , v in self .interesting_examples .items ()
1372
1390
if k not in self .shrunk_examples
1373
1391
),
1374
- key = lambda kv : (sort_key_ir (kv [1 ].ir_nodes ), sort_key (repr (kv [0 ]))),
1392
+ key = lambda kv : (sort_key_ir (kv [1 ].ir_nodes ), shortlex (repr (kv [0 ]))),
1375
1393
)
1376
- self .debug (f"Shrinking { target !r} : { data .choices } " )
1394
+ self .debug (f"Shrinking { target !r} : { example .choices } " )
1377
1395
1378
1396
if not self .settings .report_multiple_bugs :
1379
1397
# If multi-bug reporting is disabled, we shrink our currently-minimal
@@ -1400,17 +1418,22 @@ def clear_secondary_key(self) -> None:
1400
1418
# It's not worth trying the primary corpus because we already
1401
1419
# tried all of those in the initial phase.
1402
1420
corpus = sorted (
1403
- self .settings .database .fetch (self .secondary_key ), key = sort_key
1421
+ self .settings .database .fetch (self .secondary_key ), key = shortlex
1404
1422
)
1405
1423
for c in corpus :
1406
- primary = {v .buffer for v in self .interesting_examples .values ()}
1407
-
1408
- cap = max (map (sort_key , primary ))
1424
+ choices = ir_from_bytes (c )
1425
+ if choices is None :
1426
+ self .settings .database .delete (self .secondary_key , c )
1427
+ continue
1428
+ primary = {
1429
+ ir_to_bytes (v .choices ) for v in self .interesting_examples .values ()
1430
+ }
1431
+ cap = max (map (shortlex , primary ))
1409
1432
1410
- if sort_key (c ) > cap :
1433
+ if shortlex (c ) > cap :
1411
1434
break
1412
1435
else :
1413
- self .cached_test_function ( c )
1436
+ self .cached_test_function_ir ( choices )
1414
1437
# We unconditionally remove c from the secondary key as it
1415
1438
# is either now primary or worse than our primary example
1416
1439
# of this reason for interestingness.
0 commit comments