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 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 # New in Python 3.12, so not taken by our coverage job 

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

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

273 elif method == "choice": 

274 seq = kwargs["seq"] 

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

276 elif method == "choices": 

277 k = kwargs["k"] 

278 result = self.__data.draw( 

279 lists( 

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

281 min_size=k, 

282 max_size=k, 

283 ) 

284 ) 

285 elif method == "sample": 

286 k = kwargs["k"] 

287 seq = kwargs["population"] 

288 

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

290 raise ValueError( 

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

292 ) 

293 

294 if k == 0: 

295 result = [] 

296 else: 

297 result = self.__data.draw( 

298 lists( 

299 sampled_from(range(len(seq))), 

300 min_size=k, 

301 max_size=k, 

302 unique=True, 

303 ) 

304 ) 

305 

306 elif method == "getrandbits": 

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

308 elif method == "triangular": 

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

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

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

312 if mode is None: 

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

314 elif self.__data.draw_boolean(0.5): 

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

316 else: 

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

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

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

320 elif method == "shuffle": 

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

322 elif method == "randbytes": 

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

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

325 else: 

326 raise NotImplementedError(method) 

327 

328 new_state = RandomState() 

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

330 self.__state = new_state 

331 

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

333 

334 def seed(self, seed): 

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

336 

337 def getstate(self): 

338 if self.__state.state_id is not None: 

339 return self.__state.state_id 

340 

341 if self.__data.states_for_ids is None: 

342 self.__data.states_for_ids = {} 

343 states_for_ids = self.__data.states_for_ids 

344 self.__state.state_id = len(states_for_ids) 

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

346 

347 return self.__state.state_id 

348 

349 def setstate(self, state): 

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

351 

352 

353DUMMY_RANDOM = Random(0) 

354 

355 

356def convert_kwargs(name, kwargs): 

357 kwargs = dict(kwargs) 

358 

359 signature = sig_of(name) 

360 params = signature.parameters 

361 

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

363 bound.apply_defaults() 

364 

365 for k in list(kwargs): 

366 if ( 

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

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

369 ): 

370 kwargs.pop(k) 

371 

372 arg_names = list(params)[1:] 

373 

374 args = [] 

375 

376 for a in arg_names: 

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

378 break 

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

380 kwargs.pop(a, None) 

381 

382 while args: 

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

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

385 args.pop() 

386 else: 

387 break 

388 

389 return (args, kwargs) 

390 

391 

392class TrueRandom(HypothesisRandom): 

393 def __init__(self, seed, note_method_calls): 

394 super().__init__(note_method_calls=note_method_calls) 

395 self.__seed = seed 

396 self.__random = Random(seed) 

397 

398 def _hypothesis_do_random(self, method, kwargs): 

399 fn = getattr(self.__random, method) 

400 try: 

401 return fn(**kwargs) 

402 except TypeError: 

403 pass 

404 args, kwargs = convert_kwargs(method, kwargs) 

405 return fn(*args, **kwargs) 

406 

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

408 result = TrueRandom( 

409 seed=self.__seed, 

410 note_method_calls=self._note_method_calls, 

411 ) 

412 result.setstate(self.getstate()) 

413 return result 

414 

415 def __repr__(self) -> str: 

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

417 

418 def seed(self, seed): 

419 self.__random.seed(seed) 

420 self.__seed = seed 

421 

422 def getstate(self): 

423 return self.__random.getstate() 

424 

425 def setstate(self, state): 

426 self.__random.setstate(state) 

427 

428 

429class RandomStrategy(SearchStrategy[HypothesisRandom]): 

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

431 super().__init__() 

432 self.__note_method_calls = note_method_calls 

433 self.__use_true_random = use_true_random 

434 

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

436 if self.__use_true_random: 

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

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

439 else: 

440 return ArtificialRandom( 

441 note_method_calls=self.__note_method_calls, data=data 

442 )