summaryrefslogtreecommitdiffstats
path: root/contrib/python/hypothesis/py3
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2024-05-27 08:08:23 +0300
committerrobot-piglet <[email protected]>2024-05-27 08:17:40 +0300
commit0d926056f6147cc00d803b7647be7afff48b8fec (patch)
treeb50ed201039faa8b726b2a63d0879a7c299defd6 /contrib/python/hypothesis/py3
parentb781230b3ce25da73dcb68e913313a2e5328352a (diff)
Intermediate changes
Diffstat (limited to 'contrib/python/hypothesis/py3')
-rw-r--r--contrib/python/hypothesis/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/django/_impl.py1
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py1
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/compat.py40
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py2
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py43
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py357
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py12
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py19
-rw-r--r--contrib/python/hypothesis/py3/hypothesis/version.py2
-rw-r--r--contrib/python/hypothesis/py3/ya.make2
11 files changed, 334 insertions, 147 deletions
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA
index e9db66d500c..76ffe52c503 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.100.6
+Version: 6.101.0
Summary: A library for property-based testing
Home-page: https://hypothesis.works
Author: David R. MacIver and Zac Hatfield-Dodds
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/django/_impl.py b/contrib/python/hypothesis/py3/hypothesis/extra/django/_impl.py
index fb368fb566e..5a7ab8f0e3a 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/django/_impl.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/django/_impl.py
@@ -104,6 +104,7 @@ def from_model(
if (
name not in field_strategies
and not field.auto_created
+ and not isinstance(field, dm.AutoField)
and field.default is dm.fields.NOT_PROVIDED
):
field_strategies[name] = from_field(field)
diff --git a/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py b/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py
index dbef1e04a29..16ef90fc63e 100644
--- a/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py
+++ b/contrib/python/hypothesis/py3/hypothesis/extra/pandas/impl.py
@@ -534,7 +534,6 @@ def data_frames(
def rows_only(draw):
index = draw(index_strategy)
- @check_function
def row():
result = draw(rows)
check_type(abc.Iterable, result, "draw(row)")
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/compat.py b/contrib/python/hypothesis/py3/hypothesis/internal/compat.py
index 105ff8eb13c..4a3a94a0844 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/compat.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/compat.py
@@ -17,7 +17,7 @@ import sys
import sysconfig
import typing
from functools import partial
-from typing import Any, ForwardRef, List, Optional, get_args
+from typing import Any, ForwardRef, List, Optional, TypedDict as TypedDict, get_args
try:
BaseExceptionGroup = BaseExceptionGroup
@@ -28,18 +28,52 @@ except NameError:
ExceptionGroup as ExceptionGroup,
)
if typing.TYPE_CHECKING: # pragma: no cover
- from typing_extensions import Concatenate as Concatenate, ParamSpec as ParamSpec
+ from typing_extensions import (
+ Concatenate as Concatenate,
+ NotRequired as NotRequired,
+ ParamSpec as ParamSpec,
+ TypeAlias as TypeAlias,
+ TypedDict as TypedDict,
+ override as override,
+ )
else:
+ # In order to use NotRequired, we need the version of TypedDict included in Python 3.11+.
+ if sys.version_info[:2] >= (3, 11):
+ from typing import NotRequired as NotRequired, TypedDict as TypedDict
+ else:
+ try:
+ from typing_extensions import (
+ NotRequired as NotRequired,
+ TypedDict as TypedDict,
+ )
+ except ImportError:
+ # We can use the old TypedDict from Python 3.8+ at runtime.
+ class NotRequired:
+ """A runtime placeholder for the NotRequired type, which is not available in Python <3.11."""
+
+ def __class_getitem__(cls, item):
+ return cls
+
try:
- from typing import Concatenate as Concatenate, ParamSpec as ParamSpec
+ from typing import (
+ Concatenate as Concatenate,
+ ParamSpec as ParamSpec,
+ TypeAlias as TypeAlias,
+ override as override,
+ )
except ImportError:
try:
from typing_extensions import (
Concatenate as Concatenate,
ParamSpec as ParamSpec,
+ TypeAlias as TypeAlias,
+ override as override,
)
except ImportError:
Concatenate, ParamSpec = None, None
+ TypeAlias = None
+ override = lambda f: f
+
PYPY = platform.python_implementation() == "PyPy"
GRAALPY = platform.python_implementation() == "GraalVM"
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
index 99b00ebbca7..6ac7f2a70f5 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py
@@ -98,7 +98,7 @@ INTEGER_WEIGHTED_DISTRIBUTION = calc_label_from_name(
InterestingOrigin = Tuple[
Type[BaseException], str, int, Tuple[Any, ...], Tuple[Tuple[Any, ...], ...]
]
-TargetObservations = Dict[Optional[str], Union[int, float]]
+TargetObservations = Dict[str, Union[int, float]]
T = TypeVar("T")
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py
index c4aded558c7..85b78ec54de 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py
@@ -265,20 +265,37 @@ def all_children(ir_type, kwargs):
min_value = kwargs["min_value"]
max_value = kwargs["max_value"]
weights = kwargs["weights"]
- # it's a bit annoying (but completely feasible) to implement the cases
- # other than "both sides bounded" here. We haven't needed to yet because
- # in practice we don't struggle with unbounded integer generation.
- assert min_value is not None
- assert max_value is not None
-
- if weights is None:
- yield from range(min_value, max_value + 1)
+
+ if min_value is None and max_value is None:
+ # full 128 bit range.
+ yield from range(-(2**127) + 1, 2**127 - 1)
+
+ elif min_value is not None and max_value is not None:
+ if weights is None:
+ yield from range(min_value, max_value + 1)
+ else:
+ # skip any values with a corresponding weight of 0 (can never be drawn).
+ for weight, n in zip(weights, range(min_value, max_value + 1)):
+ if weight == 0:
+ continue
+ yield n
else:
- # skip any values with a corresponding weight of 0 (can never be drawn).
- for weight, n in zip(weights, range(min_value, max_value + 1)):
- if weight == 0:
- continue
- yield n
+ # hard case: only one bound was specified. Here we probe either upwards
+ # or downwards with our full 128 bit generation, but only half of these
+ # (plus one for the case of generating zero) result in a probe in the
+ # direction we want. ((2**128 - 1) // 2) + 1 == a range of 2 ** 127.
+ #
+ # strictly speaking, I think this is not actually true: if
+ # max_value > shrink_towards then our range is ((-2**127) + 1, max_value),
+ # and it only narrows when max_value < shrink_towards. But it
+ # really doesn't matter for this case because (even half) unbounded
+ # integers generation is hit extremely rarely.
+ assert (min_value is None) ^ (max_value is None)
+ if min_value is None:
+ yield from range(max_value - (2**127) + 1, max_value)
+ else:
+ assert max_value is None
+ yield from range(min_value, min_value + (2**127) - 1)
if ir_type == "boolean":
p = kwargs["p"]
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
index d154fdc28e5..070d460defc 100644
--- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
+++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py
@@ -16,46 +16,79 @@ from contextlib import contextmanager
from datetime import timedelta
from enum import Enum
from random import Random, getrandbits
-from typing import Union
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Final,
+ FrozenSet,
+ Generator,
+ List,
+ Literal,
+ NoReturn,
+ Optional,
+ Set,
+ Tuple,
+ Union,
+ overload,
+)
import attr
from hypothesis import HealthCheck, Phase, Verbosity, settings as Settings
from hypothesis._settings import local_settings
+from hypothesis.database import ExampleDatabase
from hypothesis.errors import InvalidArgument, StopTest
from hypothesis.internal.cache import LRUReusedCache
-from hypothesis.internal.compat import ceil, int_from_bytes
+from hypothesis.internal.compat import (
+ NotRequired,
+ TypeAlias,
+ TypedDict,
+ ceil,
+ int_from_bytes,
+ override,
+)
from hypothesis.internal.conjecture.data import (
AVAILABLE_PROVIDERS,
ConjectureData,
ConjectureResult,
DataObserver,
+ Example,
HypothesisProvider,
+ InterestingOrigin,
+ IRNode,
Overrun,
PrimitiveProvider,
Status,
+ _Overrun,
ir_kwargs_key,
ir_value_key,
)
-from hypothesis.internal.conjecture.datatree import DataTree, PreviouslyUnseenBehaviour
+from hypothesis.internal.conjecture.datatree import (
+ DataTree,
+ PreviouslyUnseenBehaviour,
+ TreeRecordingObserver,
+)
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
from hypothesis.internal.healthcheck import fail_health_check
from hypothesis.reporting import base_report, report
-MAX_SHRINKS = 500
-CACHE_SIZE = 10000
-MUTATION_POOL_SIZE = 100
-MIN_TEST_CALLS = 10
-BUFFER_SIZE = 8 * 1024
+MAX_SHRINKS: Final[int] = 500
+CACHE_SIZE: Final[int] = 10000
+MUTATION_POOL_SIZE: Final[int] = 100
+MIN_TEST_CALLS: Final[int] = 10
+BUFFER_SIZE: Final[int] = 8 * 1024
# If the shrinking phase takes more than five minutes, abort it early and print
# a warning. Many CI systems will kill a build after around ten minutes with
# no output, and appearing to hang isn't great for interactive use either -
# showing partially-shrunk examples is better than quitting with no examples!
# (but make it monkeypatchable, for the rare users who need to keep on shrinking)
-MAX_SHRINKING_SECONDS = 300
+MAX_SHRINKING_SECONDS: Final[int] = 300
+
+Ls: TypeAlias = List["Ls | int"]
@attr.s
@@ -63,15 +96,15 @@ class HealthCheckState:
valid_examples: int = attr.ib(default=0)
invalid_examples: int = attr.ib(default=0)
overrun_examples: int = attr.ib(default=0)
- draw_times: "defaultdict[str, list[float]]" = attr.ib(
+ draw_times: "defaultdict[str, List[float]]" = attr.ib(
factory=lambda: defaultdict(list)
)
@property
- def total_draw_time(self):
+ def total_draw_time(self) -> float:
return math.fsum(sum(self.draw_times.values(), start=[]))
- def timing_report(self):
+ def timing_report(self) -> str:
"""Return a terminal report describing what was slow."""
if not self.draw_times:
return ""
@@ -109,7 +142,7 @@ class ExitReason(Enum):
flaky = "test was flaky"
very_slow_shrinking = "shrinking was very slow"
- def describe(self, settings):
+ def describe(self, settings: Settings) -> str:
return self.value.format(s=settings)
@@ -131,57 +164,90 @@ def _get_provider(backend: str) -> Union[type, PrimitiveProvider]:
)
+class CallStats(TypedDict):
+ status: str
+ runtime: float
+ drawtime: float
+ events: List[str]
+
+
+PhaseStatistics = TypedDict(
+ "PhaseStatistics",
+ {
+ "duration-seconds": float,
+ "test-cases": List[CallStats],
+ "distinct-failures": int,
+ "shrinks-successful": int,
+ },
+)
+StatisticsDict = TypedDict(
+ "StatisticsDict",
+ {
+ "generate-phase": NotRequired[PhaseStatistics],
+ "reuse-phase": NotRequired[PhaseStatistics],
+ "shrink-phase": NotRequired[PhaseStatistics],
+ "stopped-because": NotRequired[str],
+ "targets": NotRequired[Dict[str, float]],
+ },
+)
+
+
class ConjectureRunner:
def __init__(
self,
- test_function,
+ test_function: Callable[[ConjectureData], None],
*,
- settings=None,
- random=None,
- database_key=None,
- ignore_limits=False,
- ):
- self._test_function = test_function
- self.settings = settings or Settings()
- self.shrinks = 0
- self.finish_shrinking_deadline = None
- self.call_count = 0
- self.valid_examples = 0
- self.random = random or Random(getrandbits(128))
- self.database_key = database_key
- self.ignore_limits = ignore_limits
+ settings: Optional[Settings] = None,
+ random: Optional[Random] = None,
+ database_key: Optional[bytes] = None,
+ ignore_limits: bool = False,
+ ) -> None:
+ self._test_function: Callable[[ConjectureData], None] = test_function
+ self.settings: Settings = settings or Settings()
+ self.shrinks: int = 0
+ self.finish_shrinking_deadline: Optional[float] = None
+ self.call_count: int = 0
+ self.valid_examples: int = 0
+ self.random: Random = random or Random(getrandbits(128))
+ self.database_key: Optional[bytes] = database_key
+ self.ignore_limits: bool = ignore_limits
# Global dict of per-phase statistics, and a list of per-call stats
# which transfer to the global dict at the end of each phase.
- self._current_phase = "(not a phase)"
- self.statistics = {}
- self.stats_per_test_case = []
+ self._current_phase: str = "(not a phase)"
+ self.statistics: StatisticsDict = {}
+ self.stats_per_test_case: List[CallStats] = []
- self.interesting_examples = {}
+ # At runtime, the keys are only ever type `InterestingOrigin`, but can be `None` during tests.
+ self.interesting_examples: Dict[InterestingOrigin, ConjectureResult] = {}
# We use call_count because there may be few possible valid_examples.
- self.first_bug_found_at = None
- self.last_bug_found_at = None
+ self.first_bug_found_at: Optional[int] = None
+ self.last_bug_found_at: Optional[int] = None
- self.shrunk_examples = set()
+ # At runtime, the keys are only ever type `InterestingOrigin`, but can be `None` during tests.
+ self.shrunk_examples: Set[Optional[InterestingOrigin]] = set()
- self.health_check_state = None
+ self.health_check_state: Optional[HealthCheckState] = None
- self.tree = DataTree()
+ self.tree: DataTree = DataTree()
- self.provider = _get_provider(self.settings.backend)
+ self.provider: Union[type, PrimitiveProvider] = _get_provider(
+ self.settings.backend
+ )
- self.best_observed_targets = defaultdict(lambda: NO_SCORE)
- self.best_examples_of_observed_targets = {}
+ self.best_observed_targets: "defaultdict[str, float]" = defaultdict(
+ lambda: NO_SCORE
+ )
+ self.best_examples_of_observed_targets: Dict[str, ConjectureResult] = {}
# We keep the pareto front in the example database if we have one. This
# is only marginally useful at present, but speeds up local development
# because it means that large targets will be quickly surfaced in your
# testing.
+ self.pareto_front: Optional[ParetoFront] = None
if self.database_key is not None and self.settings.database is not None:
self.pareto_front = ParetoFront(self.random)
self.pareto_front.on_evict(self.on_pareto_evict)
- else:
- self.pareto_front = None
# We want to be able to get the ConjectureData object that results
# from running a buffer without recalculating, especially during
@@ -190,24 +256,28 @@ class ConjectureRunner:
self.__data_cache = LRUReusedCache(CACHE_SIZE)
self.__data_cache_ir = LRUReusedCache(CACHE_SIZE)
- self.__pending_call_explanation = None
- self._switch_to_hypothesis_provider = False
+ self.__pending_call_explanation: Optional[str] = None
+ self._switch_to_hypothesis_provider: bool = False
- def explain_next_call_as(self, explanation):
+ def explain_next_call_as(self, explanation: str) -> None:
self.__pending_call_explanation = explanation
- def clear_call_explanation(self):
+ def clear_call_explanation(self) -> None:
self.__pending_call_explanation = None
@contextmanager
- def _log_phase_statistics(self, phase):
+ def _log_phase_statistics(
+ self, phase: Literal["reuse", "generate", "shrink"]
+ ) -> Generator[None, None, None]:
self.stats_per_test_case.clear()
start_time = time.perf_counter()
try:
self._current_phase = phase
yield
finally:
- self.statistics[phase + "-phase"] = {
+ # We ignore the mypy type error here. Because `phase` is a string literal and "-phase" is a string literal
+ # as well, the concatenation will always be valid key in the dictionary.
+ self.statistics[phase + "-phase"] = { # type: ignore
"duration-seconds": time.perf_counter() - start_time,
"test-cases": list(self.stats_per_test_case),
"distinct-failures": len(self.interesting_examples),
@@ -215,13 +285,13 @@ class ConjectureRunner:
}
@property
- def should_optimise(self):
+ def should_optimise(self) -> bool:
return Phase.target in self.settings.phases
- def __tree_is_exhausted(self):
+ def __tree_is_exhausted(self) -> bool:
return self.tree.is_exhausted and self.settings.backend == "hypothesis"
- def __stoppable_test_function(self, data):
+ def __stoppable_test_function(self, data: ConjectureData) -> None:
"""Run ``self._test_function``, but convert a ``StopTest`` exception
into a normal return and avoid raising Flaky for RecursionErrors.
"""
@@ -242,15 +312,23 @@ class ConjectureRunner:
# correct engine.
raise
- def _cache_key_ir(self, *, nodes=None, data=None):
+ def _cache_key_ir(
+ self,
+ *,
+ nodes: Optional[List[IRNode]] = None,
+ data: Union[ConjectureData, ConjectureResult, None] = None,
+ ) -> Tuple[Tuple[Any, ...], ...]:
assert (nodes is not None) ^ (data is not None)
extension = []
if data is not None:
nodes = data.examples.ir_tree_nodes
if data.invalid_at is not None:
# if we're invalid then we should have at least one node left (the invalid one).
+ assert isinstance(data, ConjectureData)
+ assert data.ir_tree_nodes is not None
assert data._node_index < len(data.ir_tree_nodes)
extension = [data.ir_tree_nodes[data._node_index]]
+ assert nodes is not None
# intentionally drop was_forced from equality here, because the was_forced
# of node prefixes on ConjectureData has no impact on that data's result
@@ -263,7 +341,7 @@ class ConjectureRunner:
for node in nodes + extension
)
- def _cache(self, data):
+ def _cache(self, data: Union[ConjectureData, ConjectureResult]) -> None:
result = data.as_result()
# when we shrink, we try out of bounds things, which can lead to the same
# data.buffer having multiple outcomes. eg data.buffer=b'' is Status.OVERRUN
@@ -281,7 +359,9 @@ class ConjectureRunner:
key = self._cache_key_ir(data=data)
self.__data_cache_ir[key] = result
- def cached_test_function_ir(self, nodes):
+ def cached_test_function_ir(
+ self, nodes: List[IRNode]
+ ) -> Union[ConjectureResult, _Overrun]:
key = self._cache_key_ir(nodes=nodes)
try:
return self.__data_cache_ir[key]
@@ -307,7 +387,7 @@ class ConjectureRunner:
self.test_function(data)
return data.as_result()
- def test_function(self, data):
+ def test_function(self, data: ConjectureData) -> None:
if self.__pending_call_explanation is not None:
self.debug(self.__pending_call_explanation)
self.__pending_call_explanation = None
@@ -328,7 +408,7 @@ class ConjectureRunner:
# the KeyboardInterrupt, never continue to the code below.
if not interrupted: # pragma: no branch
data.freeze()
- call_stats = {
+ call_stats: CallStats = {
"status": data.status.name.lower(),
"runtime": data.finish_time - data.start_time,
"drawtime": math.fsum(data.draw_times.values()),
@@ -355,7 +435,9 @@ class ConjectureRunner:
self.best_observed_targets[k] = max(self.best_observed_targets[k], v)
if k not in self.best_examples_of_observed_targets:
- self.best_examples_of_observed_targets[k] = data.as_result()
+ data_as_result = data.as_result()
+ assert not isinstance(data_as_result, _Overrun)
+ self.best_examples_of_observed_targets[k] = data_as_result
continue
existing_example = self.best_examples_of_observed_targets[k]
@@ -367,7 +449,9 @@ class ConjectureRunner:
if v > existing_score or sort_key(data.buffer) < sort_key(
existing_example.buffer
):
- self.best_examples_of_observed_targets[k] = data.as_result()
+ data_as_result = data.as_result()
+ assert not isinstance(data_as_result, _Overrun)
+ self.best_examples_of_observed_targets[k] = data_as_result
if data.status == Status.VALID:
self.valid_examples += 1
@@ -391,7 +475,7 @@ class ConjectureRunner:
key = data.interesting_origin
changed = False
try:
- existing = self.interesting_examples[key]
+ existing = self.interesting_examples[key] # type: ignore
except KeyError:
changed = True
self.last_bug_found_at = self.call_count
@@ -406,7 +490,7 @@ class ConjectureRunner:
if changed:
self.save_buffer(data.buffer)
- self.interesting_examples[key] = data.as_result()
+ self.interesting_examples[key] = data.as_result() # type: ignore
self.__data_cache.pin(data.buffer)
self.shrunk_examples.discard(key)
@@ -449,10 +533,10 @@ class ConjectureRunner:
self.record_for_health_check(data)
- def on_pareto_evict(self, data):
+ def on_pareto_evict(self, data: ConjectureData) -> None:
self.settings.database.delete(self.pareto_key, data.buffer)
- def generate_novel_prefix(self):
+ def generate_novel_prefix(self) -> bytes:
"""Uses the tree to proactively generate a starting sequence of bytes
that we haven't explored yet for this test.
@@ -462,7 +546,7 @@ class ConjectureRunner:
"""
return self.tree.generate_novel_prefix(self.random)
- def record_for_health_check(self, data):
+ def record_for_health_check(self, data: ConjectureData) -> None:
# Once we've actually found a bug, there's no point in trying to run
# health checks - they'll just mask the actually important information.
if data.status == Status.INTERESTING:
@@ -534,18 +618,20 @@ class ConjectureRunner:
HealthCheck.too_slow,
)
- def save_buffer(self, buffer, sub_key=None):
+ def save_buffer(
+ self, buffer: Union[bytes, bytearray], sub_key: Optional[bytes] = None
+ ) -> None:
if self.settings.database is not None:
key = self.sub_key(sub_key)
if key is None:
return
self.settings.database.save(key, bytes(buffer))
- def downgrade_buffer(self, buffer):
+ def downgrade_buffer(self, buffer: Union[bytes, bytearray]) -> None:
if self.settings.database is not None and self.database_key is not None:
self.settings.database.move(self.database_key, self.secondary_key, buffer)
- def sub_key(self, sub_key):
+ def sub_key(self, sub_key: Optional[bytes]) -> Optional[bytes]:
if self.database_key is None:
return None
if sub_key is None:
@@ -553,34 +639,34 @@ class ConjectureRunner:
return b".".join((self.database_key, sub_key))
@property
- def secondary_key(self):
+ def secondary_key(self) -> Optional[bytes]:
return self.sub_key(b"secondary")
@property
- def pareto_key(self):
+ def pareto_key(self) -> Optional[bytes]:
return self.sub_key(b"pareto")
- def debug(self, message):
+ def debug(self, message: str) -> None:
if self.settings.verbosity >= Verbosity.debug:
base_report(message)
@property
- def report_debug_info(self):
+ def report_debug_info(self) -> bool:
return self.settings.verbosity >= Verbosity.debug
- def debug_data(self, data):
+ def debug_data(self, data: Union[ConjectureData, ConjectureResult]) -> None:
if not self.report_debug_info:
return
- stack = [[]]
+ stack: List[Ls] = [[]]
- def go(ex):
+ def go(ex: Example) -> None:
if ex.length == 0:
return
if len(ex.children) == 0:
stack[-1].append(int_from_bytes(data.buffer[ex.start : ex.end]))
else:
- node = []
+ node: Ls = []
stack.append(node)
for v in ex.children:
@@ -601,7 +687,7 @@ class ConjectureRunner:
self.debug(f"{data.index} bytes {stack[0]!r} -> {status}, {data.output}")
- def run(self):
+ def run(self) -> None:
with local_settings(self.settings):
try:
self._run()
@@ -615,15 +701,15 @@ class ConjectureRunner:
)
@property
- def database(self):
+ def database(self) -> Optional[ExampleDatabase]:
if self.database_key is None:
return None
return self.settings.database
- def has_existing_examples(self):
+ def has_existing_examples(self) -> bool:
return self.database is not None and Phase.reuse in self.settings.phases
- def reuse_existing_examples(self):
+ def reuse_existing_examples(self) -> None:
"""If appropriate (we have a database and have been told to use it),
try to reload existing examples from the database.
@@ -672,6 +758,11 @@ class ConjectureRunner:
self.settings.database.delete(self.database_key, existing)
self.settings.database.delete(self.secondary_key, existing)
+ # Because self.database is not None (because self.has_existing_examples())
+ # and self.database_key is not None (because we fetched using it above),
+ # we can guarantee self.pareto_front is not None
+ assert self.pareto_front is not None
+
# If we've not found any interesting examples so far we try some of
# the pareto front from the last run.
if len(corpus) < desired_size and not self.interesting_examples:
@@ -688,7 +779,7 @@ class ConjectureRunner:
if data.status == Status.INTERESTING:
break
- def exit_with(self, reason):
+ def exit_with(self, reason: ExitReason) -> None:
if self.ignore_limits:
return
self.statistics["stopped-because"] = reason.describe(self.settings)
@@ -698,7 +789,7 @@ class ConjectureRunner:
self.exit_reason = reason
raise RunIsComplete
- def should_generate_more(self):
+ def should_generate_more(self) -> bool:
# End the generation phase where we would have ended it if no bugs had
# been found. This reproduces the exit logic in `self.test_function`,
# but with the important distinction that this clause will move on to
@@ -721,6 +812,8 @@ class ConjectureRunner:
or not self.settings.report_multiple_bugs
):
return False
+ assert isinstance(self.first_bug_found_at, int)
+ assert isinstance(self.last_bug_found_at, int)
assert self.first_bug_found_at <= self.last_bug_found_at <= self.call_count
# Otherwise, keep searching for between ten and 'a heuristic' calls.
# We cap 'calls after first bug' so errors are reported reasonably
@@ -730,7 +823,7 @@ class ConjectureRunner:
self.first_bug_found_at + 1000, self.last_bug_found_at * 2
)
- def generate_new_examples(self):
+ def generate_new_examples(self) -> None:
if Phase.generate not in self.settings.phases:
return
if self.interesting_examples:
@@ -744,10 +837,13 @@ class ConjectureRunner:
assert self.should_generate_more()
zero_data = self.cached_test_function(bytes(BUFFER_SIZE))
if zero_data.status > Status.OVERRUN:
+ assert isinstance(zero_data, ConjectureResult)
self.__data_cache.pin(zero_data.buffer)
if zero_data.status == Status.OVERRUN or (
- zero_data.status == Status.VALID and len(zero_data.buffer) * 2 > BUFFER_SIZE
+ zero_data.status == Status.VALID
+ and isinstance(zero_data, ConjectureResult)
+ and len(zero_data.buffer) * 2 > BUFFER_SIZE
):
fail_health_check(
self.settings,
@@ -831,6 +927,10 @@ class ConjectureRunner:
if minimal_example.status < Status.VALID:
consecutive_zero_extend_is_invalid += 1
continue
+ # Because the Status code is greater than Status.VALID, it cannot be
+ # Status.OVERRUN, which guarantees that the minimal_example is a
+ # ConjectureResult object.
+ assert isinstance(minimal_example, ConjectureResult)
consecutive_zero_extend_is_invalid = 0
@@ -846,10 +946,11 @@ class ConjectureRunner:
# running the test function for real here. If however we encounter
# some novel behaviour, we try again with the real test function,
# starting from the new novel prefix that has discovered.
+
+ trial_data = self.new_conjecture_data(
+ prefix=prefix, max_length=max_length
+ )
try:
- trial_data = self.new_conjecture_data(
- prefix=prefix, max_length=max_length
- )
self.tree.simulate_test_function(trial_data)
continue
except PreviouslyUnseenBehaviour:
@@ -857,6 +958,7 @@ class ConjectureRunner:
# If the simulation entered part of the tree that has been killed,
# we don't want to run this.
+ assert isinstance(trial_data.observer, TreeRecordingObserver)
if trial_data.observer.killed:
continue
@@ -898,7 +1000,9 @@ class ConjectureRunner:
self._current_phase = "target"
self.optimise_targets()
- def generate_mutations_from(self, data):
+ def generate_mutations_from(
+ self, data: Union[ConjectureData, ConjectureResult]
+ ) -> None:
# A thing that is often useful but rarely happens by accident is
# to generate the same value at multiple different points in the
# test case.
@@ -944,12 +1048,12 @@ class ConjectureRunner:
replacement = data.buffer[e.start : e.end]
try:
- # We attempt to replace both the the examples with
+ # We attempt to replace both the examples with
# whichever choice we made. Note that this might end
# up messing up and getting the example boundaries
# wrong - labels matching are only a best guess as to
# whether the two are equivalent - but it doesn't
- # really matter. It may not achieve the desired result
+ # really matter. It may not achieve the desired result,
# but it's still a perfectly acceptable choice sequence
# to try.
new_data = self.cached_test_function(
@@ -968,6 +1072,7 @@ class ConjectureRunner:
failed_mutations += 1
continue
+ assert isinstance(new_data, ConjectureResult)
if (
new_data.status >= data.status
and data.buffer != new_data.buffer
@@ -982,7 +1087,7 @@ class ConjectureRunner:
else:
failed_mutations += 1
- def optimise_targets(self):
+ def optimise_targets(self) -> None:
"""If any target observations have been made, attempt to optimise them
all."""
if not self.should_optimise:
@@ -1022,11 +1127,11 @@ class ConjectureRunner:
if prev_calls == self.call_count:
break
- def pareto_optimise(self):
+ def pareto_optimise(self) -> None:
if self.pareto_front is not None:
ParetoOptimiser(self).run()
- def _run(self):
+ def _run(self) -> None:
# have to use the primitive provider to interpret database bits...
self._switch_to_hypothesis_provider = True
with self._log_phase_statistics("reuse"):
@@ -1047,7 +1152,12 @@ class ConjectureRunner:
self.shrink_interesting_examples()
self.exit_with(ExitReason.finished)
- def new_conjecture_data_ir(self, ir_tree_prefix, *, observer=None):
+ def new_conjecture_data_ir(
+ self,
+ ir_tree_prefix: List[IRNode],
+ *,
+ observer: Optional[DataObserver] = None,
+ ) -> ConjectureData:
provider = (
HypothesisProvider if self._switch_to_hypothesis_provider else self.provider
)
@@ -1059,7 +1169,12 @@ class ConjectureRunner:
ir_tree_prefix, observer=observer, provider=provider
)
- def new_conjecture_data(self, prefix, max_length=BUFFER_SIZE, observer=None):
+ def new_conjecture_data(
+ self,
+ prefix: Union[bytes, bytearray],
+ max_length: int = BUFFER_SIZE,
+ observer: Optional[DataObserver] = None,
+ ) -> ConjectureData:
provider = (
HypothesisProvider if self._switch_to_hypothesis_provider else self.provider
)
@@ -1075,10 +1190,12 @@ class ConjectureRunner:
provider=provider,
)
- def new_conjecture_data_for_buffer(self, buffer):
+ def new_conjecture_data_for_buffer(
+ self, buffer: Union[bytes, bytearray]
+ ) -> ConjectureData:
return self.new_conjecture_data(buffer, max_length=len(buffer))
- def shrink_interesting_examples(self):
+ def shrink_interesting_examples(self) -> None:
"""If we've found interesting examples, try to replace each of them
with a minimal interesting example with the same interesting_origin.
@@ -1119,7 +1236,7 @@ class ConjectureRunner:
self.shrink(example, lambda d: d.status == Status.INTERESTING)
return
- def predicate(d):
+ def predicate(d: ConjectureData) -> bool:
if d.status < Status.INTERESTING:
return False
return d.interesting_origin == target
@@ -1128,7 +1245,7 @@ class ConjectureRunner:
self.shrunk_examples.add(target)
- def clear_secondary_key(self):
+ def clear_secondary_key(self) -> None:
if self.has_existing_examples():
# If we have any smaller examples in the secondary corpus, now is
# a good time to try them to see if they work as shrinks. They
@@ -1154,12 +1271,26 @@ class ConjectureRunner:
# of this reason for interestingness.
self.settings.database.delete(self.secondary_key, c)
- def shrink(self, example, predicate=None, allow_transition=None):
+ def shrink(
+ self,
+ example: Union[ConjectureData, ConjectureResult],
+ predicate: Optional[Callable[[ConjectureData], bool]] = None,
+ allow_transition: Optional[
+ Callable[[Union[ConjectureData, ConjectureResult], ConjectureData], bool]
+ ] = None,
+ ) -> Union[ConjectureData, ConjectureResult]:
s = self.new_shrinker(example, predicate, allow_transition)
s.shrink()
return s.shrink_target
- def new_shrinker(self, example, predicate=None, allow_transition=None):
+ def new_shrinker(
+ self,
+ example: Union[ConjectureData, ConjectureResult],
+ predicate: Optional[Callable[[ConjectureData], bool]] = None,
+ allow_transition: Optional[
+ Callable[[Union[ConjectureData, ConjectureResult], ConjectureData], bool]
+ ] = None,
+ ) -> Shrinker:
return Shrinker(
self,
example,
@@ -1169,7 +1300,13 @@ class ConjectureRunner:
in_target_phase=self._current_phase == "target",
)
- def cached_test_function(self, buffer, *, error_on_discard=False, extend=0):
+ def cached_test_function(
+ self,
+ buffer: Union[bytes, bytearray],
+ *,
+ error_on_discard: bool = False,
+ extend: int = 0,
+ ) -> Union[ConjectureResult, _Overrun]:
"""Checks the tree to see if we've tested this buffer, and returns the
previous result if we have.
@@ -1187,7 +1324,13 @@ class ConjectureRunner:
max_length = min(BUFFER_SIZE, len(buffer) + extend)
- def check_result(result):
+ @overload
+ def check_result(result: _Overrun) -> _Overrun: ...
+ @overload
+ def check_result(result: ConjectureResult) -> ConjectureResult: ...
+ def check_result(
+ result: Union[_Overrun, ConjectureResult],
+ ) -> Union[_Overrun, ConjectureResult]:
assert result is Overrun or (
isinstance(result, ConjectureResult) and result.status != Status.OVERRUN
)
@@ -1200,10 +1343,12 @@ class ConjectureRunner:
except KeyError:
pass
+ observer: DataObserver
if error_on_discard:
class DiscardObserver(DataObserver):
- def kill_branch(self):
+ @override
+ def kill_branch(self) -> NoReturn:
raise ContainsDiscard
observer = DiscardObserver()
@@ -1241,11 +1386,15 @@ class ConjectureRunner:
)
self.test_function(data)
result = check_result(data.as_result())
- if extend == 0 or (result is not Overrun and len(result.buffer) <= len(buffer)):
+ if extend == 0 or (
+ result is not Overrun
+ and not isinstance(result, _Overrun)
+ and len(result.buffer) <= len(buffer)
+ ):
self.__data_cache[buffer] = result
return result
- def passing_buffers(self, prefix=b""):
+ def passing_buffers(self, prefix: bytes = b"") -> FrozenSet[bytes]:
"""Return a collection of bytestrings which cause the test to pass.
Optionally restrict this by a certain prefix, which is useful for explain mode.
diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/shrinker.py
index dc1b3397f02..a2ca4a7a0ee 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 typing import TYPE_CHECKING, Callable, Dict, Optional
+from typing import TYPE_CHECKING, Callable, Dict, Optional, Union
import attr
@@ -268,10 +268,12 @@ class Shrinker:
def __init__(
self,
engine: "ConjectureRunner",
- initial: ConjectureData,
- predicate: Optional[Callable[..., bool]],
+ initial: Union[ConjectureData, ConjectureResult],
+ predicate: Optional[Callable[[ConjectureData], bool]],
*,
- allow_transition: bool,
+ allow_transition: Optional[
+ Callable[[Union[ConjectureData, ConjectureResult], ConjectureData], bool]
+ ],
explain: bool,
in_target_phase: bool = False,
):
@@ -1652,7 +1654,7 @@ class ShrinkPass:
return True
@property
- def name(self):
+ def name(self) -> str:
return self.run_with_chooser.__name__
diff --git a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
index 300a45e59b2..570645509d6 100644
--- a/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
+++ b/contrib/python/hypothesis/py3/hypothesis/strategies/_internal/core.py
@@ -83,7 +83,6 @@ from hypothesis.internal.compat import (
is_typed_named_tuple,
)
from hypothesis.internal.conjecture.utils import calc_label_from_cls, check_sample
-from hypothesis.internal.coverage import IN_COVERAGE_TESTS, description_stack
from hypothesis.internal.entropy import get_seeder_and_restorer
from hypothesis.internal.floats import float_of
from hypothesis.internal.observability import TESTCASE_CALLBACKS
@@ -1758,22 +1757,8 @@ class CompositeStrategy(SearchStrategy):
self.args = args
self.kwargs = kwargs
- if IN_COVERAGE_TESTS:
- # We do a bit of a dance here to ensure that whatever 'outer' description
- # stack we might have, doesn't affect the validation code internal to the
- # strategy we're drawing from. Otherwise, we'd get flaky fails like #3968.
- def do_draw(self, data):
- prev = description_stack[:]
- try:
- description_stack.clear()
- return self.definition(data.draw, *self.args, **self.kwargs)
- finally:
- description_stack[:] = prev
-
- else: # pragma: no cover
-
- def do_draw(self, data):
- return self.definition(data.draw, *self.args, **self.kwargs)
+ def do_draw(self, data):
+ return self.definition(data.draw, *self.args, **self.kwargs)
def calc_label(self):
return calc_label_from_cls(self.definition)
diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py
index 659ee4dc343..b4d1cbcc8cd 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, 100, 6)
+__version_info__ = (6, 101, 0)
__version__ = ".".join(map(str, __version_info__))
diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make
index a54ba569ebf..8ae370f3384 100644
--- a/contrib/python/hypothesis/py3/ya.make
+++ b/contrib/python/hypothesis/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(6.100.6)
+VERSION(6.101.0)
LICENSE(MPL-2.0)