1# This file is part of Hypothesis, which may be found at 2# https://github.com/HypothesisWorks/hypothesis/ 3# 4# Most of this work is copyright (C) 2013-2021 David R. MacIver 5# (david@drmaciver.com), but it contains contributions by others. See 6# CONTRIBUTING.rst for a full list of people who may hold copyright, and 7# consult the git log if you need to determine who owns an individual 8# contribution. 9# 10# This Source Code Form is subject to the terms of the Mozilla Public License, 11# v. 2.0. If a copy of the MPL was not distributed with this file, You can 12# obtain one at https://mozilla.org/MPL/2.0/. 13# 14# END HEADER 15 16import inspect 17import math 18from random import Random 19from typing import Dict 20 21import attr 22 23from hypothesis.control import should_note 24from hypothesis.internal.conjecture import utils as cu 25from hypothesis.internal.reflection import define_function_signature 26from hypothesis.reporting import report 27from hypothesis.strategies._internal.core import ( 28 binary, 29 lists, 30 permutations, 31 sampled_from, 32) 33from hypothesis.strategies._internal.numbers import floats, integers 34from hypothesis.strategies._internal.strategies import SearchStrategy 35 36 37class HypothesisRandom(Random): 38 """A subclass of Random designed to expose the seed it was initially 39 provided with.""" 40 41 def __init__(self, note_method_calls): 42 self.__note_method_calls = note_method_calls 43 44 def __deepcopy__(self, table): 45 return self.__copy__() 46 47 def __repr__(self): 48 raise NotImplementedError() 49 50 def seed(self, seed): 51 raise NotImplementedError() 52 53 def getstate(self): 54 raise NotImplementedError() 55 56 def setstate(self, state): 57 raise NotImplementedError() 58 59 def _hypothesis_log_random(self, method, kwargs, result): 60 if not (self.__note_method_calls and should_note()): 61 return 62 63 args, kwargs = convert_kwargs(method, kwargs) 64 argstr = ", ".join( 65 list(map(repr, args)) + [f"{k}={v!r}" for k, v in kwargs.items()] 66 ) 67 report(f"{self!r}.{method}({argstr}) -> {result!r}") 68 69 def _hypothesis_do_random(self, method, kwargs): 70 raise NotImplementedError() 71 72 73RANDOM_METHODS = [ 74 name 75 for name in [ 76 "_randbelow", 77 "betavariate", 78 "choice", 79 "choices", 80 "expovariate", 81 "gammavariate", 82 "gauss", 83 "getrandbits", 84 "lognormvariate", 85 "normalvariate", 86 "paretovariate", 87 "randint", 88 "random", 89 "randrange", 90 "sample", 91 "shuffle", 92 "triangular", 93 "uniform", 94 "vonmisesvariate", 95 "weibullvariate", 96 "randbytes", 97 ] 98 if hasattr(Random, name) 99] 100 101 102# Fake shims to get a good signature 103def getrandbits(self, n: int) -> int: # type: ignore 104 raise NotImplementedError() 105 106 107def random(self) -> float: # type: ignore 108 raise NotImplementedError() 109 110 111def _randbelow(self, n: int) -> int: # type: ignore 112 raise NotImplementedError() 113 114 115STUBS = {f.__name__: f for f in [getrandbits, random, _randbelow]} 116 117 118SIGNATURES: Dict[str, inspect.Signature] = {} 119 120 121def sig_of(name): 122 try: 123 return SIGNATURES[name] 124 except KeyError: 125 pass 126 127 target = getattr(Random, name) 128 result = inspect.signature(STUBS.get(name, target)) 129 SIGNATURES[name] = result 130 return result 131 132 133def define_copy_method(name): 134 target = getattr(Random, name) 135 136 def implementation(self, **kwargs): 137 result = self._hypothesis_do_random(name, kwargs) 138 self._hypothesis_log_random(name, kwargs, result) 139 return result 140 141 spec = inspect.getfullargspec(STUBS.get(name, target)) 142 143 result = define_function_signature(target.__name__, target.__doc__, spec)( 144 implementation 145 ) 146 147 result.__module__ = __name__ 148 result.__qualname__ = "HypothesisRandom." + result.__name__ 149 150 setattr(HypothesisRandom, name, result) 151 152 153for r in RANDOM_METHODS: 154 define_copy_method(r) 155 156 157@attr.s(slots=True) 158class RandomState: 159 next_states = attr.ib(default=attr.Factory(dict)) 160 state_id = attr.ib(default=None) 161 162 163def state_for_seed(data, seed): 164 try: 165 seeds_to_states = data.seeds_to_states 166 except AttributeError: 167 seeds_to_states = {} 168 data.seeds_to_states = seeds_to_states 169 170 try: 171 state = seeds_to_states[seed] 172 except KeyError: 173 state = RandomState() 174 seeds_to_states[seed] = state 175 176 return state 177 178 179UNIFORM = floats(0, 1) 180 181 182def normalize_zero(f: float) -> float: 183 if f == 0.0: 184 return 0.0 185 else: 186 return f 187 188 189class ArtificialRandom(HypothesisRandom): 190 VERSION = 10 ** 6 191 192 def __init__(self, note_method_calls, data): 193 super().__init__(note_method_calls=note_method_calls) 194 self.__data = data 195 self.__state = RandomState() 196 197 def __repr__(self): 198 return "HypothesisRandom(generated data)" 199 200 def __copy__(self): 201 result = ArtificialRandom( 202 note_method_calls=self._HypothesisRandom__note_method_calls, 203 data=self.__data, 204 ) 205 result.setstate(self.getstate()) 206 return result 207 208 def __convert_result(self, method, kwargs, result): 209 if method == "choice": 210 return kwargs.get("seq")[result] 211 if method in ("choices", "sample"): 212 seq = kwargs["population"] 213 return [seq[i] for i in result] 214 if method == "shuffle": 215 seq = kwargs["x"] 216 original = list(seq) 217 for i, i2 in enumerate(result): 218 seq[i] = original[i2] 219 return 220 return result 221 222 def _hypothesis_do_random(self, method, kwargs): 223 if method == "choices": 224 key = (method, len(kwargs["population"]), kwargs.get("k")) 225 elif method == "choice": 226 key = (method, len(kwargs["seq"])) 227 elif method == "shuffle": 228 key = (method, len(kwargs["x"])) 229 else: 230 key = (method,) + tuple(sorted(kwargs)) 231 232 try: 233 result, self.__state = self.__state.next_states[key] 234 except KeyError: 235 pass 236 else: 237 return self.__convert_result(method, kwargs, result) 238 239 if method == "_randbelow": 240 result = cu.integer_range(self.__data, 0, kwargs["n"] - 1) 241 elif method in ("betavariate", "random"): 242 result = self.__data.draw(UNIFORM) 243 elif method == "uniform": 244 a = normalize_zero(kwargs["a"]) 245 b = normalize_zero(kwargs["b"]) 246 result = self.__data.draw(floats(a, b)) 247 elif method in ("weibullvariate", "gammavariate"): 248 result = self.__data.draw(floats(min_value=0.0, allow_infinity=False)) 249 elif method in ("gauss", "normalvariate"): 250 mu = kwargs["mu"] 251 result = mu + self.__data.draw( 252 floats(allow_nan=False, allow_infinity=False) 253 ) 254 elif method == "vonmisesvariate": 255 result = self.__data.draw(floats(0, 2 * math.pi)) 256 elif method == "randrange": 257 if kwargs["stop"] is None: 258 stop = kwargs["start"] 259 start = 0 260 else: 261 start = kwargs["start"] 262 stop = kwargs["stop"] 263 264 step = kwargs["step"] 265 if start == stop: 266 raise ValueError(f"empty range for randrange({start}, {stop}, {step})") 267 268 if step != 1: 269 endpoint = (stop - start) // step 270 if (start - stop) % step == 0: 271 endpoint -= 1 272 273 i = cu.integer_range(self.__data, 0, endpoint) 274 result = start + i * step 275 else: 276 result = cu.integer_range(self.__data, start, stop - 1) 277 elif method == "randint": 278 result = cu.integer_range(self.__data, kwargs["a"], kwargs["b"]) 279 elif method == "choice": 280 seq = kwargs["seq"] 281 result = cu.integer_range(self.__data, 0, len(seq) - 1) 282 elif method == "choices": 283 k = kwargs["k"] 284 result = self.__data.draw( 285 lists( 286 integers(0, len(kwargs["population"]) - 1), 287 min_size=k, 288 max_size=k, 289 ) 290 ) 291 elif method == "sample": 292 k = kwargs["k"] 293 seq = kwargs["population"] 294 295 if k > len(seq) or k < 0: 296 raise ValueError( 297 f"Sample size {k} not in expected range 0 <= k <= {len(seq)}" 298 ) 299 300 result = self.__data.draw( 301 lists( 302 sampled_from(range(len(seq))), 303 min_size=k, 304 max_size=k, 305 unique=True, 306 ) 307 ) 308 309 elif method == "getrandbits": 310 result = self.__data.draw_bits(kwargs["n"]) 311 elif method == "triangular": 312 low = normalize_zero(kwargs["low"]) 313 high = normalize_zero(kwargs["high"]) 314 mode = normalize_zero(kwargs["mode"]) 315 if mode is None: 316 result = self.__data.draw(floats(low, high)) 317 elif self.__data.draw_bits(1): 318 result = self.__data.draw(floats(mode, high)) 319 else: 320 result = self.__data.draw(floats(low, mode)) 321 elif method in ("paretovariate", "expovariate", "lognormvariate"): 322 result = self.__data.draw(floats(min_value=0.0)) 323 elif method == "shuffle": 324 result = self.__data.draw(permutations(range(len(kwargs["x"])))) 325 # This is tested for but only appears in 3.9 so doesn't appear in coverage. 326 elif method == "randbytes": # pragma: no cover 327 n = kwargs["n"] 328 result = self.__data.draw(binary(min_size=n, max_size=n)) 329 else: 330 raise NotImplementedError(method) 331 332 new_state = RandomState() 333 self.__state.next_states[key] = (result, new_state) 334 self.__state = new_state 335 336 return self.__convert_result(method, kwargs, result) 337 338 def seed(self, seed): 339 self.__state = state_for_seed(self.__data, seed) 340 341 def getstate(self): 342 if self.__state.state_id is not None: 343 return self.__state.state_id 344 345 try: 346 states_for_ids = self.__data.states_for_ids 347 except AttributeError: 348 states_for_ids = {} 349 self.__data.states_for_ids = states_for_ids 350 351 self.__state.state_id = len(states_for_ids) 352 states_for_ids[self.__state.state_id] = self.__state 353 354 return self.__state.state_id 355 356 def setstate(self, state): 357 self.__state = self.__data.states_for_ids[state] 358 359 360DUMMY_RANDOM = Random(0) 361 362 363def convert_kwargs(name, kwargs): 364 kwargs = dict(kwargs) 365 366 signature = sig_of(name) 367 368 bound = signature.bind(DUMMY_RANDOM, **kwargs) 369 bound.apply_defaults() 370 371 for k in list(kwargs): 372 if ( 373 kwargs[k] is signature.parameters[k].default 374 or signature.parameters[k].kind != inspect.Parameter.KEYWORD_ONLY 375 ): 376 kwargs.pop(k) 377 378 arg_names = list(signature.parameters)[1:] 379 380 args = [] 381 382 for a in arg_names: 383 if signature.parameters[a].kind == inspect.Parameter.KEYWORD_ONLY: 384 break 385 args.append(bound.arguments[a]) 386 kwargs.pop(a, None) 387 388 while args: 389 name = arg_names[len(args) - 1] 390 if args[-1] is signature.parameters[name].default: 391 args.pop() 392 else: 393 break # pragma: no cover # Only on Python < 3.8 394 395 return (args, kwargs) 396 397 398class TrueRandom(HypothesisRandom): 399 def __init__(self, seed, note_method_calls): 400 super().__init__(note_method_calls) 401 self.__seed = seed 402 self.__random = Random(seed) 403 404 def _hypothesis_do_random(self, method, kwargs): 405 args, kwargs = convert_kwargs(method, kwargs) 406 407 return getattr(self.__random, method)(*args, **kwargs) 408 409 def __copy__(self): 410 result = TrueRandom( 411 seed=self.__seed, 412 note_method_calls=self._HypothesisRandom__note_method_calls, 413 ) 414 result.setstate(self.getstate()) 415 return result 416 417 def __repr__(self): 418 return f"Random({self.__seed!r})" 419 420 def seed(self, seed): 421 self.__random.seed(seed) 422 self.__seed = seed 423 424 def getstate(self): 425 return self.__random.getstate() 426 427 def setstate(self, state): 428 self.__random.setstate(state) 429 430 431class RandomStrategy(SearchStrategy): 432 def __init__(self, note_method_calls, use_true_random): 433 self.__note_method_calls = note_method_calls 434 self.__use_true_random = use_true_random 435 436 def do_draw(self, data): 437 if self.__use_true_random: 438 seed = data.draw_bits(64) 439 return TrueRandom(seed=seed, note_method_calls=self.__note_method_calls) 440 else: 441 return ArtificialRandom( 442 note_method_calls=self.__note_method_calls, data=data 443 ) 444