Skip to content

Commit

Permalink
Merge pull request #4254 from tybug/tcs-cleanup
Browse files Browse the repository at this point in the history
Reorganize code for the typed choice sequence
  • Loading branch information
Zac-HD authored Feb 1, 2025
2 parents 9d7e1d3 + 869e3de commit 63bb80c
Show file tree
Hide file tree
Showing 22 changed files with 730 additions and 710 deletions.
3 changes: 3 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RELEASE_TYPE: patch

Registration of experimental :ref:`alternative-backends` is now done via ``hypothesis.internal.conjecture.providers.AVAILABLE_PROVIDERS`` instead of ``hypothesis.internal.conjecture.data.AVAILABLE_PROVIDERS``.
5 changes: 1 addition & 4 deletions hypothesis-python/src/hypothesis/_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
InvalidArgument,
InvalidState,
)
from hypothesis.internal.conjecture.providers import AVAILABLE_PROVIDERS
from hypothesis.internal.reflection import get_pretty_function_description
from hypothesis.internal.validation import check_type, try_convert
from hypothesis.utils.conventions import not_set
Expand Down Expand Up @@ -296,8 +297,6 @@ def __setattr__(self, name: str, value: object) -> NoReturn:
raise AttributeError("settings objects are immutable")

def __repr__(self) -> str:
from hypothesis.internal.conjecture.data import AVAILABLE_PROVIDERS

bits = sorted(
f"{name}={getattr(self, name)!r}"
for name in all_settings
Expand Down Expand Up @@ -731,8 +730,6 @@ def is_in_ci() -> bool:


def _backend_validator(value: str) -> str:
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.'
Expand Down
21 changes: 11 additions & 10 deletions hypothesis-python/src/hypothesis/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
settings as Settings,
)
from hypothesis.control import BuildContext
from hypothesis.database import ir_from_bytes, ir_to_bytes
from hypothesis.database import choices_from_bytes, choices_to_bytes
from hypothesis.errors import (
BackendCannotProceed,
DeadlineExceeded,
Expand All @@ -77,17 +77,16 @@
int_from_bytes,
)
from hypothesis.internal.conjecture.choice import ChoiceT
from hypothesis.internal.conjecture.data import (
ConjectureData,
PrimitiveProvider,
Status,
)
from hypothesis.internal.conjecture.data import ConjectureData, Status
from hypothesis.internal.conjecture.engine import BUFFER_SIZE, ConjectureRunner
from hypothesis.internal.conjecture.junkdrawer import (
ensure_free_stackframes,
gc_cumulative_time,
)
from hypothesis.internal.conjecture.providers import BytestringProvider
from hypothesis.internal.conjecture.providers import (
BytestringProvider,
PrimitiveProvider,
)
from hypothesis.internal.conjecture.shrinker import sort_key
from hypothesis.internal.entropy import deterministic_PRNG
from hypothesis.internal.escalation import (
Expand Down Expand Up @@ -323,7 +322,7 @@ def accept(test):


def encode_failure(choices):
blob = ir_to_bytes(choices)
blob = choices_to_bytes(choices)
compressed = zlib.compress(blob)
if len(compressed) < len(blob):
blob = b"\1" + compressed
Expand Down Expand Up @@ -353,7 +352,7 @@ def decode_failure(blob: bytes) -> Sequence[ChoiceT]:
f"Could not decode blob {blob!r}: Invalid start byte {prefix!r}"
)

choices = ir_from_bytes(decoded)
choices = choices_from_bytes(decoded)
if choices is None:
raise InvalidArgument(f"Invalid serialized choice sequence for blob {blob!r}")

Expand Down Expand Up @@ -1883,7 +1882,9 @@ def fuzz_one_input(
if settings.database is not None and (
known is None or sort_key(data.nodes) <= sort_key(known)
):
settings.database.save(database_key, ir_to_bytes(data.choices))
settings.database.save(
database_key, choices_to_bytes(data.choices)
)
minimal_failures[data.interesting_origin] = data.nodes
raise
assert isinstance(data.provider, BytestringProvider)
Expand Down
12 changes: 6 additions & 6 deletions hypothesis-python/src/hypothesis/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,8 +775,8 @@ def _unpack_uleb128(buffer: bytes) -> tuple[int, int]:
return (i + 1, value)


def ir_to_bytes(ir: Iterable[ChoiceT], /) -> bytes:
"""Serialize a list of IR elements to a bytestring. Inverts ir_from_bytes."""
def choices_to_bytes(ir: Iterable[ChoiceT], /) -> bytes:
"""Serialize a list of IR elements to a bytestring. Inverts choices_from_bytes."""
# We use a custom serialization format for this, which might seem crazy - but our
# data is a flat sequence of elements, and standard tools like protobuf or msgpack
# don't deal well with e.g. nonstandard bit-pattern-NaNs, or invalid-utf8 unicode.
Expand Down Expand Up @@ -815,7 +815,7 @@ def ir_to_bytes(ir: Iterable[ChoiceT], /) -> bytes:
return b"".join(parts)


def _ir_from_bytes(buffer: bytes, /) -> tuple[ChoiceT, ...]:
def _choices_from_bytes(buffer: bytes, /) -> tuple[ChoiceT, ...]:
# See above for an explanation of the format.
parts: list[ChoiceT] = []
idx = 0
Expand Down Expand Up @@ -846,15 +846,15 @@ def _ir_from_bytes(buffer: bytes, /) -> tuple[ChoiceT, ...]:
return tuple(parts)


def ir_from_bytes(buffer: bytes, /) -> Optional[tuple[ChoiceT, ...]]:
def choices_from_bytes(buffer: bytes, /) -> Optional[tuple[ChoiceT, ...]]:
"""
Deserialize a bytestring to a tuple of choices. Inverts ir_to_bytes.
Deserialize a bytestring to a tuple of choices. Inverts choices_to_bytes.
Returns None if the given bytestring is not a valid serialization of choice
sequences.
"""
try:
return _ir_from_bytes(buffer)
return _choices_from_bytes(buffer)
except Exception:
# deserialization error, eg because our format changed or someone put junk
# data in the db.
Expand Down
20 changes: 19 additions & 1 deletion hypothesis-python/src/hypothesis/internal/conjecture/choice.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/.

import math
from collections.abc import Sequence
from collections.abc import Iterable, Sequence
from typing import (
TYPE_CHECKING,
Callable,
Expand All @@ -21,6 +21,8 @@
cast,
)

import attr

from hypothesis.errors import ChoiceTooLarge
from hypothesis.internal.conjecture.floats import float_to_lex, lex_to_float
from hypothesis.internal.conjecture.utils import identity
Expand Down Expand Up @@ -72,6 +74,16 @@ class BooleanKWargs(TypedDict):
]


@attr.s(slots=True)
class ChoiceTemplate:
type: Literal["simplest"] = attr.ib()
count: Optional[int] = attr.ib()

def __attrs_post_init__(self) -> None:
if self.count is not None:
assert self.count > 0


def _size_to_index(size: int, *, alphabet_size: int) -> int:
# this is the closed form of this geometric series:
# for i in range(size):
Expand Down Expand Up @@ -494,3 +506,9 @@ def choice_kwargs_key(ir_type, kwargs):
kwargs["shrink_towards"],
)
return tuple(kwargs[key] for key in sorted(kwargs))


def choices_size(choices: Iterable[ChoiceT]) -> int:
from hypothesis.database import choices_to_bytes

return len(choices_to_bytes(choices))
Loading

0 comments on commit 63bb80c

Please sign in to comment.