aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2024-03-24 10:10:09 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2024-03-24 10:18:54 +0300
commit1c202523203ef21da9a3060e70379cf5b20685d4 (patch)
tree5888a86acad713831cc6fdde57a59c91d27a9517
parent5600188f5ca9837fbb91c322889634e39946f9ac (diff)
downloadydb-1c202523203ef21da9a3060e70379cf5b20685d4.tar.gz
Intermediate changes
-rw-r--r--contrib/python/hypothesis/py3/.dist-info/METADATA7
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/_settings.py36
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/core.py10
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/numpy.py8
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py399
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py102
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py25
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py13
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py7
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/version.py2
-rw-r--r--contrib/python/hypothesis/py3/ya.make2
11 files changed, 516 insertions, 95 deletions
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA
index 9e2bf87fd9..9aaef70d52 100644
--- a/contrib/python/hypothesis/py3/.dist-info/METADATA
+++ b/contrib/python/hypothesis/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: hypothesis
-Version: 6.98.17
+Version: 6.99.0
Summary: A library for property-based testing
Home-page: https://hypothesis.works
Author: David R. MacIver and Zac Hatfield-Dodds
@@ -41,8 +41,10 @@ Requires-Dist: exceptiongroup >=1.0.0 ; python_version < "3.11"
Provides-Extra: all
Requires-Dist: black >=19.10b0 ; extra == 'all'
Requires-Dist: click >=7.0 ; extra == 'all'
+Requires-Dist: crosshair-tool >=0.0.50 ; extra == 'all'
Requires-Dist: django >=3.2 ; extra == 'all'
Requires-Dist: dpcontracts >=0.4 ; extra == 'all'
+Requires-Dist: hypothesis-crosshair >=0.0.1 ; extra == 'all'
Requires-Dist: lark >=0.10.1 ; extra == 'all'
Requires-Dist: libcst >=0.3.16 ; extra == 'all'
Requires-Dist: numpy >=1.17.3 ; extra == 'all'
@@ -60,6 +62,9 @@ Requires-Dist: black >=19.10b0 ; extra == 'cli'
Requires-Dist: rich >=9.0.0 ; extra == 'cli'
Provides-Extra: codemods
Requires-Dist: libcst >=0.3.16 ; extra == 'codemods'
+Provides-Extra: crosshair
+Requires-Dist: hypothesis-crosshair >=0.0.1 ; extra == 'crosshair'
+Requires-Dist: crosshair-tool >=0.0.50 ; extra == 'crosshair'
Provides-Extra: dateutil
Requires-Dist: python-dateutil >=1.4 ; extra == 'dateutil'
Provides-Extra: django
diff --git a/contrib/python/hypothesis/py3/hypothesis/_settings.py b/contrib/python/hypothesis/py3/hypothesis/_settings.py
index b11ffc54a4..061292e9bd 100644
--- a/contrib/python/hypothesis/py3/hypothesis/_settings.py
+++ b/contrib/python/hypothesis/py3/hypothesis/_settings.py
@@ -165,6 +165,7 @@ class settings(metaclass=settingsMeta):
suppress_health_check: Collection["HealthCheck"] = not_set, # type: ignore
deadline: Union[int, float, datetime.timedelta, None] = not_set, # type: ignore
print_blob: bool = not_set, # type: ignore
+ backend: str = not_set, # type: ignore
) -> None:
if parent is not None:
check_type(settings, parent, "parent")
@@ -289,7 +290,13 @@ class settings(metaclass=settingsMeta):
raise AttributeError("settings objects are immutable")
def __repr__(self):
- bits = sorted(f"{name}={getattr(self, name)!r}" for name in all_settings)
+ from hypothesis.internal.conjecture.data import AVAILABLE_PROVIDERS
+
+ bits = sorted(
+ f"{name}={getattr(self, name)!r}"
+ for name in all_settings
+ if (name != "backend" or len(AVAILABLE_PROVIDERS) > 1) # experimental
+ )
return "settings({})".format(", ".join(bits))
def show_changed(self):
@@ -706,6 +713,33 @@ The default is ``True`` if the ``CI`` or ``TF_BUILD`` env vars are set, ``False`
""",
)
+
+def _backend_validator(value):
+ from hypothesis.internal.conjecture.data import AVAILABLE_PROVIDERS
+
+ if value not in AVAILABLE_PROVIDERS:
+ if value == "crosshair": # pragma: no cover
+ install = '`pip install "hypothesis[crosshair]"` and try again.'
+ raise InvalidArgument(f"backend={value!r} is not available. {install}")
+ raise InvalidArgument(
+ f"backend={value!r} is not available - maybe you need to install a plugin?"
+ f"\n Installed backends: {sorted(AVAILABLE_PROVIDERS)!r}"
+ )
+ return value
+
+
+settings._define_setting(
+ "backend",
+ default="hypothesis",
+ show_default=False,
+ validator=_backend_validator,
+ description="""
+EXPERIMENTAL AND UNSTABLE - see :ref:`alternative-backends`.
+The importable name of a backend which Hypothesis should use to generate primitive
+types. We aim to support heuristic-random, solver-based, and fuzzing-based backends.
+""",
+)
+
settings.lock_further_definitions()
diff --git a/contrib/python/hypothesis/py3/hypothesis/core.py b/contrib/python/hypothesis/py3/hypothesis/core.py
index 4e4411fadf..402382c6aa 100644
--- a/contrib/python/hypothesis/py3/hypothesis/core.py
+++ b/contrib/python/hypothesis/py3/hypothesis/core.py
@@ -938,9 +938,13 @@ class StateForActualGivenExecution:
with local_settings(self.settings):
with deterministic_PRNG():
with BuildContext(data, is_final=is_final) as context:
- # Run the test function once, via the executor hook.
- # In most cases this will delegate straight to `run(data)`.
- result = self.test_runner(data, run)
+ # providers may throw in per_case_context_fn, and we'd like
+ # `result` to still be set in these cases.
+ result = None
+ with data.provider.per_test_case_context_manager():
+ # Run the test function once, via the executor hook.
+ # In most cases this will delegate straight to `run(data)`.
+ result = self.test_runner(data, run)
# If a failure was expected, it should have been raised already, so
# instead raise an appropriate diagnostic error.
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py b/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py
index c76edcf9a9..e8a9d3cb15 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/numpy.py
@@ -235,6 +235,12 @@ class ArrayStrategy(st.SearchStrategy):
self.unique = unique
self._check_elements = dtype.kind not in ("O", "V")
+ def __repr__(self):
+ return (
+ f"ArrayStrategy({self.element_strategy!r}, shape={self.shape}, "
+ f"dtype={self.dtype!r}, fill={self.fill!r}, unique={self.unique!r})"
+ )
+
def set_element(self, val, result, idx, *, fill=False):
try:
result[idx] = val
@@ -547,6 +553,8 @@ def arrays(
unwrapped = unwrap_strategies(elements)
if isinstance(unwrapped, MappedStrategy) and unwrapped.pack == dtype.type:
elements = unwrapped.mapped_strategy
+ if getattr(unwrapped, "force_has_reusable_values", False):
+ elements.force_has_reusable_values = True # type: ignore
if isinstance(shape, int):
shape = (shape,)
shape = tuple(shape)
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
index 10c4e17faf..486709edbb 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
@@ -8,6 +8,8 @@
# 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/.
+import abc
+import contextlib
import math
import time
from collections import defaultdict
@@ -956,7 +958,7 @@ BYTE_MASKS = [(1 << n) - 1 for n in range(8)]
BYTE_MASKS[0] = 255
-class PrimitiveProvider:
+class PrimitiveProvider(abc.ABC):
# This is the low-level interface which would also be implemented
# by e.g. CrossHair, by an Atheris-hypothesis integration, etc.
# We'd then build the structured tree handling, database and replay
@@ -964,16 +966,119 @@ class PrimitiveProvider:
#
# See https://github.com/HypothesisWorks/hypothesis/issues/3086
- def __init__(self, conjecturedata: "ConjectureData", /) -> None:
+ # How long a provider instance is used for. One of test_function or
+ # test_case. Defaults to test_function.
+ #
+ # If test_function, a single provider instance will be instantiated and used
+ # for the entirety of each test function. I.e., roughly one provider per
+ # @given annotation. This can be useful if you need to track state over many
+ # executions to a test function.
+ #
+ # This lifetime will cause None to be passed for the ConjectureData object
+ # in PrimitiveProvider.__init__, because that object is instantiated per
+ # test case.
+ #
+ # If test_case, a new provider instance will be instantiated and used each
+ # time hypothesis tries to generate a new input to the test function. This
+ # lifetime can access the passed ConjectureData object.
+ #
+ # Non-hypothesis providers probably want to set a lifetime of test_function.
+ lifetime = "test_function"
+
+ def __init__(self, conjecturedata: Optional["ConjectureData"], /) -> None:
self._cd = conjecturedata
- def draw_boolean(self, p: float = 0.5, *, forced: Optional[bool] = None) -> bool:
+ def post_test_case_hook(self, value):
+ # hook for providers to modify values returned by draw_* after a full
+ # test case concludes. Originally exposed for crosshair to reify its
+ # symbolic values into actual values.
+ # I'm not tied to this exact function name or design.
+ return value
+
+ def per_test_case_context_manager(self):
+ return contextlib.nullcontext()
+
+ @abc.abstractmethod
+ def draw_boolean(
+ self,
+ p: float = 0.5,
+ *,
+ forced: Optional[bool] = None,
+ fake_forced: bool = False,
+ ) -> bool:
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def draw_integer(
+ self,
+ min_value: Optional[int] = None,
+ max_value: Optional[int] = None,
+ *,
+ # weights are for choosing an element index from a bounded range
+ weights: Optional[Sequence[float]] = None,
+ shrink_towards: int = 0,
+ forced: Optional[int] = None,
+ fake_forced: bool = False,
+ ) -> int:
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def draw_float(
+ self,
+ *,
+ min_value: float = -math.inf,
+ max_value: float = math.inf,
+ allow_nan: bool = True,
+ smallest_nonzero_magnitude: float,
+ # TODO: consider supporting these float widths at the IR level in the
+ # future.
+ # width: Literal[16, 32, 64] = 64,
+ # exclude_min and exclude_max handled higher up,
+ forced: Optional[float] = None,
+ fake_forced: bool = False,
+ ) -> float:
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def draw_string(
+ self,
+ intervals: IntervalSet,
+ *,
+ min_size: int = 0,
+ max_size: Optional[int] = None,
+ forced: Optional[str] = None,
+ fake_forced: bool = False,
+ ) -> str:
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def draw_bytes(
+ self, size: int, *, forced: Optional[bytes] = None, fake_forced: bool = False
+ ) -> bytes:
+ raise NotImplementedError
+
+
+class HypothesisProvider(PrimitiveProvider):
+ lifetime = "test_case"
+
+ def __init__(self, conjecturedata: Optional["ConjectureData"], /):
+ assert conjecturedata is not None
+ super().__init__(conjecturedata)
+
+ def draw_boolean(
+ self,
+ p: float = 0.5,
+ *,
+ forced: Optional[bool] = None,
+ fake_forced: bool = False,
+ ) -> bool:
"""Return True with probability p (assuming a uniform generator),
shrinking towards False. If ``forced`` is set to a non-None value, this
will always return that value but will write choices appropriate to having
drawn that value randomly."""
# Note that this could also be implemented in terms of draw_integer().
+ assert self._cd is not None
# NB this function is vastly more complicated than it may seem reasonable
# for it to be. This is because it is used in a lot of places and it's
# important for it to shrink well, so it's worth the engineering effort.
@@ -1035,7 +1140,9 @@ class PrimitiveProvider:
partial = True
i = self._cd.draw_bits(
- bits, forced=None if forced is None else int(forced)
+ bits,
+ forced=None if forced is None else int(forced),
+ fake_forced=fake_forced,
)
# We always choose the region that causes us to repeat the loop as
@@ -1079,7 +1186,10 @@ class PrimitiveProvider:
weights: Optional[Sequence[float]] = None,
shrink_towards: int = 0,
forced: Optional[int] = None,
+ fake_forced: bool = False,
) -> int:
+ assert self._cd is not None
+
if min_value is not None:
shrink_towards = max(min_value, shrink_towards)
if max_value is not None:
@@ -1100,7 +1210,7 @@ class PrimitiveProvider:
forced_idx = forced - shrink_towards
else:
forced_idx = shrink_towards + gap - forced
- idx = sampler.sample(self._cd, forced=forced_idx)
+ idx = sampler.sample(self._cd, forced=forced_idx, fake_forced=fake_forced)
# For range -2..2, interpret idx = 0..4 as [0, 1, 2, -1, -2]
if idx <= gap:
@@ -1109,7 +1219,7 @@ class PrimitiveProvider:
return shrink_towards - (idx - gap)
if min_value is None and max_value is None:
- return self._draw_unbounded_integer(forced=forced)
+ return self._draw_unbounded_integer(forced=forced, fake_forced=fake_forced)
if min_value is None:
assert max_value is not None # make mypy happy
@@ -1117,7 +1227,8 @@ class PrimitiveProvider:
while max_value < probe:
self._cd.start_example(ONE_BOUND_INTEGERS_LABEL)
probe = shrink_towards + self._draw_unbounded_integer(
- forced=None if forced is None else forced - shrink_towards
+ forced=None if forced is None else forced - shrink_towards,
+ fake_forced=fake_forced,
)
self._cd.stop_example()
return probe
@@ -1128,7 +1239,8 @@ class PrimitiveProvider:
while probe < min_value:
self._cd.start_example(ONE_BOUND_INTEGERS_LABEL)
probe = shrink_towards + self._draw_unbounded_integer(
- forced=None if forced is None else forced - shrink_towards
+ forced=None if forced is None else forced - shrink_towards,
+ fake_forced=fake_forced,
)
self._cd.stop_example()
return probe
@@ -1138,6 +1250,7 @@ class PrimitiveProvider:
max_value,
center=shrink_towards,
forced=forced,
+ fake_forced=fake_forced,
)
def draw_float(
@@ -1152,6 +1265,7 @@ class PrimitiveProvider:
# width: Literal[16, 32, 64] = 64,
# exclude_min and exclude_max handled higher up,
forced: Optional[float] = None,
+ fake_forced: bool = False,
) -> float:
(
sampler,
@@ -1166,6 +1280,8 @@ class PrimitiveProvider:
smallest_nonzero_magnitude=smallest_nonzero_magnitude,
)
+ assert self._cd is not None
+
while True:
self._cd.start_example(FLOAT_STRATEGY_DO_DRAW_LABEL)
# If `forced in nasty_floats`, then `forced` was *probably*
@@ -1174,11 +1290,17 @@ class PrimitiveProvider:
# i == 0 is able to produce all possible floats, and the forcing
# logic is simpler if we assume this choice.
forced_i = None if forced is None else 0
- i = sampler.sample(self._cd, forced=forced_i) if sampler else 0
+ i = (
+ sampler.sample(self._cd, forced=forced_i, fake_forced=fake_forced)
+ if sampler
+ else 0
+ )
self._cd.start_example(DRAW_FLOAT_LABEL)
if i == 0:
result = self._draw_float(
- forced_sign_bit=forced_sign_bit, forced=forced
+ forced_sign_bit=forced_sign_bit,
+ forced=forced,
+ fake_forced=fake_forced,
)
if allow_nan and math.isnan(result):
clamped = result
@@ -1191,12 +1313,12 @@ class PrimitiveProvider:
if clamped != result and not (math.isnan(result) and allow_nan):
self._cd.stop_example()
self._cd.start_example(DRAW_FLOAT_LABEL)
- self._draw_float(forced=clamped)
+ self._draw_float(forced=clamped, fake_forced=fake_forced)
result = clamped
else:
result = nasty_floats[i - 1]
- self._draw_float(forced=result)
+ self._draw_float(forced=result, fake_forced=fake_forced)
self._cd.stop_example() # (DRAW_FLOAT_LABEL)
self._cd.stop_example() # (FLOAT_STRATEGY_DO_DRAW_LABEL)
@@ -1209,11 +1331,13 @@ class PrimitiveProvider:
min_size: int = 0,
max_size: Optional[int] = None,
forced: Optional[str] = None,
+ fake_forced: bool = False,
) -> str:
if max_size is None:
max_size = DRAW_STRING_DEFAULT_MAX_SIZE
assert forced is None or min_size <= len(forced) <= max_size
+ assert self._cd is not None
average_size = min(
max(min_size * 2, min_size + 5),
@@ -1227,6 +1351,7 @@ class PrimitiveProvider:
max_size=max_size,
average_size=average_size,
forced=None if forced is None else len(forced),
+ fake_forced=fake_forced,
observe=False,
)
while elements.more():
@@ -1237,47 +1362,74 @@ class PrimitiveProvider:
if len(intervals) > 256:
if self.draw_boolean(
- 0.2, forced=None if forced_i is None else forced_i > 255
+ 0.2,
+ forced=None if forced_i is None else forced_i > 255,
+ fake_forced=fake_forced,
):
i = self._draw_bounded_integer(
- 256, len(intervals) - 1, forced=forced_i
+ 256,
+ len(intervals) - 1,
+ forced=forced_i,
+ fake_forced=fake_forced,
)
else:
- i = self._draw_bounded_integer(0, 255, forced=forced_i)
+ i = self._draw_bounded_integer(
+ 0, 255, forced=forced_i, fake_forced=fake_forced
+ )
else:
- i = self._draw_bounded_integer(0, len(intervals) - 1, forced=forced_i)
+ i = self._draw_bounded_integer(
+ 0, len(intervals) - 1, forced=forced_i, fake_forced=fake_forced
+ )
chars.append(intervals.char_in_shrink_order(i))
return "".join(chars)
- def draw_bytes(self, size: int, *, forced: Optional[bytes] = None) -> bytes:
+ def draw_bytes(
+ self, size: int, *, forced: Optional[bytes] = None, fake_forced: bool = False
+ ) -> bytes:
forced_i = None
if forced is not None:
forced_i = int_from_bytes(forced)
size = len(forced)
- return self._cd.draw_bits(8 * size, forced=forced_i).to_bytes(size, "big")
+ assert self._cd is not None
+ return self._cd.draw_bits(
+ 8 * size, forced=forced_i, fake_forced=fake_forced
+ ).to_bytes(size, "big")
def _draw_float(
- self, forced_sign_bit: Optional[int] = None, *, forced: Optional[float] = None
+ self,
+ forced_sign_bit: Optional[int] = None,
+ *,
+ forced: Optional[float] = None,
+ fake_forced: bool = False,
) -> float:
"""
Helper for draw_float which draws a random 64-bit float.
"""
+ assert self._cd is not None
+
if forced is not None:
# sign_aware_lte(forced, -0.0) does not correctly handle the
# math.nan case here.
forced_sign_bit = math.copysign(1, forced) == -1
- is_negative = self._cd.draw_bits(1, forced=forced_sign_bit)
+ is_negative = self._cd.draw_bits(
+ 1, forced=forced_sign_bit, fake_forced=fake_forced
+ )
f = lex_to_float(
self._cd.draw_bits(
- 64, forced=None if forced is None else float_to_lex(abs(forced))
+ 64,
+ forced=None if forced is None else float_to_lex(abs(forced)),
+ fake_forced=fake_forced,
)
)
return -f if is_negative else f
- def _draw_unbounded_integer(self, *, forced: Optional[int] = None) -> int:
+ def _draw_unbounded_integer(
+ self, *, forced: Optional[int] = None, fake_forced: bool = False
+ ) -> int:
+ assert self._cd is not None
forced_i = None
if forced is not None:
# Using any bucket large enough to contain this integer would be a
@@ -1292,7 +1444,9 @@ class PrimitiveProvider:
size = min(size for size in INT_SIZES if bit_size <= size)
forced_i = INT_SIZES.index(size)
- size = INT_SIZES[INT_SIZES_SAMPLER.sample(self._cd, forced=forced_i)]
+ size = INT_SIZES[
+ INT_SIZES_SAMPLER.sample(self._cd, forced=forced_i, fake_forced=fake_forced)
+ ]
forced_r = None
if forced is not None:
@@ -1302,7 +1456,7 @@ class PrimitiveProvider:
forced_r = -forced_r
forced_r |= 1
- r = self._cd.draw_bits(size, forced=forced_r)
+ r = self._cd.draw_bits(size, forced=forced_r, fake_forced=fake_forced)
sign = r & 1
r >>= 1
if sign:
@@ -1316,9 +1470,11 @@ class PrimitiveProvider:
*,
center: Optional[int] = None,
forced: Optional[int] = None,
+ fake_forced: bool = False,
) -> int:
assert lower <= upper
assert forced is None or lower <= forced <= upper
+ assert self._cd is not None
if lower == upper:
# Write a value even when this is trivial so that when a bound depends
# on other values we don't suddenly disappear when the gap shrinks to
@@ -1337,7 +1493,9 @@ class PrimitiveProvider:
above = True
else:
force_above = None if forced is None else forced < center
- above = not self._cd.draw_bits(1, forced=force_above)
+ above = not self._cd.draw_bits(
+ 1, forced=force_above, fake_forced=fake_forced
+ )
if above:
gap = upper - center
@@ -1350,7 +1508,7 @@ class PrimitiveProvider:
probe = gap + 1
if bits > 24 and self.draw_boolean(
- 7 / 8, forced=None if forced is None else False
+ 7 / 8, forced=None if forced is None else False, fake_forced=fake_forced
):
# For large ranges, we combine the uniform random distribution from draw_bits
# with a weighting scheme with moderate chance. Cutoff at 2 ** 24 so that our
@@ -1361,7 +1519,9 @@ class PrimitiveProvider:
while probe > gap:
self._cd.start_example(INTEGER_RANGE_DRAW_LABEL)
probe = self._cd.draw_bits(
- bits, forced=None if forced is None else abs(forced - center)
+ bits,
+ forced=None if forced is None else abs(forced - center),
+ fake_forced=fake_forced,
)
self._cd.stop_example()
@@ -1476,21 +1636,59 @@ class PrimitiveProvider:
return (sampler, forced_sign_bit, neg_clamper, pos_clamper, nasty_floats)
+# The set of available `PrimitiveProvider`s, by name. Other libraries, such as
+# crosshair, can implement this interface and add themselves; at which point users
+# can configure which backend to use via settings. Keys are the name of the library,
+# which doubles as the backend= setting, and values are importable class names.
+#
+# NOTE: this is a temporary interface. We DO NOT promise to continue supporting it!
+# (but if you want to experiment and don't mind breakage, here you go)
+AVAILABLE_PROVIDERS = {
+ "hypothesis": "hypothesis.internal.conjecture.data.HypothesisProvider",
+}
+
+
class ConjectureData:
@classmethod
def for_buffer(
cls,
buffer: Union[List[int], bytes],
+ *,
observer: Optional[DataObserver] = None,
+ provider: Union[type, PrimitiveProvider] = HypothesisProvider,
) -> "ConjectureData":
- return cls(len(buffer), buffer, random=None, observer=observer)
+ return cls(
+ len(buffer), buffer, random=None, observer=observer, provider=provider
+ )
+
+ @classmethod
+ def for_ir_tree(
+ cls,
+ ir_tree_prefix: List[IRNode],
+ *,
+ observer: Optional[DataObserver] = None,
+ provider: Union[type, PrimitiveProvider] = HypothesisProvider,
+ ) -> "ConjectureData":
+ from hypothesis.internal.conjecture.engine import BUFFER_SIZE
+
+ return cls(
+ BUFFER_SIZE,
+ b"",
+ random=None,
+ ir_tree_prefix=ir_tree_prefix,
+ observer=observer,
+ provider=provider,
+ )
def __init__(
self,
max_length: int,
prefix: Union[List[int], bytes, bytearray],
+ *,
random: Optional[Random],
observer: Optional[DataObserver] = None,
+ provider: Union[type, PrimitiveProvider] = HypothesisProvider,
+ ir_tree_prefix: Optional[List[IRNode]] = None,
) -> None:
if observer is None:
observer = DataObserver()
@@ -1503,7 +1701,8 @@ class ConjectureData:
self.__prefix = bytes(prefix)
self.__random = random
- assert random is not None or max_length <= len(prefix)
+ if ir_tree_prefix is None:
+ assert random is not None or max_length <= len(prefix)
self.blocks = Blocks(self)
self.buffer: "Union[bytes, bytearray]" = bytearray()
@@ -1522,7 +1721,9 @@ class ConjectureData:
self._stateful_run_times: "DefaultDict[str, float]" = defaultdict(float)
self.max_depth = 0
self.has_discards = False
- self.provider = PrimitiveProvider(self)
+
+ self.provider = provider(self) if isinstance(provider, type) else provider
+ assert isinstance(self.provider, PrimitiveProvider)
self.__result: "Optional[ConjectureResult]" = None
@@ -1556,6 +1757,7 @@ class ConjectureData:
self.extra_information = ExtraInformation()
+ self.ir_tree_nodes = ir_tree_prefix
self.start_example(TOP_LABEL)
def __repr__(self):
@@ -1565,7 +1767,8 @@ class ConjectureData:
", frozen" if self.frozen else "",
)
- # A bit of explanation of the `observe` argument in our draw_* functions.
+ # A bit of explanation of the `observe` and `fake_forced` arguments in our
+ # draw_* functions.
#
# There are two types of draws: sub-ir and super-ir. For instance, some ir
# nodes use `many`, which in turn calls draw_boolean. But some strategies
@@ -1577,6 +1780,17 @@ class ConjectureData:
#
# `observe` formalizes this distinction. The draw will only be written to
# the DataTree if observe is True.
+ #
+ # `fake_forced` deals with a different problem. We use `forced=` to convert
+ # ir prefixes, which are potentially from other backends, into our backing
+ # bits representation. This works fine, except using `forced=` in this way
+ # also sets `was_forced=True` for all blocks, even those that weren't forced
+ # in the traditional way. The shrinker chokes on this due to thinking that
+ # nothing can be modified.
+ #
+ # Setting `fake_forced` to true says that yes, we want to force a particular
+ # value to be returned, but we don't want to treat that block as fixed for
+ # e.g. the shrinker.
def draw_integer(
self,
@@ -1587,6 +1801,7 @@ class ConjectureData:
weights: Optional[Sequence[float]] = None,
shrink_towards: int = 0,
forced: Optional[int] = None,
+ fake_forced: bool = False,
observe: bool = True,
) -> int:
# Validate arguments
@@ -1617,13 +1832,25 @@ class ConjectureData:
"shrink_towards": shrink_towards,
},
)
- value = self.provider.draw_integer(**kwargs, forced=forced)
+
+ if self.ir_tree_nodes is not None and observe:
+ node = self._pop_ir_tree_node("integer", kwargs)
+ assert isinstance(node.value, int)
+ forced = node.value
+ fake_forced = not node.was_forced
+
+ value = self.provider.draw_integer(
+ **kwargs, forced=forced, fake_forced=fake_forced
+ )
if observe:
self.observer.draw_integer(
- value, kwargs=kwargs, was_forced=forced is not None
+ value, kwargs=kwargs, was_forced=forced is not None and not fake_forced
)
self.__example_record.record_ir_draw(
- "integer", value, kwargs=kwargs, was_forced=forced is not None
+ "integer",
+ value,
+ kwargs=kwargs,
+ was_forced=forced is not None and not fake_forced,
)
return value
@@ -1639,6 +1866,7 @@ class ConjectureData:
# width: Literal[16, 32, 64] = 64,
# exclude_min and exclude_max handled higher up,
forced: Optional[float] = None,
+ fake_forced: bool = False,
observe: bool = True,
) -> float:
assert smallest_nonzero_magnitude > 0
@@ -1660,13 +1888,25 @@ class ConjectureData:
"smallest_nonzero_magnitude": smallest_nonzero_magnitude,
},
)
- value = self.provider.draw_float(**kwargs, forced=forced)
+
+ if self.ir_tree_nodes is not None and observe:
+ node = self._pop_ir_tree_node("float", kwargs)
+ assert isinstance(node.value, float)
+ forced = node.value
+ fake_forced = not node.was_forced
+
+ value = self.provider.draw_float(
+ **kwargs, forced=forced, fake_forced=fake_forced
+ )
if observe:
self.observer.draw_float(
- value, kwargs=kwargs, was_forced=forced is not None
+ value, kwargs=kwargs, was_forced=forced is not None and not fake_forced
)
self.__example_record.record_ir_draw(
- "float", value, kwargs=kwargs, was_forced=forced is not None
+ "float",
+ value,
+ kwargs=kwargs,
+ was_forced=forced is not None and not fake_forced,
)
return value
@@ -1677,6 +1917,7 @@ class ConjectureData:
min_size: int = 0,
max_size: Optional[int] = None,
forced: Optional[str] = None,
+ fake_forced: bool = False,
observe: bool = True,
) -> str:
assert forced is None or min_size <= len(forced)
@@ -1689,13 +1930,24 @@ class ConjectureData:
"max_size": max_size,
},
)
- value = self.provider.draw_string(**kwargs, forced=forced)
+ if self.ir_tree_nodes is not None and observe:
+ node = self._pop_ir_tree_node("string", kwargs)
+ assert isinstance(node.value, str)
+ forced = node.value
+ fake_forced = not node.was_forced
+
+ value = self.provider.draw_string(
+ **kwargs, forced=forced, fake_forced=fake_forced
+ )
if observe:
self.observer.draw_string(
- value, kwargs=kwargs, was_forced=forced is not None
+ value, kwargs=kwargs, was_forced=forced is not None and not fake_forced
)
self.__example_record.record_ir_draw(
- "string", value, kwargs=kwargs, was_forced=forced is not None
+ "string",
+ value,
+ kwargs=kwargs,
+ was_forced=forced is not None and not fake_forced,
)
return value
@@ -1705,24 +1957,42 @@ class ConjectureData:
size: int,
*,
forced: Optional[bytes] = None,
+ fake_forced: bool = False,
observe: bool = True,
) -> bytes:
assert forced is None or len(forced) == size
assert size >= 0
kwargs: BytesKWargs = self._pooled_kwargs("bytes", {"size": size})
- value = self.provider.draw_bytes(**kwargs, forced=forced)
+
+ if self.ir_tree_nodes is not None and observe:
+ node = self._pop_ir_tree_node("bytes", kwargs)
+ assert isinstance(node.value, bytes)
+ forced = node.value
+ fake_forced = not node.was_forced
+
+ value = self.provider.draw_bytes(
+ **kwargs, forced=forced, fake_forced=fake_forced
+ )
if observe:
self.observer.draw_bytes(
- value, kwargs=kwargs, was_forced=forced is not None
+ value, kwargs=kwargs, was_forced=forced is not None and not fake_forced
)
self.__example_record.record_ir_draw(
- "bytes", value, kwargs=kwargs, was_forced=forced is not None
+ "bytes",
+ value,
+ kwargs=kwargs,
+ was_forced=forced is not None and not fake_forced,
)
return value
def draw_boolean(
- self, p: float = 0.5, *, forced: Optional[bool] = None, observe: bool = True
+ self,
+ p: float = 0.5,
+ *,
+ forced: Optional[bool] = None,
+ observe: bool = True,
+ fake_forced: bool = False,
) -> bool:
# Internally, we treat probabilities lower than 1 / 2**64 as
# unconditionally false.
@@ -1735,13 +2005,25 @@ class ConjectureData:
assert p < (1 - 2 ** (-64))
kwargs: BooleanKWargs = self._pooled_kwargs("boolean", {"p": p})
- value = self.provider.draw_boolean(**kwargs, forced=forced)
+
+ if self.ir_tree_nodes is not None and observe:
+ node = self._pop_ir_tree_node("boolean", kwargs)
+ assert isinstance(node.value, bool)
+ forced = node.value
+ fake_forced = not node.was_forced
+
+ value = self.provider.draw_boolean(
+ **kwargs, forced=forced, fake_forced=fake_forced
+ )
if observe:
self.observer.draw_boolean(
- value, kwargs=kwargs, was_forced=forced is not None
+ value, kwargs=kwargs, was_forced=forced is not None and not fake_forced
)
self.__example_record.record_ir_draw(
- "boolean", value, kwargs=kwargs, was_forced=forced is not None
+ "boolean",
+ value,
+ kwargs=kwargs,
+ was_forced=forced is not None and not fake_forced,
)
return value
@@ -1765,6 +2047,14 @@ class ConjectureData:
POOLED_KWARGS_CACHE[key] = kwargs
return kwargs
+ def _pop_ir_tree_node(self, ir_type: IRTypeName, kwargs: IRKWargsType) -> IRNode:
+ assert self.ir_tree_nodes is not None
+ node = self.ir_tree_nodes.pop(0)
+ assert node.ir_type == ir_type
+ assert kwargs == node.kwargs
+
+ return node
+
def as_result(self) -> Union[ConjectureResult, _Overrun]:
"""Convert the result of running this test into
either an Overrun object or a ConjectureResult."""
@@ -1945,13 +2235,22 @@ class ConjectureData:
values: Sequence[T],
*,
forced: Optional[T] = None,
+ fake_forced: bool = False,
observe: bool = True,
) -> T:
forced_i = None if forced is None else values.index(forced)
- i = self.draw_integer(0, len(values) - 1, forced=forced_i, observe=observe)
+ i = self.draw_integer(
+ 0,
+ len(values) - 1,
+ forced=forced_i,
+ fake_forced=fake_forced,
+ observe=observe,
+ )
return values[i]
- def draw_bits(self, n: int, *, forced: Optional[int] = None) -> int:
+ def draw_bits(
+ self, n: int, *, forced: Optional[int] = None, fake_forced: bool = False
+ ) -> int:
"""Return an ``n``-bit integer from the underlying source of
bytes. If ``forced`` is set to an integer will instead
ignore the underlying source and simulate a draw as if it had
@@ -1993,7 +2292,7 @@ class ConjectureData:
self.buffer.extend(buf)
self.index = len(self.buffer)
- if forced is not None:
+ if forced is not None and not fake_forced:
self.forced_indices.update(range(initial, self.index))
self.blocks.add_endpoint(self.index)
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
index 2a011a8b11..b0cf812298 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
@@ -8,6 +8,7 @@
# 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/.
+import importlib
import math
import time
from collections import defaultdict
@@ -15,26 +16,26 @@ from contextlib import contextmanager
from datetime import timedelta
from enum import Enum
from random import Random, getrandbits
+from typing import Union
import attr
from hypothesis import HealthCheck, Phase, Verbosity, settings as Settings
from hypothesis._settings import local_settings
-from hypothesis.errors import StopTest
+from hypothesis.errors import InvalidArgument, StopTest
from hypothesis.internal.cache import LRUReusedCache
from hypothesis.internal.compat import ceil, int_from_bytes
from hypothesis.internal.conjecture.data import (
+ AVAILABLE_PROVIDERS,
ConjectureData,
ConjectureResult,
DataObserver,
+ HypothesisProvider,
Overrun,
+ PrimitiveProvider,
Status,
)
-from hypothesis.internal.conjecture.datatree import (
- DataTree,
- PreviouslyUnseenBehaviour,
- TreeRecordingObserver,
-)
+from hypothesis.internal.conjecture.datatree import DataTree, PreviouslyUnseenBehaviour
from hypothesis.internal.conjecture.junkdrawer import clamp, ensure_free_stackframes
from hypothesis.internal.conjecture.pareto import NO_SCORE, ParetoFront, ParetoOptimiser
from hypothesis.internal.conjecture.shrinker import Shrinker, sort_key
@@ -114,6 +115,20 @@ class RunIsComplete(Exception):
pass
+def _get_provider(backend: str) -> Union[type, PrimitiveProvider]:
+ mname, cname = AVAILABLE_PROVIDERS[backend].rsplit(".", 1)
+ provider_cls = getattr(importlib.import_module(mname), cname)
+ if provider_cls.lifetime == "test_function":
+ return provider_cls(None)
+ elif provider_cls.lifetime == "test_case":
+ return provider_cls
+ else:
+ raise InvalidArgument(
+ f"invalid lifetime {provider_cls.lifetime} for provider {provider_cls.__name__}. "
+ "Expected one of 'test_function', 'test_case'."
+ )
+
+
class ConjectureRunner:
def __init__(
self,
@@ -151,6 +166,8 @@ class ConjectureRunner:
self.tree = DataTree()
+ self.provider = _get_provider(self.settings.backend)
+
self.best_observed_targets = defaultdict(lambda: NO_SCORE)
self.best_examples_of_observed_targets = {}
@@ -171,6 +188,7 @@ class ConjectureRunner:
self.__data_cache = LRUReusedCache(CACHE_SIZE)
self.__pending_call_explanation = None
+ self._switch_to_hypothesis_provider = False
def explain_next_call_as(self, explanation):
self.__pending_call_explanation = explanation
@@ -198,7 +216,7 @@ class ConjectureRunner:
return Phase.target in self.settings.phases
def __tree_is_exhausted(self):
- return self.tree.is_exhausted
+ return self.tree.is_exhausted and self.settings.backend == "hypothesis"
def __stoppable_test_function(self, data):
"""Run ``self._test_function``, but convert a ``StopTest`` exception
@@ -226,7 +244,6 @@ class ConjectureRunner:
self.debug(self.__pending_call_explanation)
self.__pending_call_explanation = None
- assert isinstance(data.observer, TreeRecordingObserver)
self.call_count += 1
interrupted = False
@@ -284,6 +301,21 @@ class ConjectureRunner:
self.valid_examples += 1
if data.status == Status.INTERESTING:
+ if self.settings.backend != "hypothesis":
+ for node in data.examples.ir_tree_nodes:
+ value = data.provider.post_test_case_hook(node.value)
+ # require providers to return something valid here.
+ assert (
+ value is not None
+ ), "providers must return a non-null value from post_test_case_hook"
+ node.value = value
+
+ # drive the ir tree through the test function to convert it
+ # to a buffer
+ data = ConjectureData.for_ir_tree(data.examples.ir_tree_nodes)
+ self.__stoppable_test_function(data)
+ self.__data_cache[data.buffer] = data.as_result()
+
key = data.interesting_origin
changed = False
try:
@@ -702,6 +734,15 @@ class ConjectureRunner:
ran_optimisations = False
while self.should_generate_more():
+ # Unfortunately generate_novel_prefix still operates in terms of
+ # a buffer and uses HypothesisProvider as its backing provider,
+ # not whatever is specified by the backend. We can improve this
+ # once more things are on the ir.
+ if self.settings.backend != "hypothesis":
+ data = self.new_conjecture_data(prefix=b"", max_length=BUFFER_SIZE)
+ self.test_function(data)
+ continue
+
self._current_phase = "generate"
prefix = self.generate_novel_prefix()
assert len(prefix) <= BUFFER_SIZE
@@ -905,26 +946,40 @@ class ConjectureRunner:
ParetoOptimiser(self).run()
def _run(self):
+ # have to use the primitive provider to interpret database bits...
+ self._switch_to_hypothesis_provider = True
with self._log_phase_statistics("reuse"):
self.reuse_existing_examples()
+ # ...but we should use the supplied provider when generating...
+ self._switch_to_hypothesis_provider = False
with self._log_phase_statistics("generate"):
self.generate_new_examples()
# We normally run the targeting phase mixed in with the generate phase,
# but if we've been asked to run it but not generation then we have to
- # run it explciitly on its own here.
+ # run it explicitly on its own here.
if Phase.generate not in self.settings.phases:
self._current_phase = "target"
self.optimise_targets()
+ # ...and back to the primitive provider when shrinking.
+ self._switch_to_hypothesis_provider = True
with self._log_phase_statistics("shrink"):
self.shrink_interesting_examples()
self.exit_with(ExitReason.finished)
def new_conjecture_data(self, prefix, max_length=BUFFER_SIZE, observer=None):
+ provider = (
+ HypothesisProvider if self._switch_to_hypothesis_provider else self.provider
+ )
+ observer = observer or self.tree.new_observer()
+ if self.settings.backend != "hypothesis":
+ observer = DataObserver()
+
return ConjectureData(
prefix=prefix,
max_length=max_length,
random=self.random,
- observer=observer or self.tree.new_observer(),
+ observer=observer,
+ provider=provider,
)
def new_conjecture_data_for_buffer(self, buffer):
@@ -1066,20 +1121,21 @@ class ConjectureRunner:
prefix=buffer, max_length=max_length, observer=observer
)
- try:
- self.tree.simulate_test_function(dummy_data)
- except PreviouslyUnseenBehaviour:
- pass
- else:
- if dummy_data.status > Status.OVERRUN:
- dummy_data.freeze()
- try:
- return self.__data_cache[dummy_data.buffer]
- except KeyError:
- pass
+ if self.settings.backend == "hypothesis":
+ try:
+ self.tree.simulate_test_function(dummy_data)
+ except PreviouslyUnseenBehaviour:
+ pass
else:
- self.__data_cache[buffer] = Overrun
- return Overrun
+ if dummy_data.status > Status.OVERRUN:
+ dummy_data.freeze()
+ try:
+ return self.__data_cache[dummy_data.buffer]
+ except KeyError:
+ pass
+ else:
+ self.__data_cache[buffer] = Overrun
+ return Overrun
# We didn't find a match in the tree, so we need to run the test
# function normally. Note that test_function will automatically
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py
index 5e77437a78..509c03ef71 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/utils.py
@@ -166,7 +166,13 @@ class Sampler:
self.table.append((base, alternate, alternate_chance))
self.table.sort()
- def sample(self, data: "ConjectureData", forced: Optional[int] = None) -> int:
+ def sample(
+ self,
+ data: "ConjectureData",
+ *,
+ forced: Optional[int] = None,
+ fake_forced: bool = False,
+ ) -> int:
data.start_example(SAMPLE_IN_SAMPLER_LABEL)
forced_choice = ( # pragma: no branch # https://github.com/nedbat/coveragepy/issues/1617
None
@@ -178,7 +184,10 @@ class Sampler:
)
)
base, alternate, alternate_chance = data.choice(
- self.table, forced=forced_choice, observe=self.observe
+ self.table,
+ forced=forced_choice,
+ fake_forced=fake_forced,
+ observe=self.observe,
)
forced_use_alternate = None
if forced is not None:
@@ -189,7 +198,10 @@ class Sampler:
assert forced == base or forced_use_alternate
use_alternate = data.draw_boolean(
- alternate_chance, forced=forced_use_alternate, observe=self.observe
+ alternate_chance,
+ forced=forced_use_alternate,
+ fake_forced=fake_forced,
+ observe=self.observe,
)
data.stop_example()
if use_alternate:
@@ -224,6 +236,7 @@ class many:
average_size: Union[int, float],
*,
forced: Optional[int] = None,
+ fake_forced: bool = False,
observe: bool = True,
) -> None:
assert 0 <= min_size <= average_size <= max_size
@@ -232,6 +245,7 @@ class many:
self.max_size = max_size
self.data = data
self.forced_size = forced
+ self.fake_forced = fake_forced
self.p_continue = _calc_p_continue(average_size - min_size, max_size - min_size)
self.count = 0
self.rejections = 0
@@ -267,7 +281,10 @@ class many:
elif self.forced_size is not None:
forced_result = self.count < self.forced_size
should_continue = self.data.draw_boolean(
- self.p_continue, forced=forced_result, observe=self.observe
+ self.p_continue,
+ forced=forced_result,
+ fake_forced=self.fake_forced,
+ observe=self.observe,
)
if should_continue:
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py
index 75de4a82ec..f37fd0fcae 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/collections.py
@@ -143,15 +143,18 @@ class ListStrategy(SearchStrategy):
self.min_size = min_size or 0
self.max_size = max_size if max_size is not None else float("inf")
assert 0 <= self.min_size <= self.max_size
- if min_size > BUFFER_SIZE:
- raise InvalidArgument(
- f"min_size={min_size:_d} is larger than Hypothesis is designed to handle"
- )
self.average_size = min(
max(self.min_size * 2, self.min_size + 5),
0.5 * (self.min_size + self.max_size),
)
self.element_strategy = elements
+ if min_size > BUFFER_SIZE:
+ raise InvalidArgument(
+ f"{self!r} can never generate an example, because min_size is larger "
+ "than Hypothesis supports. Including it is at best slowing down your "
+ "tests for no benefit; at worst making them fail (maybe flakily) with "
+ "a HealthCheck error."
+ )
def calc_label(self):
return combine_labels(self.class_label, self.element_strategy.label)
@@ -193,7 +196,7 @@ class ListStrategy(SearchStrategy):
return result
def __repr__(self):
- return "{}({!r}, min_size={!r}, max_size={!r})".format(
+ return "{}({!r}, min_size={:_}, max_size={:_})".format(
self.__class__.__name__, self.element_strategy, self.min_size, self.max_size
)
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py
index 46d4005cdb..83b0e6b059 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/strategies.py
@@ -28,7 +28,7 @@ from typing import (
)
from hypothesis._settings import HealthCheck, Phase, Verbosity, settings
-from hypothesis.control import _current_build_context, assume
+from hypothesis.control import _current_build_context
from hypothesis.errors import (
HypothesisException,
HypothesisWarning,
@@ -1002,7 +1002,6 @@ class FilteredStrategy(SearchStrategy[Ex]):
def do_filtered_draw(self, data):
for i in range(3):
- start_index = data.index
data.start_example(FILTERED_SEARCH_STRATEGY_DO_DRAW_LABEL)
value = data.draw(self.filtered_strategy)
if self.condition(value):
@@ -1012,10 +1011,6 @@ class FilteredStrategy(SearchStrategy[Ex]):
data.stop_example(discard=True)
if i == 0:
data.events[f"Retried draw from {self!r} to satisfy filter"] = ""
- # This is to guard against the case where we consume no data.
- # As long as we consume data, we'll eventually pass or raise.
- # But if we don't this could be an infinite loop.
- assume(data.index > start_index)
return filter_not_satisfied
diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py
index 22bdf94369..aa19764f61 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, 98, 17)
+__version_info__ = (6, 99, 0)
__version__ = ".".join(map(str, __version_info__))
diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make
index 5658273209..3ed4fc14fc 100644
--- a/contrib/python/hypothesis/py3/ya.make
+++ b/contrib/python/hypothesis/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(6.98.17)
+VERSION(6.99.0)
LICENSE(MPL-2.0)