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

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

195 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 copy 

12import math 

13from collections.abc import Callable, Iterable 

14from typing import Any, overload 

15 

16from hypothesis import strategies as st 

17from hypothesis.control import current_build_context 

18from hypothesis.errors import InvalidArgument 

19from hypothesis.internal.conjecture import utils as cu 

20from hypothesis.internal.conjecture.data import ConjectureData 

21from hypothesis.internal.conjecture.engine import BUFFER_SIZE 

22from hypothesis.internal.conjecture.junkdrawer import LazySequenceCopy 

23from hypothesis.internal.conjecture.utils import combine_labels 

24from hypothesis.internal.filtering import get_integer_predicate_bounds 

25from hypothesis.internal.reflection import is_identity_function 

26from hypothesis.strategies._internal.strategies import ( 

27 T3, 

28 T4, 

29 T5, 

30 Ex, 

31 FilteredStrategy, 

32 RecurT, 

33 SampledFromStrategy, 

34 SearchStrategy, 

35 T, 

36 check_strategy, 

37 filter_not_satisfied, 

38) 

39from hypothesis.strategies._internal.utils import cacheable, defines_strategy 

40from hypothesis.utils.conventions import UniqueIdentifier 

41from hypothesis.vendor.pretty import ( 

42 ArgLabelsT, 

43 IDKey, 

44 _fixeddict_pprinter, 

45 _tuple_pprinter, 

46) 

47 

48 

49class TupleStrategy(SearchStrategy[tuple[Ex, ...]]): 

50 """A strategy responsible for fixed length tuples based on heterogeneous 

51 strategies for each of their elements.""" 

52 

53 def __init__(self, strategies: Iterable[SearchStrategy[Any]]): 

54 super().__init__() 

55 self.element_strategies = tuple(strategies) 

56 

57 def do_validate(self) -> None: 

58 for s in self.element_strategies: 

59 s.validate() 

60 

61 def calc_label(self) -> int: 

62 return combine_labels( 

63 self.class_label, *(s.label for s in self.element_strategies) 

64 ) 

65 

66 def __repr__(self) -> str: 

67 tuple_string = ", ".join(map(repr, self.element_strategies)) 

68 return f"TupleStrategy(({tuple_string}))" 

69 

70 def calc_has_reusable_values(self, recur: RecurT) -> bool: 

71 return all(recur(e) for e in self.element_strategies) 

72 

73 def do_draw(self, data: ConjectureData) -> tuple[Ex, ...]: 

74 context = current_build_context() 

75 arg_labels: ArgLabelsT = {} 

76 result = [] 

77 for i, strategy in enumerate(self.element_strategies): 

78 with context.track_arg_label(f"arg[{i}]") as arg_label: 

79 result.append(data.draw(strategy)) 

80 arg_labels |= arg_label 

81 

82 result = tuple(result) 

83 if arg_labels: 

84 context.known_object_printers[IDKey(result)].append( 

85 _tuple_pprinter(arg_labels) 

86 ) 

87 return result 

88 

89 def calc_is_empty(self, recur: RecurT) -> bool: 

90 return any(recur(e) for e in self.element_strategies) 

91 

92 

93@overload 

94def tuples() -> SearchStrategy[tuple[()]]: # pragma: no cover 

95 ... 

96 

97 

98@overload 

99def tuples(__a1: SearchStrategy[Ex]) -> SearchStrategy[tuple[Ex]]: # pragma: no cover 

100 ... 

101 

102 

103@overload 

104def tuples( 

105 __a1: SearchStrategy[Ex], __a2: SearchStrategy[T] 

106) -> SearchStrategy[tuple[Ex, T]]: # pragma: no cover 

107 ... 

108 

109 

110@overload 

111def tuples( 

112 __a1: SearchStrategy[Ex], __a2: SearchStrategy[T], __a3: SearchStrategy[T3] 

113) -> SearchStrategy[tuple[Ex, T, T3]]: # pragma: no cover 

114 ... 

115 

116 

117@overload 

118def tuples( 

119 __a1: SearchStrategy[Ex], 

120 __a2: SearchStrategy[T], 

121 __a3: SearchStrategy[T3], 

122 __a4: SearchStrategy[T4], 

123) -> SearchStrategy[tuple[Ex, T, T3, T4]]: # pragma: no cover 

124 ... 

125 

126 

127@overload 

128def tuples( 

129 __a1: SearchStrategy[Ex], 

130 __a2: SearchStrategy[T], 

131 __a3: SearchStrategy[T3], 

132 __a4: SearchStrategy[T4], 

133 __a5: SearchStrategy[T5], 

134) -> SearchStrategy[tuple[Ex, T, T3, T4, T5]]: # pragma: no cover 

135 ... 

136 

137 

138@overload 

139def tuples( 

140 *args: SearchStrategy[Any], 

141) -> SearchStrategy[tuple[Any, ...]]: # pragma: no cover 

142 ... 

143 

144 

145@cacheable 

146@defines_strategy() 

147def tuples(*args: SearchStrategy[Any]) -> SearchStrategy[tuple[Any, ...]]: 

148 """Return a strategy which generates a tuple of the same length as args by 

149 generating the value at index i from args[i]. 

150 

151 e.g. tuples(integers(), integers()) would generate a tuple of length 

152 two with both values an integer. 

153 

154 Examples from this strategy shrink by shrinking their component parts. 

155 """ 

156 for arg in args: 

157 check_strategy(arg) 

158 

159 return TupleStrategy(args) 

160 

161 

162class ListStrategy(SearchStrategy[list[Ex]]): 

163 """A strategy for lists which takes a strategy for its elements and the 

164 allowed lengths, and generates lists with the correct size and contents.""" 

165 

166 _nonempty_filters: tuple[Callable[[Any], Any], ...] = (bool, len, tuple, list) 

167 

168 def __init__( 

169 self, 

170 elements: SearchStrategy[Ex], 

171 min_size: int = 0, 

172 max_size: float | int | None = math.inf, 

173 ): 

174 super().__init__() 

175 self.min_size = min_size or 0 

176 self.max_size = max_size if max_size is not None else math.inf 

177 assert 0 <= self.min_size <= self.max_size 

178 self.average_size = min( 

179 max(self.min_size * 2, self.min_size + 5), 

180 0.5 * (self.min_size + self.max_size), 

181 ) 

182 self.element_strategy = elements 

183 if min_size > BUFFER_SIZE: 

184 raise InvalidArgument( 

185 f"{self!r} can never generate an example, because min_size is larger " 

186 "than Hypothesis supports. Including it is at best slowing down your " 

187 "tests for no benefit; at worst making them fail (maybe flakily) with " 

188 "a HealthCheck error." 

189 ) 

190 

191 def calc_label(self) -> int: 

192 return combine_labels(self.class_label, self.element_strategy.label) 

193 

194 def do_validate(self) -> None: 

195 self.element_strategy.validate() 

196 if self.is_empty: 

197 raise InvalidArgument( 

198 "Cannot create non-empty lists with elements drawn from " 

199 f"strategy {self.element_strategy!r} because it has no values." 

200 ) 

201 if self.element_strategy.is_empty and 0 < self.max_size < float("inf"): 

202 raise InvalidArgument( 

203 f"Cannot create a collection of max_size={self.max_size!r}, " 

204 "because no elements can be drawn from the element strategy " 

205 f"{self.element_strategy!r}" 

206 ) 

207 

208 def calc_is_empty(self, recur: RecurT) -> bool: 

209 if self.min_size == 0: 

210 return False 

211 return recur(self.element_strategy) 

212 

213 def do_draw(self, data: ConjectureData) -> list[Ex]: 

214 if self.element_strategy.is_empty: 

215 assert self.min_size == 0 

216 return [] 

217 

218 elements = cu.many( 

219 data, 

220 min_size=self.min_size, 

221 max_size=self.max_size, 

222 average_size=self.average_size, 

223 ) 

224 result = [] 

225 while elements.more(): 

226 result.append(data.draw(self.element_strategy)) 

227 return result 

228 

229 def __repr__(self) -> str: 

230 return ( 

231 f"{self.__class__.__name__}({self.element_strategy!r}, " 

232 f"min_size={self.min_size:_}, max_size={self.max_size:_})" 

233 ) 

234 

235 def filter(self, condition: Callable[[list[Ex]], Any]) -> SearchStrategy[list[Ex]]: 

236 if condition in self._nonempty_filters or is_identity_function(condition): 

237 assert self.max_size >= 1, "Always-empty is special cased in st.lists()" 

238 if self.min_size >= 1: 

239 return self 

240 new = copy.copy(self) 

241 new.min_size = 1 

242 return new 

243 

244 constraints, pred = get_integer_predicate_bounds(condition) 

245 if constraints.get("len") and ( 

246 "min_value" in constraints or "max_value" in constraints 

247 ): 

248 new = copy.copy(self) 

249 new.min_size = max( 

250 self.min_size, constraints.get("min_value", self.min_size) 

251 ) 

252 new.max_size = min( 

253 self.max_size, constraints.get("max_value", self.max_size) 

254 ) 

255 # Unsatisfiable filters are easiest to understand without rewriting. 

256 if new.min_size > new.max_size: 

257 return SearchStrategy.filter(self, condition) 

258 # Recompute average size; this is cheaper than making it into a property. 

259 new.average_size = min( 

260 max(new.min_size * 2, new.min_size + 5), 

261 0.5 * (new.min_size + new.max_size), 

262 ) 

263 if pred is None: 

264 return new 

265 return SearchStrategy.filter(new, condition) 

266 

267 return SearchStrategy.filter(self, condition) 

268 

269 

270class UniqueListStrategy(ListStrategy[Ex]): 

271 def __init__( 

272 self, 

273 elements: SearchStrategy[Ex], 

274 min_size: int, 

275 max_size: float | int | None, 

276 # TODO: keys are guaranteed to be Hashable, not just Any, but this makes 

277 # other things harder to type 

278 keys: tuple[Callable[[Ex], Any], ...], 

279 tuple_suffixes: SearchStrategy[tuple[Ex, ...]] | None, 

280 ): 

281 super().__init__(elements, min_size, max_size) 

282 self.keys = keys 

283 self.tuple_suffixes = tuple_suffixes 

284 

285 def do_draw(self, data: ConjectureData) -> list[Ex]: 

286 if self.element_strategy.is_empty: 

287 assert self.min_size == 0 

288 return [] 

289 

290 elements = cu.many( 

291 data, 

292 min_size=self.min_size, 

293 max_size=self.max_size, 

294 average_size=self.average_size, 

295 ) 

296 seen_sets: tuple[set[Ex], ...] = tuple(set() for _ in self.keys) 

297 # actually list[Ex], but if self.tuple_suffixes is present then Ex is a 

298 # tuple[T, ...] because self.element_strategy is a TuplesStrategy, and 

299 # appending a concrete tuple to `result: list[Ex]` makes mypy unhappy 

300 # without knowing that Ex = tuple. 

301 result: list[Any] = [] 

302 

303 # We construct a filtered strategy here rather than using a check-and-reject 

304 # approach because some strategies have special logic for generation under a 

305 # filter, and FilteredStrategy can consolidate multiple filters. 

306 def not_yet_in_unique_list(val: Ex) -> bool: # type: ignore # covariant type param 

307 return all( 

308 key(val) not in seen 

309 for key, seen in zip(self.keys, seen_sets, strict=True) 

310 ) 

311 

312 filtered = FilteredStrategy( 

313 self.element_strategy, conditions=(not_yet_in_unique_list,) 

314 ) 

315 while elements.more(): 

316 value = filtered.do_filtered_draw(data) 

317 if value is filter_not_satisfied: 

318 elements.reject(f"Aborted test because unable to satisfy {filtered!r}") 

319 else: 

320 assert not isinstance(value, UniqueIdentifier) 

321 for key, seen in zip(self.keys, seen_sets, strict=True): 

322 seen.add(key(value)) 

323 if self.tuple_suffixes is not None: 

324 value = (value, *data.draw(self.tuple_suffixes)) # type: ignore 

325 result.append(value) 

326 assert self.max_size >= len(result) >= self.min_size 

327 return result 

328 

329 

330class UniqueSampledListStrategy(UniqueListStrategy): 

331 def do_draw(self, data: ConjectureData) -> list[Ex]: 

332 assert isinstance(self.element_strategy, SampledFromStrategy) 

333 

334 should_draw = cu.many( 

335 data, 

336 min_size=self.min_size, 

337 max_size=self.max_size, 

338 average_size=self.average_size, 

339 ) 

340 seen_sets: tuple[set[Ex], ...] = tuple(set() for _ in self.keys) 

341 result: list[Any] = [] 

342 

343 remaining = LazySequenceCopy(self.element_strategy.elements) 

344 

345 while remaining and should_draw.more(): 

346 j = data.draw_integer(0, len(remaining) - 1) 

347 value = self.element_strategy._transform(remaining.pop(j)) 

348 if value is not filter_not_satisfied and all( 

349 key(value) not in seen 

350 for key, seen in zip(self.keys, seen_sets, strict=True) 

351 ): 

352 for key, seen in zip(self.keys, seen_sets, strict=True): 

353 seen.add(key(value)) 

354 if self.tuple_suffixes is not None: 

355 value = (value, *data.draw(self.tuple_suffixes)) 

356 result.append(value) 

357 else: 

358 should_draw.reject( 

359 "UniqueSampledListStrategy filter not satisfied or value already seen" 

360 ) 

361 assert self.max_size >= len(result) >= self.min_size 

362 return result 

363 

364 

365class FixedDictStrategy(SearchStrategy[dict[Any, Any]]): 

366 """A strategy which produces dicts with a fixed set of keys, given a 

367 strategy for each of their equivalent values. 

368 

369 e.g. {'foo' : some_int_strategy} would generate dicts with the single 

370 key 'foo' mapping to some integer. 

371 """ 

372 

373 def __init__( 

374 self, 

375 mapping: dict[Any, SearchStrategy[Any]], 

376 *, 

377 optional: dict[Any, SearchStrategy[Any]] | None, 

378 ): 

379 super().__init__() 

380 dict_type = type(mapping) 

381 self.mapping = mapping 

382 keys = tuple(mapping.keys()) 

383 self.fixed = st.tuples(*[mapping[k] for k in keys]).map( 

384 lambda value: dict_type(zip(keys, value, strict=True)) 

385 ) 

386 self.optional = optional 

387 

388 def do_draw(self, data: ConjectureData) -> dict[Any, Any]: 

389 context = current_build_context() 

390 arg_labels: ArgLabelsT = {} 

391 value = type(self.mapping)() 

392 

393 for key, strategy in self.mapping.items(): 

394 with context.track_arg_label(str(key)) as arg_label: 

395 value[key] = data.draw(strategy) 

396 arg_labels |= arg_label 

397 

398 if self.optional is not None: 

399 remaining = [k for k, v in self.optional.items() if not v.is_empty] 

400 should_draw = cu.many( 

401 data, 

402 min_size=0, 

403 max_size=len(remaining), 

404 average_size=len(remaining) / 2, 

405 ) 

406 while should_draw.more(): 

407 j = data.draw_integer(0, len(remaining) - 1) 

408 remaining[-1], remaining[j] = remaining[j], remaining[-1] 

409 key = remaining.pop() 

410 with context.track_arg_label(str(key)) as arg_label: 

411 value[key] = data.draw(self.optional[key]) 

412 arg_labels |= arg_label 

413 

414 if arg_labels: 

415 context.known_object_printers[IDKey(value)].append( 

416 _fixeddict_pprinter(arg_labels, self.mapping) 

417 ) 

418 return value 

419 

420 def calc_is_empty(self, recur: RecurT) -> bool: 

421 return recur(self.fixed) 

422 

423 def __repr__(self) -> str: 

424 if self.optional is not None: 

425 return f"fixed_dictionaries({self.mapping!r}, optional={self.optional!r})" 

426 return f"fixed_dictionaries({self.mapping!r})"