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

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

271 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 random import Random 

15from typing import Any 

16 

17import attr 

18 

19from hypothesis.control import should_note 

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, seed): 

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@attr.s(slots=True) 

151class RandomState: 

152 next_states: dict = attr.ib(factory=dict) 

153 state_id: Any = attr.ib(default=None) 

154 

155 

156def state_for_seed(data, seed): 

157 try: 

158 seeds_to_states = data.seeds_to_states 

159 except AttributeError: 

160 seeds_to_states = {} 

161 data.seeds_to_states = seeds_to_states 

162 

163 try: 

164 state = seeds_to_states[seed] 

165 except KeyError: 

166 state = RandomState() 

167 seeds_to_states[seed] = state 

168 

169 return state 

170 

171 

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

173 if f == 0.0: 

174 return 0.0 

175 else: 

176 return f 

177 

178 

179class ArtificialRandom(HypothesisRandom): 

180 VERSION = 10**6 

181 

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

183 super().__init__(note_method_calls=note_method_calls) 

184 self.__data = data 

185 self.__state = RandomState() 

186 

187 def __repr__(self) -> str: 

188 return "HypothesisRandom(generated data)" 

189 

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

191 result = ArtificialRandom( 

192 note_method_calls=self._note_method_calls, 

193 data=self.__data, 

194 ) 

195 result.setstate(self.getstate()) 

196 return result 

197 

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

199 if method == "choice": 

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

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

202 seq = kwargs["population"] 

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

204 if method == "shuffle": 

205 seq = kwargs["x"] 

206 original = list(seq) 

207 for i, i2 in enumerate(result): 

208 seq[i] = original[i2] 

209 return None 

210 return result 

211 

212 def _hypothesis_do_random(self, method, kwargs): 

213 if method == "choices": 

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

215 elif method == "choice": 

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

217 elif method == "shuffle": 

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

219 else: 

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

221 

222 try: 

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

224 except KeyError: 

225 pass 

226 else: 

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

228 

229 if method == "_randbelow": 

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

231 elif method == "random": 

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

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

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

235 elif method == "betavariate": 

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

237 elif method == "uniform": 

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

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

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

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

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

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

244 mu = kwargs["mu"] 

245 result = mu + self.__data.draw( 

246 floats(allow_nan=False, allow_infinity=False) 

247 ) 

248 elif method == "vonmisesvariate": 

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

250 elif method == "randrange": 

251 if kwargs["stop"] is None: 

252 stop = kwargs["start"] 

253 start = 0 

254 else: 

255 start = kwargs["start"] 

256 stop = kwargs["stop"] 

257 

258 step = kwargs["step"] 

259 if start == stop: 

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

261 

262 if step != 1: 

263 endpoint = (stop - start) // step 

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

265 endpoint -= 1 

266 

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

268 result = start + i * step 

269 else: 

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

271 elif method == "randint": 

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

273 # New in Python 3.12, so not taken by our coverage job 

274 elif method == "binomialvariate": # pragma: no cover 

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

276 elif method == "choice": 

277 seq = kwargs["seq"] 

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

279 elif method == "choices": 

280 k = kwargs["k"] 

281 result = self.__data.draw( 

282 lists( 

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

284 min_size=k, 

285 max_size=k, 

286 ) 

287 ) 

288 elif method == "sample": 

289 k = kwargs["k"] 

290 seq = kwargs["population"] 

291 

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

293 raise ValueError( 

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

295 ) 

296 

297 if k == 0: 

298 result = [] 

299 else: 

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_integer(0, 2 ** kwargs["n"] - 1) 

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_boolean(0.5): 

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 elif method == "randbytes": 

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

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

328 else: 

329 raise NotImplementedError(method) 

330 

331 new_state = RandomState() 

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

333 self.__state = new_state 

334 

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

336 

337 def seed(self, seed): 

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

339 

340 def getstate(self): 

341 if self.__state.state_id is not None: 

342 return self.__state.state_id 

343 

344 try: 

345 states_for_ids = self.__data.states_for_ids 

346 except AttributeError: 

347 states_for_ids = {} 

348 self.__data.states_for_ids = states_for_ids 

349 

350 self.__state.state_id = len(states_for_ids) 

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

352 

353 return self.__state.state_id 

354 

355 def setstate(self, state): 

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

357 

358 

359DUMMY_RANDOM = Random(0) 

360 

361 

362def convert_kwargs(name, kwargs): 

363 kwargs = dict(kwargs) 

364 

365 signature = sig_of(name) 

366 params = signature.parameters 

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 params[k].default 

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

375 ): 

376 kwargs.pop(k) 

377 

378 arg_names = list(params)[1:] 

379 

380 args = [] 

381 

382 for a in arg_names: 

383 if params[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 params[name].default: 

391 args.pop() 

392 else: 

393 break 

394 

395 return (args, kwargs) 

396 

397 

398class TrueRandom(HypothesisRandom): 

399 def __init__(self, seed, note_method_calls): 

400 super().__init__(note_method_calls=note_method_calls) 

401 self.__seed = seed 

402 self.__random = Random(seed) 

403 

404 def _hypothesis_do_random(self, method, kwargs): 

405 fn = getattr(self.__random, method) 

406 try: 

407 return fn(**kwargs) 

408 except TypeError: 

409 pass 

410 args, kwargs = convert_kwargs(method, kwargs) 

411 return fn(*args, **kwargs) 

412 

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

414 result = TrueRandom( 

415 seed=self.__seed, 

416 note_method_calls=self._note_method_calls, 

417 ) 

418 result.setstate(self.getstate()) 

419 return result 

420 

421 def __repr__(self) -> str: 

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

423 

424 def seed(self, seed): 

425 self.__random.seed(seed) 

426 self.__seed = seed 

427 

428 def getstate(self): 

429 return self.__random.getstate() 

430 

431 def setstate(self, state): 

432 self.__random.setstate(state) 

433 

434 

435class RandomStrategy(SearchStrategy[HypothesisRandom]): 

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

437 self.__note_method_calls = note_method_calls 

438 self.__use_true_random = use_true_random 

439 

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

441 if self.__use_true_random: 

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

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

444 else: 

445 return ArtificialRandom( 

446 note_method_calls=self.__note_method_calls, data=data 

447 )