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

268 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 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 normalize_zero(f: float) -> float: 

171 if f == 0.0: 

172 return 0.0 

173 else: 

174 return f 

175 

176 

177class ArtificialRandom(HypothesisRandom): 

178 VERSION = 10**6 

179 

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

181 super().__init__(note_method_calls=note_method_calls) 

182 self.__data = data 

183 self.__state = RandomState() 

184 

185 def __repr__(self) -> str: 

186 return "HypothesisRandom(generated data)" 

187 

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

189 result = ArtificialRandom( 

190 note_method_calls=self._note_method_calls, 

191 data=self.__data, 

192 ) 

193 result.setstate(self.getstate()) 

194 return result 

195 

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

197 if method == "choice": 

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

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

200 seq = kwargs["population"] 

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

202 if method == "shuffle": 

203 seq = kwargs["x"] 

204 original = list(seq) 

205 for i, i2 in enumerate(result): 

206 seq[i] = original[i2] 

207 return None 

208 return result 

209 

210 def _hypothesis_do_random(self, method, kwargs): 

211 if method == "choices": 

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

213 elif method == "choice": 

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

215 elif method == "shuffle": 

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

217 else: 

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

219 

220 try: 

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

222 except KeyError: 

223 pass 

224 else: 

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

226 

227 if method == "_randbelow": 

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

229 elif method == "random": 

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

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

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

233 elif method == "betavariate": 

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

235 elif method == "uniform": 

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

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

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

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

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

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

242 mu = kwargs["mu"] 

243 result = mu + self.__data.draw( 

244 floats(allow_nan=False, allow_infinity=False) 

245 ) 

246 elif method == "vonmisesvariate": 

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

248 elif method == "randrange": 

249 if kwargs["stop"] is None: 

250 stop = kwargs["start"] 

251 start = 0 

252 else: 

253 start = kwargs["start"] 

254 stop = kwargs["stop"] 

255 

256 step = kwargs["step"] 

257 if start == stop: 

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

259 

260 if step != 1: 

261 endpoint = (stop - start) // step 

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

263 endpoint -= 1 

264 

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

266 result = start + i * step 

267 else: 

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

269 elif method == "randint": 

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

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

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

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

274 elif method == "choice": 

275 seq = kwargs["seq"] 

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

277 elif method == "choices": 

278 k = kwargs["k"] 

279 result = self.__data.draw( 

280 lists( 

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

282 min_size=k, 

283 max_size=k, 

284 ) 

285 ) 

286 elif method == "sample": 

287 k = kwargs["k"] 

288 seq = kwargs["population"] 

289 

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

291 raise ValueError( 

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

293 ) 

294 

295 if k == 0: 

296 result = [] 

297 else: 

298 result = self.__data.draw( 

299 lists( 

300 sampled_from(range(len(seq))), 

301 min_size=k, 

302 max_size=k, 

303 unique=True, 

304 ) 

305 ) 

306 

307 elif method == "getrandbits": 

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

309 elif method == "triangular": 

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

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

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

313 if mode is None: 

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

315 elif self.__data.draw_boolean(0.5): 

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

317 else: 

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

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

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

321 elif method == "shuffle": 

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

323 elif method == "randbytes": 

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

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

326 else: 

327 raise NotImplementedError(method) 

328 

329 new_state = RandomState() 

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

331 self.__state = new_state 

332 

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

334 

335 def seed(self, seed): 

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

337 

338 def getstate(self): 

339 if self.__state.state_id is not None: 

340 return self.__state.state_id 

341 

342 if self.__data.states_for_ids is None: 

343 self.__data.states_for_ids = {} 

344 states_for_ids = self.__data.states_for_ids 

345 self.__state.state_id = len(states_for_ids) 

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

347 

348 return self.__state.state_id 

349 

350 def setstate(self, state): 

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

352 

353 

354DUMMY_RANDOM = Random(0) 

355 

356 

357def convert_kwargs(name, kwargs): 

358 kwargs = dict(kwargs) 

359 

360 signature = sig_of(name) 

361 params = signature.parameters 

362 

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

364 bound.apply_defaults() 

365 

366 for k in list(kwargs): 

367 if ( 

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

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

370 ): 

371 kwargs.pop(k) 

372 

373 arg_names = list(params)[1:] 

374 

375 args = [] 

376 

377 for a in arg_names: 

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

379 break 

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

381 kwargs.pop(a, None) 

382 

383 while args: 

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

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

386 args.pop() 

387 else: 

388 break 

389 

390 return (args, kwargs) 

391 

392 

393class TrueRandom(HypothesisRandom): 

394 def __init__(self, seed, note_method_calls): 

395 super().__init__(note_method_calls=note_method_calls) 

396 self.__seed = seed 

397 self.__random = Random(seed) 

398 

399 def _hypothesis_do_random(self, method, kwargs): 

400 fn = getattr(self.__random, method) 

401 try: 

402 return fn(**kwargs) 

403 except TypeError: 

404 pass 

405 args, kwargs = convert_kwargs(method, kwargs) 

406 return fn(*args, **kwargs) 

407 

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

409 result = TrueRandom( 

410 seed=self.__seed, 

411 note_method_calls=self._note_method_calls, 

412 ) 

413 result.setstate(self.getstate()) 

414 return result 

415 

416 def __repr__(self) -> str: 

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

418 

419 def seed(self, seed): 

420 self.__random.seed(seed) 

421 self.__seed = seed 

422 

423 def getstate(self): 

424 return self.__random.getstate() 

425 

426 def setstate(self, state): 

427 self.__random.setstate(state) 

428 

429 

430class RandomStrategy(SearchStrategy[HypothesisRandom]): 

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

432 super().__init__() 

433 self.__note_method_calls = note_method_calls 

434 self.__use_true_random = use_true_random 

435 

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

437 if self.__use_true_random: 

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

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 )