summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2026-05-25 20:01:12 +0300
committerrobot-piglet <[email protected]>2026-05-25 20:26:59 +0300
commit022e2dfce8e0ab77b6c50cd1f440fe9d63bb8403 (patch)
tree0956b4d1b54610b2d3365d6250fc378f49c91c92 /contrib/python
parent68351f08c7b172ecf6c235416133e750fc1cba1a (diff)
Intermediate changes
commit_hash:b27059cc559f64ac69592b370db5564662de0722
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/hypothesis/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py92
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/version.py2
-rw-r--r--contrib/python/hypothesis/py3/ya.make2
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)