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

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

174 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 Iterable 

14from typing import Any, Callable, Optional, Union, overload 

15 

16from hypothesis import strategies as st 

17from hypothesis.errors import InvalidArgument 

18from hypothesis.internal.conjecture import utils as cu 

19from hypothesis.internal.conjecture.data import ConjectureData 

20from hypothesis.internal.conjecture.engine import BUFFER_SIZE 

21from hypothesis.internal.conjecture.junkdrawer import LazySequenceCopy 

22from hypothesis.internal.conjecture.utils import combine_labels 

23from hypothesis.internal.filtering import get_integer_predicate_bounds 

24from hypothesis.internal.reflection import is_identity_function 

25from hypothesis.strategies._internal.strategies import ( 

26 T3, 

27 T4, 

28 T5, 

29 Ex, 

30 FilteredStrategy, 

31 RecurT, 

32 SampledFromStrategy, 

33 SearchStrategy, 

34 T, 

35 check_strategy, 

36 filter_not_satisfied, 

37) 

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

39from hypothesis.utils.conventions import UniqueIdentifier 

40 

41 

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

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

44 strategies for each of their elements.""" 

45 

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

47 super().__init__() 

48 self.element_strategies = tuple(strategies) 

49 

50 def do_validate(self) -> None: 

51 for s in self.element_strategies: 

52 s.validate() 

53 

54 def calc_label(self) -> int: 

55 return combine_labels( 

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

57 ) 

58 

59 def __repr__(self) -> str: 

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

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

62 

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

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

65 

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

67 return tuple(data.draw(e) for e in self.element_strategies) 

68 

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

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

71 

72 

73@overload 

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

75 ... 

76 

77 

78@overload 

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

80 ... 

81 

82 

83@overload 

84def tuples( 

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

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

87 ... 

88 

89 

90@overload 

91def tuples( 

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

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

94 ... 

95 

96 

97@overload 

98def tuples( 

99 __a1: SearchStrategy[Ex], 

100 __a2: SearchStrategy[T], 

101 __a3: SearchStrategy[T3], 

102 __a4: SearchStrategy[T4], 

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

104 ... 

105 

106 

107@overload 

108def tuples( 

109 __a1: SearchStrategy[Ex], 

110 __a2: SearchStrategy[T], 

111 __a3: SearchStrategy[T3], 

112 __a4: SearchStrategy[T4], 

113 __a5: SearchStrategy[T5], 

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

115 ... 

116 

117 

118@overload 

119def tuples( 

120 *args: SearchStrategy[Any], 

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

122 ... 

123 

124 

125@cacheable 

126@defines_strategy() 

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

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

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

130 

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

132 two with both values an integer. 

133 

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

135 """ 

136 for arg in args: 

137 check_strategy(arg) 

138 

139 return TupleStrategy(args) 

140 

141 

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

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

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

145 

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

147 

148 def __init__( 

149 self, 

150 elements: SearchStrategy[Ex], 

151 min_size: int = 0, 

152 max_size: Optional[Union[float, int]] = math.inf, 

153 ): 

154 super().__init__() 

155 self.min_size = min_size or 0 

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

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

158 self.average_size = min( 

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

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

161 ) 

162 self.element_strategy = elements 

163 if min_size > BUFFER_SIZE: 

164 raise InvalidArgument( 

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

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

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

168 "a HealthCheck error." 

169 ) 

170 

171 def calc_label(self) -> int: 

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

173 

174 def do_validate(self) -> None: 

175 self.element_strategy.validate() 

176 if self.is_empty: 

177 raise InvalidArgument( 

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

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

180 ) 

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

182 raise InvalidArgument( 

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

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

185 f"{self.element_strategy!r}" 

186 ) 

187 

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

189 if self.min_size == 0: 

190 return False 

191 return recur(self.element_strategy) 

192 

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

194 if self.element_strategy.is_empty: 

195 assert self.min_size == 0 

196 return [] 

197 

198 elements = cu.many( 

199 data, 

200 min_size=self.min_size, 

201 max_size=self.max_size, 

202 average_size=self.average_size, 

203 ) 

204 result = [] 

205 while elements.more(): 

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

207 return result 

208 

209 def __repr__(self) -> str: 

210 return ( 

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

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

213 ) 

214 

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

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

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

218 if self.min_size >= 1: 

219 return self 

220 new = copy.copy(self) 

221 new.min_size = 1 

222 return new 

223 

224 constraints, pred = get_integer_predicate_bounds(condition) 

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

226 "min_value" in constraints or "max_value" in constraints 

227 ): 

228 new = copy.copy(self) 

229 new.min_size = max( 

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

231 ) 

232 new.max_size = min( 

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

234 ) 

235 # Unsatisfiable filters are easiest to understand without rewriting. 

236 if new.min_size > new.max_size: 

237 return SearchStrategy.filter(self, condition) 

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

239 new.average_size = min( 

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

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

242 ) 

243 if pred is None: 

244 return new 

245 return SearchStrategy.filter(new, condition) 

246 

247 return SearchStrategy.filter(self, condition) 

248 

249 

250class UniqueListStrategy(ListStrategy[Ex]): 

251 def __init__( 

252 self, 

253 elements: SearchStrategy[Ex], 

254 min_size: int, 

255 max_size: Optional[Union[float, int]], 

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

257 # other things harder to type 

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

259 tuple_suffixes: Optional[SearchStrategy[tuple[Ex, ...]]], 

260 ): 

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

262 self.keys = keys 

263 self.tuple_suffixes = tuple_suffixes 

264 

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

266 if self.element_strategy.is_empty: 

267 assert self.min_size == 0 

268 return [] 

269 

270 elements = cu.many( 

271 data, 

272 min_size=self.min_size, 

273 max_size=self.max_size, 

274 average_size=self.average_size, 

275 ) 

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

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

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

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

280 # without knowing that Ex = tuple. 

281 result: list[Any] = [] 

282 

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

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

285 # filter, and FilteredStrategy can consolidate multiple filters. 

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

287 return all(key(val) not in seen for key, seen in zip(self.keys, seen_sets)) 

288 

289 filtered = FilteredStrategy( 

290 self.element_strategy, conditions=(not_yet_in_unique_list,) 

291 ) 

292 while elements.more(): 

293 value = filtered.do_filtered_draw(data) 

294 if value is filter_not_satisfied: 

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

296 else: 

297 assert not isinstance(value, UniqueIdentifier) 

298 for key, seen in zip(self.keys, seen_sets): 

299 seen.add(key(value)) 

300 if self.tuple_suffixes is not None: 

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

302 result.append(value) 

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

304 return result 

305 

306 

307class UniqueSampledListStrategy(UniqueListStrategy): 

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

309 assert isinstance(self.element_strategy, SampledFromStrategy) 

310 

311 should_draw = cu.many( 

312 data, 

313 min_size=self.min_size, 

314 max_size=self.max_size, 

315 average_size=self.average_size, 

316 ) 

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

318 result: list[Any] = [] 

319 

320 remaining = LazySequenceCopy(self.element_strategy.elements) 

321 

322 while remaining and should_draw.more(): 

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

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

325 if value is not filter_not_satisfied and all( 

326 key(value) not in seen for key, seen in zip(self.keys, seen_sets) 

327 ): 

328 for key, seen in zip(self.keys, seen_sets): 

329 seen.add(key(value)) 

330 if self.tuple_suffixes is not None: 

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

332 result.append(value) 

333 else: 

334 should_draw.reject( 

335 "UniqueSampledListStrategy filter not satisfied or value already seen" 

336 ) 

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

338 return result 

339 

340 

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

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

343 strategy for each of their equivalent values. 

344 

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

346 key 'foo' mapping to some integer. 

347 """ 

348 

349 def __init__( 

350 self, 

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

352 *, 

353 optional: Optional[dict[Any, SearchStrategy[Any]]], 

354 ): 

355 super().__init__() 

356 dict_type = type(mapping) 

357 self.mapping = mapping 

358 keys = tuple(mapping.keys()) 

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

360 lambda value: dict_type(zip(keys, value)) 

361 ) 

362 self.optional = optional 

363 

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

365 value = data.draw(self.fixed) 

366 if self.optional is None: 

367 return value 

368 

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

370 should_draw = cu.many( 

371 data, min_size=0, max_size=len(remaining), average_size=len(remaining) / 2 

372 ) 

373 while should_draw.more(): 

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

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

376 key = remaining.pop() 

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

378 return value 

379 

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

381 return recur(self.fixed) 

382 

383 def __repr__(self) -> str: 

384 if self.optional is not None: 

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

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