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

173 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 RecurT, 

31 SampledFromStrategy, 

32 SearchStrategy, 

33 T, 

34 check_strategy, 

35 filter_not_satisfied, 

36) 

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

38from hypothesis.utils.conventions import UniqueIdentifier 

39 

40 

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

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

43 strategies for each of their elements.""" 

44 

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

46 super().__init__() 

47 self.element_strategies = tuple(strategies) 

48 

49 def do_validate(self) -> None: 

50 for s in self.element_strategies: 

51 s.validate() 

52 

53 def calc_label(self) -> int: 

54 return combine_labels( 

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

56 ) 

57 

58 def __repr__(self) -> str: 

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

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

61 

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

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

64 

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

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

67 

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

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

70 

71 

72@overload 

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

74 ... 

75 

76 

77@overload 

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

79 ... 

80 

81 

82@overload 

83def tuples( 

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

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

86 ... 

87 

88 

89@overload 

90def tuples( 

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

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

93 ... 

94 

95 

96@overload 

97def tuples( 

98 __a1: SearchStrategy[Ex], 

99 __a2: SearchStrategy[T], 

100 __a3: SearchStrategy[T3], 

101 __a4: SearchStrategy[T4], 

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

103 ... 

104 

105 

106@overload 

107def tuples( 

108 __a1: SearchStrategy[Ex], 

109 __a2: SearchStrategy[T], 

110 __a3: SearchStrategy[T3], 

111 __a4: SearchStrategy[T4], 

112 __a5: SearchStrategy[T5], 

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

114 ... 

115 

116 

117@overload 

118def tuples( 

119 *args: SearchStrategy[Any], 

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

121 ... 

122 

123 

124@cacheable 

125@defines_strategy() 

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

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

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

129 

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

131 two with both values an integer. 

132 

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

134 """ 

135 for arg in args: 

136 check_strategy(arg) 

137 

138 return TupleStrategy(args) 

139 

140 

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

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

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

144 

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

146 

147 def __init__( 

148 self, 

149 elements: SearchStrategy[Ex], 

150 min_size: int = 0, 

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

152 ): 

153 super().__init__() 

154 self.min_size = min_size or 0 

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

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

157 self.average_size = min( 

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

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

160 ) 

161 self.element_strategy = elements 

162 if min_size > BUFFER_SIZE: 

163 raise InvalidArgument( 

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

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

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

167 "a HealthCheck error." 

168 ) 

169 

170 def calc_label(self) -> int: 

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

172 

173 def do_validate(self) -> None: 

174 self.element_strategy.validate() 

175 if self.is_empty: 

176 raise InvalidArgument( 

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

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

179 ) 

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

181 raise InvalidArgument( 

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

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

184 f"{self.element_strategy!r}" 

185 ) 

186 

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

188 if self.min_size == 0: 

189 return False 

190 else: 

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 = self.element_strategy._filter_for_filtered_draw( 

290 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 dict_type = type(mapping) 

356 self.mapping = mapping 

357 keys = tuple(mapping.keys()) 

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

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

360 ) 

361 self.optional = optional 

362 

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

364 value = data.draw(self.fixed) 

365 if self.optional is None: 

366 return value 

367 

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

369 should_draw = cu.many( 

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

371 ) 

372 while should_draw.more(): 

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

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

375 key = remaining.pop() 

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

377 return value 

378 

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

380 return recur(self.fixed) 

381 

382 def __repr__(self) -> str: 

383 if self.optional is not None: 

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

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