Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/hypothesis/strategies/_internal/random.py: 3%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

276 statements  

1# This file is part of Hypothesis, which may be found at 

2# https://github.com/HypothesisWorks/hypothesis/ 

3# 

4# Copyright the Hypothesis Authors. 

5# Individual contributors are listed in AUTHORS.rst and the git log. 

6# 

7# This Source Code Form is subject to the terms of the Mozilla Public License, 

8# v. 2.0. If a copy of the MPL was not distributed with this file, You can 

9# obtain one at https://mozilla.org/MPL/2.0/. 

10 

11import abc 

12import inspect 

13import math 

14from dataclasses import dataclass, field 

15from random import Random 

16from typing import Any 

17 

18from hypothesis.control import should_note 

19from hypothesis.errors import InvalidArgument 

20from hypothesis.internal.conjecture.data import ConjectureData 

21from hypothesis.internal.reflection import define_function_signature 

22from hypothesis.reporting import report 

23from hypothesis.strategies._internal.core import lists, permutations, sampled_from 

24from hypothesis.strategies._internal.numbers import floats, integers 

25from hypothesis.strategies._internal.strategies import SearchStrategy 

26 

27 

28class HypothesisRandom(Random, abc.ABC): 

29 """A subclass of Random designed to expose the seed it was initially 

30 provided with.""" 

31 

32 def __init__(self, *, note_method_calls: bool) -> None: 

33 self._note_method_calls = note_method_calls 

34 

35 def __deepcopy__(self, table): 

36 return self.__copy__() 

37 

38 @abc.abstractmethod 

39 def seed(self, a=None, version=2): 

40 raise NotImplementedError 

41 

42 @abc.abstractmethod 

43 def getstate(self): 

44 raise NotImplementedError 

45 

46 @abc.abstractmethod 

47 def setstate(self, state): 

48 raise NotImplementedError 

49 

50 @abc.abstractmethod 

51 def _hypothesis_do_random(self, method, kwargs): 

52 raise NotImplementedError 

53 

54 def _hypothesis_log_random(self, method, kwargs, result): 

55 if not (self._note_method_calls and should_note()): 

56 return 

57 

58 args, kwargs = convert_kwargs(method, kwargs) 

59 argstr = ", ".join( 

60 list(map(repr, args)) + [f"{k}={v!r}" for k, v in kwargs.items()] 

61 ) 

62 report(f"{self!r}.{method}({argstr}) -> {result!r}") 

63 

64 

65RANDOM_METHODS = [ 

66 name 

67 for name in [ 

68 "_randbelow", 

69 "betavariate", 

70 "binomialvariate", 

71 "choice", 

72 "choices", 

73 "expovariate", 

74 "gammavariate", 

75 "gauss", 

76 "getrandbits", 

77 "lognormvariate", 

78 "normalvariate", 

79 "paretovariate", 

80 "randint", 

81 "random", 

82 "randrange", 

83 "sample", 

84 "shuffle", 

85 "triangular", 

86 "uniform", 

87 "vonmisesvariate", 

88 "weibullvariate", 

89 "randbytes", 

90 ] 

91 if hasattr(Random, name) 

92] 

93 

94 

95# Fake shims to get a good signature 

96def getrandbits(self, n: int) -> int: # type: ignore 

97 raise NotImplementedError 

98 

99 

100def random(self) -> float: # type: ignore 

101 raise NotImplementedError 

102 

103 

104def _randbelow(self, n: int) -> int: # type: ignore 

105 raise NotImplementedError 

106 

107 

108STUBS = {f.__name__: f for f in [getrandbits, random, _randbelow]} 

109 

110 

111SIGNATURES: dict[str, inspect.Signature] = {} 

112 

113 

114def sig_of(name): 

115 try: 

116 return SIGNATURES[name] 

117 except KeyError: 

118 pass 

119 

120 target = getattr(Random, name) 

121 result = inspect.signature(STUBS.get(name, target)) 

122 SIGNATURES[name] = result 

123 return result 

124 

125 

126def define_copy_method(name): 

127 target = getattr(Random, name) 

128 

129 def implementation(self, **kwargs): 

130 result = self._hypothesis_do_random(name, kwargs) 

131 self._hypothesis_log_random(name, kwargs, result) 

132 return result 

133 

134 sig = inspect.signature(STUBS.get(name, target)) 

135 

136 result = define_function_signature(target.__name__, target.__doc__, sig)( 

137 implementation 

138 ) 

139 

140 result.__module__ = __name__ 

141 result.__qualname__ = "HypothesisRandom." + result.__name__ 

142 

143 setattr(HypothesisRandom, name, result) 

144 

145 

146for r in RANDOM_METHODS: 

147 define_copy_method(r) 

148 

149 

150@dataclass(slots=True, frozen=False) 

151class RandomState: 

152 next_states: dict = field(default_factory=dict) 

153 state_id: Any = None 

154 

155 

156def state_for_seed(data, seed): 

157 if data.seeds_to_states is None: 

158 data.seeds_to_states = {} 

159 

160 seeds_to_states = data.seeds_to_states 

161 try: 

162 state = seeds_to_states[seed] 

163 except KeyError: 

164 state = RandomState() 

165 seeds_to_states[seed] = state 

166 

167 return state 

168 

169 

170def check_seed(a: object) -> None: 

171 if a is None: 

172 raise InvalidArgument( 

173 "Hypothesis-generated Random instances do not support seed() " 

174 "without an explicit seed" 

175 ) 

176 

177 

178def normalize_zero(f: float) -> float: 

179 if f == 0.0: 

180 return 0.0 

181 else: 

182 return f 

183 

184 

185class ArtificialRandom(HypothesisRandom): 

186 VERSION = 10**6 

187 

188 def __init__(self, *, note_method_calls: bool, data: ConjectureData) -> None: 

189 super().__init__(note_method_calls=note_method_calls) 

190 self.__data = data 

191 self.__state = RandomState() 

192 

193 def __repr__(self) -> str: 

194 return "HypothesisRandom(generated data)" 

195 

196 def __copy__(self) -> "ArtificialRandom": 

197 result = ArtificialRandom( 

198 note_method_calls=self._note_method_calls, 

199 data=self.__data, 

200 ) 

201 result.setstate(self.getstate()) 

202 return result 

203 

204 def __convert_result(self, method, kwargs, result): 

205 if method == "choice": 

206 return kwargs.get("seq")[result] 

207 if method in ("choices", "sample"): 

208 seq = kwargs["population"] 

209 return [seq[i] for i in result] 

210 if method == "shuffle": 

211 seq = kwargs["x"] 

212 original = list(seq) 

213 for i, i2 in enumerate(result): 

214 seq[i] = original[i2] 

215 return None 

216 return result 

217 

218 def _hypothesis_do_random(self, method, kwargs): 

219 if method == "choices": 

220 key = (method, len(kwargs["population"]), kwargs.get("k")) 

221 elif method == "choice": 

222 key = (method, len(kwargs["seq"])) 

223 elif method == "shuffle": 

224 key = (method, len(kwargs["x"])) 

225 else: 

226 key = (method, *sorted(kwargs)) 

227 

228 try: 

229 result, self.__state = self.__state.next_states[key] 

230 except KeyError: 

231 pass 

232 else: 

233 return self.__convert_result(method, kwargs, result) 

234 

235 if method == "_randbelow": 

236 result = self.__data.draw_integer(0, kwargs["n"] - 1) 

237 elif method == "random": 

238 # See https://github.com/HypothesisWorks/hypothesis/issues/4297 

239 # for numerics/bounds of "random" and "betavariate" 

240 result = self.__data.draw(floats(0, 1, exclude_max=True)) 

241 elif method == "betavariate": 

242 result = self.__data.draw(floats(0, 1)) 

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 = self.__data.draw_integer(0, endpoint) 

274 result = start + i * step 

275 else: 

276 result = self.__data.draw_integer(start, stop - 1) 

277 elif method == "randint": 

278 result = self.__data.draw_integer(kwargs["a"], kwargs["b"]) 

279 elif method == "binomialvariate": 

280 result = self.__data.draw_integer(0, kwargs["n"]) 

281 elif method == "choice": 

282 seq = kwargs["seq"] 

283 result = self.__data.draw_integer(0, len(seq) - 1) 

284 elif method == "choices": 

285 k = kwargs["k"] 

286 result = self.__data.draw( 

287 lists( 

288 integers(0, len(kwargs["population"]) - 1), 

289 min_size=k, 

290 max_size=k, 

291 ) 

292 ) 

293 elif method == "sample": 

294 k = kwargs["k"] 

295 seq = kwargs["population"] 

296 

297 if k > len(seq) or k < 0: 

298 raise ValueError( 

299 f"Sample size {k} not in expected range 0 <= k <= {len(seq)}" 

300 ) 

301 

302 if k == 0: 

303 result = [] 

304 else: 

305 result = self.__data.draw( 

306 lists( 

307 sampled_from(range(len(seq))), 

308 min_size=k, 

309 max_size=k, 

310 unique=True, 

311 ) 

312 ) 

313 

314 elif method == "getrandbits": 

315 result = self.__data.draw_integer(0, 2 ** kwargs["n"] - 1) 

316 elif method == "triangular": 

317 low = normalize_zero(kwargs["low"]) 

318 high = normalize_zero(kwargs["high"]) 

319 mode = normalize_zero(kwargs["mode"]) 

320 if mode is None: 

321 result = self.__data.draw(floats(low, high)) 

322 elif self.__data.draw_boolean(0.5): 

323 result = self.__data.draw(floats(mode, high)) 

324 else: 

325 result = self.__data.draw(floats(low, mode)) 

326 elif method in ("paretovariate", "expovariate", "lognormvariate"): 

327 result = self.__data.draw(floats(min_value=0.0)) 

328 elif method == "shuffle": 

329 result = self.__data.draw(permutations(range(len(kwargs["x"])))) 

330 elif method == "randbytes": 

331 n = int(kwargs["n"]) 

332 result = self.__data.draw_bytes(min_size=n, max_size=n) 

333 else: 

334 raise NotImplementedError(method) 

335 

336 new_state = RandomState() 

337 self.__state.next_states[key] = (result, new_state) 

338 self.__state = new_state 

339 

340 return self.__convert_result(method, kwargs, result) 

341 

342 def seed(self, a=None, version=2): 

343 check_seed(a) 

344 self.__state = state_for_seed(self.__data, a) 

345 

346 def getstate(self): 

347 if self.__state.state_id is not None: 

348 return self.__state.state_id 

349 

350 if self.__data.states_for_ids is None: 

351 self.__data.states_for_ids = {} 

352 states_for_ids = self.__data.states_for_ids 

353 self.__state.state_id = len(states_for_ids) 

354 states_for_ids[self.__state.state_id] = self.__state 

355 

356 return self.__state.state_id 

357 

358 def setstate(self, state): 

359 self.__state = self.__data.states_for_ids[state] 

360 

361 

362DUMMY_RANDOM = Random(0) 

363 

364 

365def convert_kwargs(name, kwargs): 

366 kwargs = dict(kwargs) 

367 

368 signature = sig_of(name) 

369 params = signature.parameters 

370 

371 bound = signature.bind(DUMMY_RANDOM, **kwargs) 

372 bound.apply_defaults() 

373 

374 for k in list(kwargs): 

375 if ( 

376 kwargs[k] is params[k].default 

377 or params[k].kind != inspect.Parameter.KEYWORD_ONLY 

378 ): 

379 kwargs.pop(k) 

380 

381 arg_names = list(params)[1:] 

382 

383 args = [] 

384 

385 for a in arg_names: 

386 if params[a].kind == inspect.Parameter.KEYWORD_ONLY: 

387 break 

388 args.append(bound.arguments[a]) 

389 kwargs.pop(a, None) 

390 

391 while args: 

392 name = arg_names[len(args) - 1] 

393 if args[-1] is params[name].default: 

394 args.pop() 

395 else: 

396 break 

397 

398 return (args, kwargs) 

399 

400 

401class TrueRandom(HypothesisRandom): 

402 def __init__(self, seed, note_method_calls): 

403 super().__init__(note_method_calls=note_method_calls) 

404 self.__seed = seed 

405 self.__random = Random(seed) 

406 

407 def _hypothesis_do_random(self, method, kwargs): 

408 fn = getattr(self.__random, method) 

409 try: 

410 return fn(**kwargs) 

411 except TypeError: 

412 pass 

413 args, kwargs = convert_kwargs(method, kwargs) 

414 return fn(*args, **kwargs) 

415 

416 def __copy__(self) -> "TrueRandom": 

417 result = TrueRandom( 

418 seed=self.__seed, 

419 note_method_calls=self._note_method_calls, 

420 ) 

421 result.setstate(self.getstate()) 

422 return result 

423 

424 def __repr__(self) -> str: 

425 return f"Random({self.__seed!r})" 

426 

427 def seed(self, a=None, version=2): 

428 check_seed(a) 

429 self.__random.seed(a, version) 

430 self.__seed = a 

431 

432 def getstate(self): 

433 return self.__random.getstate() 

434 

435 def setstate(self, state): 

436 self.__random.setstate(state) 

437 

438 

439class RandomStrategy(SearchStrategy[HypothesisRandom]): 

440 def __init__(self, *, note_method_calls: bool, use_true_random: bool) -> None: 

441 super().__init__() 

442 self.__note_method_calls = note_method_calls 

443 self.__use_true_random = use_true_random 

444 

445 def do_draw(self, data: ConjectureData) -> HypothesisRandom: 

446 if self.__use_true_random: 

447 seed = data.draw_integer(0, 2**64 - 1) 

448 return TrueRandom(seed=seed, note_method_calls=self.__note_method_calls) 

449 else: 

450 return ArtificialRandom( 

451 note_method_calls=self.__note_method_calls, data=data 

452 )