@@ -127,8 +127,8 @@ class BooleanKWargs(TypedDict):
127
127
IntegerKWargs , FloatKWargs , StringKWargs , BytesKWargs , BooleanKWargs
128
128
]
129
129
IRTypeName : TypeAlias = Literal ["integer" , "string" , "boolean" , "float" , "bytes" ]
130
- # ir_type, kwargs, forced
131
- InvalidAt : TypeAlias = Tuple [IRTypeName , IRKWargsType , Optional [IRType ]]
130
+ # index, ir_type, kwargs, forced
131
+ MisalignedAt : TypeAlias = Tuple [int , IRTypeName , IRKWargsType , Optional [IRType ]]
132
132
133
133
134
134
class ExtraInformation :
@@ -954,9 +954,6 @@ def draw_boolean(
954
954
) -> None :
955
955
pass
956
956
957
- def mark_invalid (self , invalid_at : InvalidAt ) -> None :
958
- pass
959
-
960
957
961
958
@attr .s (slots = True , repr = False , eq = False )
962
959
class IRNode :
@@ -1169,7 +1166,7 @@ class ConjectureResult:
1169
1166
examples : Examples = attr .ib (repr = False , eq = False )
1170
1167
arg_slices : Set [Tuple [int , int ]] = attr .ib (repr = False )
1171
1168
slice_comments : Dict [Tuple [int , int ], str ] = attr .ib (repr = False )
1172
- invalid_at : Optional [InvalidAt ] = attr .ib (repr = False )
1169
+ misaligned_at : Optional [MisalignedAt ] = attr .ib (repr = False )
1173
1170
1174
1171
index : int = attr .ib (init = False )
1175
1172
@@ -2060,7 +2057,7 @@ def __init__(
2060
2057
self .extra_information = ExtraInformation ()
2061
2058
2062
2059
self .ir_tree_nodes = ir_tree_prefix
2063
- self .invalid_at : Optional [InvalidAt ] = None
2060
+ self .misaligned_at : Optional [MisalignedAt ] = None
2064
2061
self ._node_index = 0
2065
2062
self .start_example (TOP_LABEL )
2066
2063
@@ -2144,10 +2141,10 @@ def draw_integer(
2144
2141
)
2145
2142
2146
2143
if self .ir_tree_nodes is not None and observe :
2147
- node = self ._pop_ir_tree_node ("integer" , kwargs , forced = forced )
2144
+ node_value = self ._pop_ir_tree_node ("integer" , kwargs , forced = forced )
2148
2145
if forced is None :
2149
- assert isinstance (node . value , int )
2150
- forced = node . value
2146
+ assert isinstance (node_value , int )
2147
+ forced = node_value
2151
2148
fake_forced = True
2152
2149
2153
2150
value = self .provider .draw_integer (
@@ -2201,10 +2198,10 @@ def draw_float(
2201
2198
)
2202
2199
2203
2200
if self .ir_tree_nodes is not None and observe :
2204
- node = self ._pop_ir_tree_node ("float" , kwargs , forced = forced )
2201
+ node_value = self ._pop_ir_tree_node ("float" , kwargs , forced = forced )
2205
2202
if forced is None :
2206
- assert isinstance (node . value , float )
2207
- forced = node . value
2203
+ assert isinstance (node_value , float )
2204
+ forced = node_value
2208
2205
fake_forced = True
2209
2206
2210
2207
value = self .provider .draw_float (
@@ -2243,10 +2240,10 @@ def draw_string(
2243
2240
},
2244
2241
)
2245
2242
if self .ir_tree_nodes is not None and observe :
2246
- node = self ._pop_ir_tree_node ("string" , kwargs , forced = forced )
2243
+ node_value = self ._pop_ir_tree_node ("string" , kwargs , forced = forced )
2247
2244
if forced is None :
2248
- assert isinstance (node . value , str )
2249
- forced = node . value
2245
+ assert isinstance (node_value , str )
2246
+ forced = node_value
2250
2247
fake_forced = True
2251
2248
2252
2249
value = self .provider .draw_string (
@@ -2279,10 +2276,10 @@ def draw_bytes(
2279
2276
kwargs : BytesKWargs = self ._pooled_kwargs ("bytes" , {"size" : size })
2280
2277
2281
2278
if self .ir_tree_nodes is not None and observe :
2282
- node = self ._pop_ir_tree_node ("bytes" , kwargs , forced = forced )
2279
+ node_value = self ._pop_ir_tree_node ("bytes" , kwargs , forced = forced )
2283
2280
if forced is None :
2284
- assert isinstance (node . value , bytes )
2285
- forced = node . value
2281
+ assert isinstance (node_value , bytes )
2282
+ forced = node_value
2286
2283
fake_forced = True
2287
2284
2288
2285
value = self .provider .draw_bytes (
@@ -2320,10 +2317,10 @@ def draw_boolean(
2320
2317
kwargs : BooleanKWargs = self ._pooled_kwargs ("boolean" , {"p" : p })
2321
2318
2322
2319
if self .ir_tree_nodes is not None and observe :
2323
- node = self ._pop_ir_tree_node ("boolean" , kwargs , forced = forced )
2320
+ node_value = self ._pop_ir_tree_node ("boolean" , kwargs , forced = forced )
2324
2321
if forced is None :
2325
- assert isinstance (node . value , bool )
2326
- forced = node . value
2322
+ assert isinstance (node_value , bool )
2323
+ forced = node_value
2327
2324
fake_forced = True
2328
2325
2329
2326
value = self .provider .draw_boolean (
@@ -2367,41 +2364,57 @@ def _pooled_kwargs(self, ir_type, kwargs):
2367
2364
2368
2365
def _pop_ir_tree_node (
2369
2366
self , ir_type : IRTypeName , kwargs : IRKWargsType , * , forced : Optional [IRType ]
2370
- ) -> IRNode :
2367
+ ) -> IRType :
2368
+ from hypothesis .internal .conjecture .engine import BUFFER_SIZE
2369
+
2371
2370
assert self .ir_tree_nodes is not None
2372
2371
2373
2372
if self ._node_index == len (self .ir_tree_nodes ):
2374
2373
self .mark_overrun ()
2375
2374
2376
2375
node = self .ir_tree_nodes [self ._node_index ]
2377
- # If we're trying to draw a different ir type at the same location, then
2378
- # this ir tree has become badly misaligned. We don't have many good/simple
2379
- # options here for realigning beyond giving up.
2376
+ value = node .value
2377
+ # If we're trying to:
2378
+ # * draw a different ir type at the same location
2379
+ # * draw the same ir type with a different kwargs
2380
+ #
2381
+ # then we call this a misalignment, because the choice sequence has
2382
+ # slipped from what we expected at some point. An easy misalignment is
2383
+ #
2384
+ # st.one_of(st.integers(0, 100), st.integers(101, 200))
2380
2385
#
2381
- # This is more of an issue for ir nodes while shrinking than it was for
2382
- # buffers: misaligned buffers are still usually valid, just interpreted
2383
- # differently. This would be somewhat like drawing a random value for
2384
- # the new ir type here. For what it's worth, misaligned buffers are
2385
- # rather unlikely to be *useful* buffers, so giving up isn't a big downgrade.
2386
- # (in fact, it is possible that giving up early here results in more time
2387
- # for useful shrinks to run).
2388
- if node .ir_type != ir_type :
2389
- invalid_at = (ir_type , kwargs , forced )
2390
- self .invalid_at = invalid_at
2391
- self .observer .mark_invalid (invalid_at )
2392
- self .mark_invalid (f"(internal) want a { ir_type } but have a { node .ir_type } " )
2393
-
2394
- # if a node has different kwargs (and so is misaligned), but has a value
2395
- # that is allowed by the expected kwargs, then we can coerce this node
2396
- # into an aligned one by using its value. It's unclear how useful this is.
2397
- if not ir_value_permitted (node .value , node .ir_type , kwargs ):
2398
- invalid_at = (ir_type , kwargs , forced )
2399
- self .invalid_at = invalid_at
2400
- self .observer .mark_invalid (invalid_at )
2401
- self .mark_invalid (f"(internal) got a { ir_type } but outside the valid range" )
2386
+ # where the choice sequence [0, 100] has kwargs {min_value: 0, max_value: 100}
2387
+ # at position 2, but [0, 101] has kwargs {min_value: 101, max_value: 200} at
2388
+ # position 2.
2389
+ #
2390
+ # When we see a misalignment, we can't offer up the stored node value as-is.
2391
+ # We need to make it appropriate for the requested kwargs and ir type.
2392
+ # Right now we do that by using bytes as the intermediary to convert between
2393
+ # ir types/kwargs. In the future we'll probably use the index into a custom
2394
+ # ordering for an (ir_type, kwargs) pair.
2395
+ if node .ir_type != ir_type or not ir_value_permitted (
2396
+ node .value , node .ir_type , kwargs
2397
+ ):
2398
+ # only track first misalignment for now.
2399
+ if self .misaligned_at is None :
2400
+ self .misaligned_at = (self ._node_index , ir_type , kwargs , forced )
2401
+ (_value , buffer ) = ir_to_buffer (
2402
+ node .ir_type , node .kwargs , forced = node .value
2403
+ )
2404
+ try :
2405
+ value = buffer_to_ir (
2406
+ ir_type , kwargs , buffer = buffer + bytes (BUFFER_SIZE - len (buffer ))
2407
+ )
2408
+ except StopTest :
2409
+ # must have been an overrun.
2410
+ #
2411
+ # maybe we should fall back to to an arbitrary small value here
2412
+ # instead? eg
2413
+ # buffer_to_ir(ir_type, kwargs, buffer=bytes(BUFFER_SIZE))
2414
+ self .mark_overrun ()
2402
2415
2403
2416
self ._node_index += 1
2404
- return node
2417
+ return value
2405
2418
2406
2419
def as_result (self ) -> Union [ConjectureResult , _Overrun ]:
2407
2420
"""Convert the result of running this test into
@@ -2429,7 +2442,7 @@ def as_result(self) -> Union[ConjectureResult, _Overrun]:
2429
2442
forced_indices = frozenset (self .forced_indices ),
2430
2443
arg_slices = self .arg_slices ,
2431
2444
slice_comments = self .slice_comments ,
2432
- invalid_at = self .invalid_at ,
2445
+ misaligned_at = self .misaligned_at ,
2433
2446
)
2434
2447
assert self .__result is not None
2435
2448
self .blocks .transfer_ownership (self .__result )
@@ -2578,38 +2591,9 @@ def freeze(self) -> None:
2578
2591
self .stop_example ()
2579
2592
2580
2593
self .__example_record .freeze ()
2581
-
2582
2594
self .frozen = True
2583
-
2584
2595
self .buffer = bytes (self .buffer )
2585
-
2586
- # if we were invalid because of a misalignment in the tree, we don't
2587
- # want to tell the DataTree that. Doing so would lead to inconsistent behavior.
2588
- # Given an empty DataTree
2589
- # ┌──────┐
2590
- # │ root │
2591
- # └──────┘
2592
- # and supposing the very first draw is misaligned, concluding here would
2593
- # tell the datatree that the *only* possibility at the root node is Status.INVALID:
2594
- # ┌──────┐
2595
- # │ root │
2596
- # └──┬───┘
2597
- # ┌───────────┴───────────────┐
2598
- # │ Conclusion(Status.INVALID)│
2599
- # └───────────────────────────┘
2600
- # when in fact this is only the case when we try to draw a misaligned node.
2601
- # For instance, suppose we come along in the second test case and try a
2602
- # valid node as the first draw from the root. The DataTree thinks this
2603
- # is flaky (because root must lead to Status.INVALID in the tree) while
2604
- # in fact nothing in the test function has changed and the only change
2605
- # is in the ir tree prefix we are supplying.
2606
- #
2607
- # From the perspective of DataTree, it is safe to not conclude here. This
2608
- # tells the datatree that we don't know what happens after this node - which
2609
- # is true! We are aborting early here because the ir tree became misaligned,
2610
- # which is a semantically different invalidity than an assume or filter failing.
2611
- if self .invalid_at is None :
2612
- self .observer .conclude_test (self .status , self .interesting_origin )
2596
+ self .observer .conclude_test (self .status , self .interesting_origin )
2613
2597
2614
2598
def choice (
2615
2599
self ,
@@ -2716,3 +2700,24 @@ def bits_to_bytes(n: int) -> int:
2716
2700
Equivalent to (n + 7) // 8, but slightly faster. This really is
2717
2701
called enough times that that matters."""
2718
2702
return (n + 7 ) >> 3
2703
+
2704
+
2705
+ def ir_to_buffer (ir_type , kwargs , * , forced = None , random = None ):
2706
+ from hypothesis .internal .conjecture .engine import BUFFER_SIZE
2707
+
2708
+ if forced is None :
2709
+ assert random is not None
2710
+
2711
+ cd = ConjectureData (
2712
+ max_length = BUFFER_SIZE ,
2713
+ # buffer doesn't matter if forced is passed since we're forcing the sole draw
2714
+ prefix = b"" if forced is None else bytes (BUFFER_SIZE ),
2715
+ random = random ,
2716
+ )
2717
+ value = getattr (cd .provider , f"draw_{ ir_type } " )(** kwargs , forced = forced )
2718
+ return (value , cd .buffer )
2719
+
2720
+
2721
+ def buffer_to_ir (ir_type , kwargs , * , buffer ):
2722
+ cd = ConjectureData .for_buffer (buffer )
2723
+ return getattr (cd .provider , f"draw_{ ir_type } " )(** kwargs )
0 commit comments