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

270 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.internal.conjecture.data import ConjectureData 

20from hypothesis.internal.reflection import define_function_signature 

21from hypothesis.reporting import report 

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

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

24from hypothesis.strategies._internal.strategies import SearchStrategy 

25 

26 

27class HypothesisRandom(Random, abc.ABC): 

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

29 provided with.""" 

30 

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

32 self._note_method_calls = note_method_calls 

33 

34 def __deepcopy__(self, table): 

35 return self.__copy__() 

36 

37 @abc.abstractmethod 

38 def seed(self, seed): 

39 raise NotImplementedError 

40 

41 @abc.abstractmethod 

42 def getstate(self): 

43 raise NotImplementedError 

44 

45 @abc.abstractmethod 

46 def setstate(self, state): 

47 raise NotImplementedError 

48 

49 @abc.abstractmethod 

50 def _hypothesis_do_random(self, method, kwargs): 

51 raise NotImplementedError 

52 

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

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

55 return 

56 

57 args, kwargs = convert_kwargs(method, kwargs) 

58 argstr = ", ".join( 

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

60 ) 

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

62 

63 

64RANDOM_METHODS = [ 

65 name 

66 for name in [ 

67 "_randbelow", 

68 "betavariate", 

69 "binomialvariate", 

70 "choice", 

71 "choices", 

72 "expovariate", 

73 "gammavariate", 

74 "gauss", 

75 "getrandbits", 

76 "lognormvariate", 

77 "normalvariate", 

78 "paretovariate", 

79 "randint", 

80 "random", 

81 "randrange", 

82 "sample", 

83 "shuffle", 

84 "triangular", 

85 "uniform", 

86 "vonmisesvariate", 

87 "weibullvariate", 

88 "randbytes", 

89 ] 

90 if hasattr(Random, name) 

91] 

92 

93 

94# Fake shims to get a good signature 

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

96 raise NotImplementedError 

97 

98 

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

100 raise NotImplementedError 

101 

102 

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

104 raise NotImplementedError 

105 

106 

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

108 

109 

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

111 

112 

113def sig_of(name): 

114 try: 

115 return SIGNATURES[name] 

116 except KeyError: 

117 pass 

118 

119 target = getattr(Random, name) 

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

121 SIGNATURES[name] = result 

122 return result 

123 

124 

125def define_copy_method(name): 

126 target = getattr(Random, name) 

127 

128 def implementation(self, **kwargs): 

129 result = self._hypothesis_do_random(name, kwargs) 

130 self._hypothesis_log_random(name, kwargs, result) 

131 return result 

132 

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

134 

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

136 implementation 

137 ) 

138 

139 result.__module__ = __name__ 

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

141 

142 setattr(HypothesisRandom, name, result) 

143 

144 

145for r in RANDOM_METHODS: 

146 define_copy_method(r) 

147 

148 

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

150class RandomState: 

151 next_states: dict = field(default_factory=dict) 

152 state_id: Any = None 

153 

154 

155def state_for_seed(data, seed): 

156 if data.seeds_to_states is None: 

157 data.seeds_to_states = {} 

158 

159 seeds_to_states = data.seeds_to_states 

160 try: 

161 state = seeds_to_states[seed] 

162 except KeyError: 

163 state = RandomState() 

164 seeds_to_states[seed] = state 

165 

166 return state 

167 

168 

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

170 if f == 0.0: 

171 return 0.0 

172 else: 

173 return f 

174 

175 

176class ArtificialRandom(HypothesisRandom): 

177 VERSION = 10**6 

178 

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

180 super().__init__(note_method_calls=note_method_calls) 

181 self.__data = data 

182 self.__state = RandomState() 

183 

184 def __repr__(self) -> str: 

185 return "HypothesisRandom(generated data)" 

186 

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

188 result = ArtificialRandom( 

189 note_method_calls=self._note_method_calls, 

190 data=self.__data, 

191 ) 

192 result.setstate(self.getstate()) 

193 return result 

194 

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

196 if method == "choice": 

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

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

199 seq = kwargs["population"] 

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

201 if method == "shuffle": 

202 seq = kwargs["x"] 

203 original = list(seq) 

204 for i, i2 in enumerate(result): 

205 seq[i] = original[i2] 

206 return None 

207 return result 

208 

209 def _hypothesis_do_random(self, method, kwargs): 

210 if method == "choices": 

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

212 elif method == "choice": 

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

214 elif method == "shuffle": 

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

216 else: 

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

218 

219 try: 

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

221 except KeyError: 

222 pass 

223 else: 

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

225 

226 if method == "_randbelow": 

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

228 elif method == "random": 

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

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

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

232 elif method == "betavariate": 

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

234 elif method == "uniform": 

235 a = normalize_zero(kwargs["a"]) 

236 b = normalize_zero(kwargs["b"]) 

237 result = self.__data.draw(floats(a, b)) 

238 elif method in ("weibullvariate", "gammavariate"): 

239 result = self.__data.draw(floats(min_value=0.0, allow_infinity=False)) 

240 elif method in ("gauss", "normalvariate"): 

241 mu = kwargs["mu"] 

242 result = mu + self.__data.draw( 

243 floats(allow_nan=False, allow_infinity=False) 

244 ) 

245 elif method == "vonmisesvariate": 

246 result = self.__data.draw(floats(0, 2 * math.pi)) 

247 elif method == "randrange": 

248 if kwargs["stop"] is None: 

249 stop = kwargs["start"] 

250 start = 0 

251 else: 

252 start = kwargs["start"] 

253 stop = kwargs["stop"] 

254 

255 step = kwargs["step"] 

256 if start == stop: 

257 raise ValueError(f"empty range for randrange({start}, {stop}, {step})") 

258 

259 if step != 1: 

260 endpoint = (stop - start) // step 

261 if (start - stop) % step == 0: 

262 endpoint -= 1 

263 

264 i = self.__data.draw_integer(0, endpoint) 

265 result = start + i * step 

266 else: 

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

268 elif method == "randint": 

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

270 elif method == "binomialvariate": 

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

272 elif method == "choice": 

273 seq = kwargs["seq"] 

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

275 elif method == "choices": 

276 k = kwargs["k"] 

277 result = self.__data.draw( 

278 lists( 

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

280 min_size=k, 

281 max_size=k, 

282 ) 

283 ) 

284 elif method == "sample": 

285 k = kwargs["k"] 

286 seq = kwargs["population"] 

287 

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

289 raise ValueError( 

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

291 ) 

292 

293 if k == 0: 

294 result = [] 

295 else: 

296 result = self.__data.draw( 

297 lists( 

298 sampled_from(range(len(seq))), 

299 min_size=k, 

300 max_size=k, 

301 unique=True, 

302 ) 

303 ) 

304 

305 elif method == "getrandbits": 

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

307 elif method == "triangular": 

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

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

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

311 if mode is None: 

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

313 elif self.__data.draw_boolean(0.5): 

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

315 else: 

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

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

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

319 elif method == "shuffle": 

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

321 elif method == "randbytes": 

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

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

324 else: 

325 raise NotImplementedError(method) 

326 

327 new_state = RandomState() 

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

329 self.__state = new_state 

330 

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

332 

333 def seed(self, seed): 

334 self.__state = state_for_seed(self.__data, seed) 

335 

336 def getstate(self): 

337 if self.__state.state_id is not None: 

338 return self.__state.state_id 

339 

340 if self.__data.states_for_ids is None: 

341 self.__data.states_for_ids = {} 

342 states_for_ids = self.__data.states_for_ids 

343 self.__state.state_id = len(states_for_ids) 

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

345 

346 return self.__state.state_id 

347 

348 def setstate(self, state): 

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

350 

351 

352DUMMY_RANDOM = Random(0) 

353 

354 

355def convert_kwargs(name, kwargs): 

356 kwargs = dict(kwargs) 

357 

358 signature = sig_of(name) 

359 params = signature.parameters 

360 

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

362 bound.apply_defaults() 

363 

364 for k in list(kwargs): 

365 if ( 

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

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

368 ): 

369 kwargs.pop(k) 

370 

371 arg_names = list(params)[1:] 

372 

373 args = [] 

374 

375 for a in arg_names: 

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

377 break 

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

379 kwargs.pop(a, None) 

380 

381 while args: 

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

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

384 args.pop() 

385 else: 

386 break 

387 

388 return (args, kwargs) 

389 

390 

391class TrueRandom(HypothesisRandom): 

392 def __init__(self, seed, note_method_calls): 

393 super().__init__(note_method_calls=note_method_calls) 

394 self.__seed = seed 

395 self.__random = Random(seed) 

396 

397 def _hypothesis_do_random(self, method, kwargs): 

398 fn = getattr(self.__random, method) 

399 try: 

400 return fn(**kwargs) 

401 except TypeError: 

402 pass 

403 args, kwargs = convert_kwargs(method, kwargs) 

404 return fn(*args, **kwargs) 

405 

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

407 result = TrueRandom( 

408 seed=self.__seed, 

409 note_method_calls=self._note_method_calls, 

410 ) 

411 result.setstate(self.getstate()) 

412 return result 

413 

414 def __repr__(self) -> str: 

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

416 

417 def seed(self, seed): 

418 self.__random.seed(seed) 

419 self.__seed = seed 

420 

421 def getstate(self): 

422 return self.__random.getstate() 

423 

424 def setstate(self, state): 

425 self.__random.setstate(state) 

426 

427 

428class RandomStrategy(SearchStrategy[HypothesisRandom]): 

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

430 super().__init__() 

431 self.__note_method_calls = note_method_calls 

432 self.__use_true_random = use_true_random 

433 

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

435 if self.__use_true_random: 

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

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

438 else: 

439 return ArtificialRandom( 

440 note_method_calls=self.__note_method_calls, data=data 

441 )