Skip to content

Commit

Permalink
Merge branch 'master' into lowlevel-provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD committed Nov 16, 2023
2 parents ab973c7 + c5efb04 commit 126fb94
Show file tree
Hide file tree
Showing 33 changed files with 471 additions and 177 deletions.
54 changes: 49 additions & 5 deletions hypothesis-python/docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,52 @@ Hypothesis 6.x

.. include:: ../RELEASE.rst

.. _v6.89.0:

-------------------
6.89.0 - 2023-11-16
-------------------

This release teaches :func:`~hypothesis.strategies.from_type` to handle constraints
implied by the :pypi:`annotated-types` package - as used by e.g. :pypi:`Pydantic`.
This is usually efficient, but falls back to filtering in a few remaining cases.

Thanks to Viicos for :pull:`3780`!

.. _v6.88.4:

-------------------
6.88.4 - 2023-11-13
-------------------

This patch adds a warning when :func:`@st.composite <hypothesis.strategies.composite>`
wraps a function annotated as returning a :class:`~hypothesis.strategies.SearchStrategy`,
since this is usually an error (:issue:`3786`). The function should return a value,
and the decorator will convert it to a function which returns a strategy.

.. _v6.88.3:

-------------------
6.88.3 - 2023-11-05
-------------------

This patch refactors ``from_type(typing.Tuple)``, allowing
:func:`~hypothesis.strategies.register_type_strategy` to take effect
for tuples instead of being silently ignored (:issue:`3750`).

Thanks to Nick Collins for reporting and extensive work on this issue.

.. _v6.88.2:

-------------------
6.88.2 - 2023-11-05
-------------------

This patch improves the speed of the explain phase on python 3.12+, by using the new
:mod:`sys.monitoring` module to collect coverage, instead of :obj:`sys.settrace`.

Thanks to Liam DeVoe for :pull:`3776`!

.. _v6.88.1:

-------------------
Expand All @@ -44,12 +90,10 @@ return a strategy, by returning :data:`NotImplemented` (:issue:`3767`).
6.87.4 - 2023-10-12
-------------------

When :func:`~hypothesis.strategies.randoms` was called with `use_true_randoms=False`,
calling `sample` on it with an empty sequence and 0 elements would result in an error,
When :func:`~hypothesis.strategies.randoms` was called with ``use_true_randoms=False``,
calling ``r.sample([], 0)`` would result in an error,
when it should have returned an empty sequence to agree with the normal behaviour of
`random.Random`. This fixes that discrepancy.

Fixes :issue:`3765``
:func:`random.sample`. This fixes that discrepancy (:issue:`3765`).

.. _v6.87.3:

Expand Down
6 changes: 6 additions & 0 deletions hypothesis-python/scripts/other-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ pip install "$(grep 'typing-extensions==' ../requirements/coverage.txt)"
$PYTEST tests/typing_extensions/
pip uninstall -y typing_extensions

if [ "$(python -c 'import sys; print(sys.version_info[:2] >= (3, 9))')" = "True" ] ; then
pip install "$(grep 'annotated-types==' ../requirements/coverage.txt)"
$PYTEST tests/test_annotated_types.py
pip uninstall -y annotated-types
fi

pip install ".[lark]"
pip install "$(grep -oE 'lark>=([0-9.]+)' ../hypothesis-python/setup.py | tr '>' =)"
$PYTEST -Wignore tests/lark/
Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import setuptools

if sys.version_info[:2] < (3, 8):
if sys.version_info[:2] < (3, 8): # noqa # "unreachable" sanity check
raise Exception(
"You are trying to install Hypothesis using Python "
f"{sys.version.split()[0]}, but it requires Python 3.8 or later."
Expand Down
1 change: 0 additions & 1 deletion hypothesis-python/src/hypothesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
failing examples it finds.
"""

import hypothesis._error_if_old # noqa # imported for side-effect of nice error
from hypothesis._settings import HealthCheck, Phase, Verbosity, settings
from hypothesis.control import (
assume,
Expand Down
23 changes: 0 additions & 23 deletions hypothesis-python/src/hypothesis/_error_if_old.py

This file was deleted.

3 changes: 1 addition & 2 deletions hypothesis-python/src/hypothesis/_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,8 +345,7 @@ def load_profile(name: str) -> None:

@contextlib.contextmanager
def local_settings(s):
default_context_manager = default_variable.with_value(s)
with default_context_manager:
with default_variable.with_value(s):
yield s


Expand Down
18 changes: 8 additions & 10 deletions hypothesis-python/src/hypothesis/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -910,20 +910,18 @@ def _execute_once_for_engine(self, data):
and not self.failed_due_to_deadline
and Phase.shrink in self.settings.phases
and Phase.explain in self.settings.phases
and sys.gettrace() is None
and (sys.gettrace() is None or sys.version_info[:2] >= (3, 12))
and not PYPY
): # pragma: no cover
# This is in fact covered by our *non-coverage* tests, but due to the
# settrace() contention *not* by our coverage tests. Ah well.
tracer = Tracer()
try:
sys.settrace(tracer.trace)
result = self.execute_once(data)
if data.status == Status.VALID:
self.explain_traces[None].add(frozenset(tracer.branches))
finally:
sys.settrace(None)
trace = frozenset(tracer.branches)
with Tracer() as tracer:
try:
result = self.execute_once(data)
if data.status == Status.VALID:
self.explain_traces[None].add(frozenset(tracer.branches))
finally:
trace = frozenset(tracer.branches)
else:
result = self.execute_once(data)
if result is not None:
Expand Down
21 changes: 3 additions & 18 deletions hypothesis-python/src/hypothesis/extra/lark.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
from inspect import signature
from typing import Dict, Optional

import attr
import lark
from lark.grammar import NonTerminal, Terminal

Expand All @@ -40,20 +39,6 @@
__all__ = ["from_lark"]


@attr.s()
class DrawState:
"""Tracks state of a single draw from a lark grammar.
Currently just wraps a list of tokens that will be emitted at the
end, but as we support more sophisticated parsers this will need
to track more state for e.g. indentation level.
"""

# The text output so far as a list of string tokens resulting from
# each draw to a non-terminal.
result = attr.ib(default=attr.Factory(list))


def get_terminal_names(terminals, rules, ignore_names):
"""Get names of all terminals in the grammar.
Expand Down Expand Up @@ -138,10 +123,10 @@ def __init__(self, grammar, start, explicit):
self.__rule_labels = {}

def do_draw(self, data):
state = DrawState()
state = []
start = data.draw(self.start)
self.draw_symbol(data, start, state)
return "".join(state.result)
return "".join(state)

def rule_label(self, name):
try:
Expand All @@ -162,7 +147,7 @@ def draw_symbol(self, data, symbol, draw_state):
'names-to-strategies, such as `{%r: st.just("")}`'
% (symbol.name, symbol.name)
) from None
draw_state.result.append(data.draw(strategy))
draw_state.append(data.draw(strategy))
else:
assert isinstance(symbol, NonTerminal)
data.start_example(self.rule_label(symbol.name))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ class TreeNode:
# with the ``n_bits`` argument going in ``bit_lengths`` and the
# values seen in ``values``. These should always have the same
# length.
bit_lengths = attr.ib(default=attr.Factory(IntList))
values = attr.ib(default=attr.Factory(IntList))
bit_lengths = attr.ib(factory=IntList)
values = attr.ib(factory=IntList)

# The indices of of the calls to ``draw_bits`` that we have stored
# where ``forced`` is not None. Stored as None if no indices
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,25 +87,25 @@ class DistinguishedState:
distinct from ones we have previously seen so far."""

# Index of this state in the learner's list of states
index = attr.ib()
index: int = attr.ib()

# A string that witnesses this state (i.e. when starting from the origin
# and following this string you will end up in this state).
label = attr.ib()
label: str = attr.ib()

# A boolean as to whether this is an accepting state.
accepting = attr.ib()
accepting: bool = attr.ib()

# A list of experiments that it is necessary to run to determine whether
# a string is in this state. This is stored as a dict mapping experiments
# to their expected result. A string is only considered to lead to this
# state if ``all(learner.member(s + experiment) == result for experiment,
# result in self.experiments.items())``.
experiments = attr.ib()
experiments: dict = attr.ib()

# A cache of transitions out of this state, mapping bytes to the states
# that they lead to.
transitions = attr.ib(default=attr.Factory(dict))
transitions: dict = attr.ib(factory=dict)


class LStar:
Expand Down
10 changes: 5 additions & 5 deletions hypothesis-python/src/hypothesis/internal/conjecture/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@

@attr.s
class HealthCheckState:
valid_examples = attr.ib(default=0)
invalid_examples = attr.ib(default=0)
overrun_examples = attr.ib(default=0)
draw_times = attr.ib(default=attr.Factory(list))
valid_examples: int = attr.ib(default=0)
invalid_examples: int = attr.ib(default=0)
overrun_examples: int = attr.ib(default=0)
draw_times: list = attr.ib(factory=list)


class ExitReason(Enum):
Expand Down Expand Up @@ -976,7 +976,7 @@ def new_shrinker(self, example, predicate=None, allow_transition=None):
self,
example,
predicate,
allow_transition,
allow_transition=allow_transition,
explain=Phase.explain in self.settings.phases,
)

Expand Down
33 changes: 21 additions & 12 deletions hypothesis-python/src/hypothesis/internal/conjecture/shrinker.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# obtain one at https://mozilla.org/MPL/2.0/.

from collections import defaultdict
from typing import TYPE_CHECKING, Dict
from typing import TYPE_CHECKING, Callable, Dict, Optional

import attr

Expand All @@ -19,7 +19,9 @@
prefix_selection_order,
random_selection_order,
)
from hypothesis.internal.conjecture.data import (
from hypothesis.internal.conjecture.data import ConjectureData, ConjectureResult, Status
from hypothesis.internal.conjecture.dfa import ConcreteDFA
from hypothesis.internal.conjecture.floats import (
DRAW_FLOAT_LABEL,
ConjectureData,
ConjectureResult,
Expand Down Expand Up @@ -262,7 +264,15 @@ def accept(self):
accept.__name__ = fn.__name__
return property(accept)

def __init__(self, engine, initial, predicate, allow_transition, explain):
def __init__(
self,
engine: "ConjectureRunner",
initial: ConjectureData,
predicate: Optional[Callable[..., bool]],
*,
allow_transition: bool,
explain: bool,
):
"""Create a shrinker for a particular engine, with a given starting
point and predicate. When shrink() is called it will attempt to find an
example for which predicate is True and which is strictly smaller than
Expand All @@ -272,17 +282,17 @@ def __init__(self, engine, initial, predicate, allow_transition, explain):
takes ConjectureData objects.
"""
assert predicate is not None or allow_transition is not None
self.engine: "ConjectureRunner" = engine
self.engine = engine
self.__predicate = predicate or (lambda data: True)
self.__allow_transition = allow_transition or (lambda source, destination: True)
self.__derived_values = {}
self.__derived_values: dict = {}
self.__pending_shrink_explanation = None

self.initial_size = len(initial.buffer)

# We keep track of the current best example on the shrink_target
# attribute.
self.shrink_target: ConjectureData = initial
self.shrink_target = initial
self.clear_change_tracking()
self.shrinks = 0

Expand All @@ -294,12 +304,11 @@ def __init__(self, engine, initial, predicate, allow_transition, explain):
self.initial_calls = self.engine.call_count
self.calls_at_last_shrink = self.initial_calls

self.passes_by_name = {}
self.passes = []
self.passes_by_name: Dict[str, ShrinkPass] = {}

# Extra DFAs that may be installed. This is used solely for
# testing and learning purposes.
self.extra_dfas = {}
self.extra_dfas: Dict[str, ConcreteDFA] = {}

self.should_explain = explain

Expand All @@ -325,9 +334,8 @@ def add_new_pass(self, run):
p = ShrinkPass(
run_with_chooser=definition.run_with_chooser,
shrinker=self,
index=len(self.passes),
index=len(self.passes_by_name),
)
self.passes.append(p)
self.passes_by_name[p.name] = p
return p

Expand Down Expand Up @@ -474,7 +482,8 @@ def s(n):
self.debug("Useless passes:")
self.debug("")
for p in sorted(
self.passes, key=lambda t: (-t.calls, t.deletions, t.shrinks)
self.passes_by_name.values(),
key=lambda t: (-t.calls, t.deletions, t.shrinks),
):
if p.calls == 0:
continue
Expand Down
10 changes: 9 additions & 1 deletion hypothesis-python/src/hypothesis/internal/filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from decimal import Decimal
from fractions import Fraction
from functools import partial
from typing import Any, Callable, Dict, NamedTuple, Optional, TypeVar
from typing import Any, Callable, Collection, Dict, NamedTuple, Optional, TypeVar

from hypothesis.internal.compat import ceil, floor
from hypothesis.internal.floats import next_down, next_up
Expand Down Expand Up @@ -295,3 +295,11 @@ def get_float_predicate_bounds(predicate: Predicate) -> ConstructivePredicate:

kwargs = {k: v for k, v in kwargs.items() if k in {"min_value", "max_value"}}
return ConstructivePredicate(kwargs, predicate)


def max_len(size: int, element: Collection) -> bool:
return len(element) <= size


def min_len(size: int, element: Collection) -> bool:
return size <= len(element)

0 comments on commit 126fb94

Please sign in to comment.