1
1
import functools
2
2
import math
3
+ import string
3
4
import sys
4
5
from typing import Any , List , Tuple , Type , Optional
5
6
@@ -34,11 +35,15 @@ def sample_attr(draw, name):
34
35
values = sample_attr ("values" )
35
36
scores = sample_attr ("scores" )
36
37
38
+ eng_text = st .builds (
39
+ lambda x : x .encode (), st .text (alphabet = string .ascii_letters , min_size = 1 )
40
+ )
37
41
ints = st .integers (min_value = MIN_INT , max_value = MAX_INT )
38
42
int_as_bytes = st .builds (lambda x : str (_default_normalize (x )).encode (), ints )
39
- float_as_bytes = st .builds (
40
- lambda x : repr ( _default_normalize ( x )). encode (), st . floats ( width = 32 )
43
+ floats = st .floats (
44
+ width = 32 , allow_nan = False , allow_subnormal = False , allow_infinity = False
41
45
)
46
+ float_as_bytes = st .builds (lambda x : repr (_default_normalize (x )).encode (), floats )
42
47
counts = st .integers (min_value = - 3 , max_value = 3 ) | ints
43
48
# Redis has an integer overflow bug in swapdb, so we confine the numbers to
44
49
# a limited range (https://github.com/antirez/redis/issues/5737).
@@ -51,8 +56,8 @@ def sample_attr(draw, name):
51
56
) | st .binary ().filter (lambda x : b"\0 " not in x )
52
57
53
58
# Redis has integer overflow bugs in time computations, which is why we set a maximum.
54
- expires_seconds = st .integers (min_value = 100000 , max_value = MAX_INT )
55
- expires_ms = st .integers (min_value = 100000000 , max_value = MAX_INT )
59
+ expires_seconds = st .integers (min_value = 5 , max_value = 1_000 )
60
+ expires_ms = st .integers (min_value = 5_000 , max_value = 50_000 )
56
61
57
62
58
63
class WrappedException :
@@ -98,6 +103,8 @@ def _sort_list(lst):
98
103
99
104
100
105
def _normalize_if_number (x ):
106
+ if isinstance (x , list ):
107
+ return [_normalize_if_number (i ) for i in x ]
101
108
try :
102
109
res = float (x )
103
110
return x if math .isnan (res ) else res
@@ -149,6 +156,7 @@ def normalize(self):
149
156
b"sinter" ,
150
157
b"sunion" ,
151
158
b"smembers" ,
159
+ b"hexpire" ,
152
160
}
153
161
if command in unordered :
154
162
return _sort_list
@@ -159,7 +167,7 @@ def normalize(self):
159
167
def testable (self ) -> bool :
160
168
"""Whether this command is suitable for a test.
161
169
162
- The fuzzer can create commands with behaviour that is non-deterministic, not supported, or which hits redis bugs.
170
+ The fuzzer can create commands with behavior that is non-deterministic, not supported, or which hits redis bugs.
163
171
"""
164
172
N = len (self .args )
165
173
if N == 0 :
@@ -201,20 +209,6 @@ def commands(*args, **kwargs):
201
209
| commands (st .just ("sort" ), keys , * zero_or_more ("asc" , "desc" , "alpha" ))
202
210
)
203
211
204
- attrs = st .fixed_dictionaries (
205
- {
206
- "keys" : st .lists (st .binary (), min_size = 2 , max_size = 5 , unique = True ),
207
- "fields" : st .lists (st .binary (), min_size = 2 , max_size = 5 , unique = True ),
208
- "values" : st .lists (
209
- st .binary () | int_as_bytes | float_as_bytes ,
210
- min_size = 2 ,
211
- max_size = 5 ,
212
- unique = True ,
213
- ),
214
- "scores" : st .lists (st .floats (width = 32 ), min_size = 2 , max_size = 5 , unique = True ),
215
- }
216
- )
217
-
218
212
219
213
@hypothesis .settings (max_examples = 1000 )
220
214
class CommonMachine (hypothesis .stateful .RuleBasedStateMachine ):
@@ -291,6 +285,16 @@ def _compare(self, command: Command) -> None:
291
285
for n , r , f in zip (self .transaction_normalize , real_result , fake_result ):
292
286
assert n (f ) == n (r )
293
287
self .transaction_normalize = []
288
+ elif isinstance (fake_result , list ):
289
+ assert len (fake_result ) == len (real_result ), (
290
+ f"Discrepancy when running command { command } , fake({ fake_result } ) != real({ real_result } )" ,
291
+ )
292
+ for i in range (len (fake_result )):
293
+ assert fake_result [i ] == real_result [i ] or (
294
+ type (fake_result [i ]) is float
295
+ and fake_result [i ] == pytest .approx (real_result [i ])
296
+ ), f"Discrepancy when running command { command } , fake({ fake_result } ) != real({ real_result } )"
297
+
294
298
else :
295
299
assert fake_result == real_result or (
296
300
type (fake_result ) is float and fake_result == pytest .approx (real_result )
@@ -307,7 +311,26 @@ def _compare(self, command: Command) -> None:
307
311
):
308
312
self .transaction_normalize = []
309
313
310
- @initialize (attrs = attrs )
314
+ @initialize (
315
+ attrs = st .fixed_dictionaries (
316
+ dict (
317
+ keys = st .lists (eng_text , min_size = 2 , max_size = 5 , unique = True ),
318
+ fields = st .lists (eng_text , min_size = 2 , max_size = 5 , unique = True ),
319
+ values = st .lists (
320
+ eng_text | int_as_bytes | float_as_bytes ,
321
+ min_size = 2 ,
322
+ max_size = 5 ,
323
+ unique = True ,
324
+ ),
325
+ scores = st .lists (
326
+ floats ,
327
+ min_size = 2 ,
328
+ max_size = 5 ,
329
+ unique = True ,
330
+ ),
331
+ )
332
+ )
333
+ )
311
334
def init_attrs (self , attrs ):
312
335
for key , value in attrs .items ():
313
336
setattr (self , key , value )
0 commit comments