diff options
| author | robot-piglet <[email protected]> | 2026-05-25 20:01:12 +0300 |
|---|---|---|
| committer | robot-piglet <[email protected]> | 2026-05-25 20:26:59 +0300 |
| commit | 022e2dfce8e0ab77b6c50cd1f440fe9d63bb8403 (patch) | |
| tree | 0956b4d1b54610b2d3365d6250fc378f49c91c92 /contrib/python | |
| parent | 68351f08c7b172ecf6c235416133e750fc1cba1a (diff) | |
Intermediate changes
commit_hash:b27059cc559f64ac69592b370db5564662de0722
Diffstat (limited to 'contrib/python')
4 files changed, 70 insertions, 28 deletions
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA index 3de2dcc4a98..adb3b9f3fb3 100644 --- a/contrib/python/hypothesis/py3/.dist-info/METADATA +++ b/contrib/python/hypothesis/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: hypothesis -Version: 6.152.4 +Version: 6.152.5 Summary: The property-based testing library for Python Author-email: "David R. MacIver and Zac Hatfield-Dodds" <[email protected]> License-Expression: MPL-2.0 diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py index f5d3fb5b2c0..15a4c1bad61 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py @@ -10,7 +10,7 @@ import math from collections import defaultdict -from collections.abc import Callable, Sequence +from collections.abc import Callable, Iterator, Sequence from dataclasses import dataclass from typing import ( TYPE_CHECKING, @@ -501,27 +501,36 @@ class Shrinker: ): continue + # Try a few targeted candidates before falling back to random sampling, + # so that simple cases like ``assert n1 == n2`` -- where the only + # passing value of ``n1`` is exactly ``n2``'s value -- aren't reported + # as freely-variable just because random sampling missed it. + candidates = list(self._explain_candidates(start, end)) + # Run our experiments n_same_failures = 0 note = "or any other generated value" # TODO: is 100 same-failures out of 500 attempts a good heuristic? - for n_attempt in range(500): # pragma: no branch + for n_attempt in range(500 + len(candidates)): # pragma: no branch # no-branch here because we don't coverage-test the abort-at-500 logic. - if n_attempt - 10 > n_same_failures * 5: + if n_attempt - 10 - len(candidates) > n_same_failures * 5: # stop early if we're seeing mostly invalid examples break # pragma: no cover - # replace start:end with random values - replacement = [] - for i in range(start, end): - node = nodes[i] - if not node.was_forced: - value = draw_choice( - node.type, node.constraints, random=self.random - ) - node = node.copy(with_value=value) - replacement.append(node.value) + if n_attempt < len(candidates): + replacement = list(candidates[n_attempt]) + else: + # replace start:end with random values + replacement = [] + for i in range(start, end): + node = nodes[i] + if not node.was_forced: + value = draw_choice( + node.type, node.constraints, random=self.random + ) + node = node.copy(with_value=value) + replacement.append(node.value) attempt = choices[:start] + tuple(replacement) + choices[end:] result = self.engine.cached_test_function(attempt, extend="full") @@ -610,6 +619,33 @@ class Shrinker: ) break + def _explain_candidates( + self, start: int, end: int + ) -> "Iterator[tuple[ChoiceT, ...]]": + """Yield deterministic candidate replacements for ``nodes[start:end]``. + + Random sampling alone misses cases like ``assert n1 == n2``, where the + only passing value of ``n1`` is exactly ``n2``'s value. We try + substituting values from each other arg slice with matching length and + types, which catches such comparisons. Invalid borrowed values just + produce an irrelevant test result the outer loop discards. + """ + nodes = self.nodes + target_types = tuple(nodes[i].type for i in range(start, end)) + current_keys = tuple(choice_key(nodes[i].value) for i in range(start, end)) + seen: set[tuple[Any, ...]] = {current_keys} + for s2, e2 in sorted(self.shrink_target.arg_slices): + if (s2, e2) == (start, end) or (e2 - s2) != (end - start): + continue + if tuple(nodes[s2 + j].type for j in range(end - start)) != target_types: + continue + borrowed = tuple(nodes[s2 + j].value for j in range(end - start)) + key = tuple(choice_key(v) for v in borrowed) + if key in seen: + continue + seen.add(key) + yield borrowed + def greedy_shrink(self) -> None: """Run a full set of greedy shrinks (that is, ones that will only ever move to a better target) and update shrink_target appropriately. @@ -1364,13 +1400,15 @@ class Shrinker: ) node2 = chooser.choose( self.nodes, - lambda node: can_choose_node(node) - # Note that it's fine for node2 to be trivial, because we're going to - # explicitly make it *not* trivial by adding to its value. - and not node.was_forced - # to avoid quadratic behavior, scan ahead only a small amount for - # the related node. - and node1.index < node.index <= node1.index + 4, + lambda node: ( + can_choose_node(node) + # Note that it's fine for node2 to be trivial, because we're going to + # explicitly make it *not* trivial by adding to its value. + and not node.was_forced + # to avoid quadratic behavior, scan ahead only a small amount for + # the related node. + and node1.index < node.index <= node1.index + 4 + ), ) m: int | float = node1.value @@ -1422,8 +1460,9 @@ class Shrinker: node2 = self.nodes[ chooser.choose( range(node1.index + 1, min(len(self.nodes), node1.index + 3 + 1)), - lambda i: self.nodes[i].type == "integer" - and not self.nodes[i].was_forced, + lambda i: ( + self.nodes[i].type == "integer" and not self.nodes[i].was_forced + ), ) ] @@ -1476,9 +1515,12 @@ class Shrinker: node2 = self.nodes[ chooser.choose( range(node1.index + 1, min(len(self.nodes), node1.index + 1 + 4)), - lambda i: self.nodes[i].type == "string" and not self.nodes[i].trivial - # select nodes which have at least one of the same character present - and set(node1.value) & set(self.nodes[i].value), + lambda i: ( + self.nodes[i].type == "string" + and not self.nodes[i].trivial + # select nodes which have at least one of the same character present + and set(node1.value) & set(self.nodes[i].value) + ), ) ] diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py index 3d9b6be8a60..bb420c28aea 100644 --- a/contrib/python/hypothesis/py3/hypothesis/version.py +++ b/contrib/python/hypothesis/py3/hypothesis/version.py @@ -8,5 +8,5 @@ # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. -__version_info__ = (6, 152, 4) +__version_info__ = (6, 152, 5) __version__ = ".".join(map(str, __version_info__)) diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make index bf698231af0..584f7300bd7 100644 --- a/contrib/python/hypothesis/py3/ya.make +++ b/contrib/python/hypothesis/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(6.152.4) +VERSION(6.152.5) LICENSE(MPL-2.0) |
