Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ jobs:
- alt-nocover
- alt-rest
exclude:
- { os: macos-latest, python-architecture: "x86" }
- { python-version: "3.13", python-architecture: "x86" }
- { os: macos-latest, python-architecture: "x86" }
- { python-version: "3.13", python-architecture: "x86" }
- { python-version: "3.11", task: nocover }
- { python-version: "3.11", task: rest }
- { python-version: "3.13", task: alt-nocover }
Expand Down
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

Fixes our bundled |run_conformance_test| not respecting |PrimitiveProvider.avoid_realization|.
2 changes: 1 addition & 1 deletion hypothesis-python/docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Hypothesis is the property-based testing library for Python. With Hypothesis, yo
@given(st.lists(st.integers() | st.floats()))
def test_sort_correct(lst):
# lst is a random list of numbers
# hypothesis generates random lists of numbers to test
assert my_sort(lst) == sorted(lst)
test_sort_correct()
Expand Down
2 changes: 2 additions & 0 deletions hypothesis-python/docs/prolog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,10 @@
.. |PrimitiveProvider.add_observability_callback| replace:: :data:`~hypothesis.internal.conjecture.providers.PrimitiveProvider.add_observability_callback`
.. |PrimitiveProvider.span_start| replace:: :func:`~hypothesis.internal.conjecture.providers.PrimitiveProvider.span_start`
.. |PrimitiveProvider.span_end| replace:: :func:`~hypothesis.internal.conjecture.providers.PrimitiveProvider.span_end`
.. |PrimitiveProvider.avoid_realization| replace:: :data:`~hypothesis.internal.conjecture.providers.PrimitiveProvider.avoid_realization`

.. |AVAILABLE_PROVIDERS| replace:: :data:`~hypothesis.internal.conjecture.providers.AVAILABLE_PROVIDERS`
.. |run_conformance_test| replace:: :func:`~hypothesis.internal.conjecture.provider_conformance.run_conformance_test`

.. |add_observability_callback| replace:: :data:`~hypothesis.internal.observability.add_observability_callback`
.. |remove_observability_callback| replace:: :data:`~hypothesis.internal.observability.remove_observability_callback`
Expand Down
19 changes: 1 addition & 18 deletions hypothesis-python/src/hypothesis/internal/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,24 +117,7 @@ def __setitem__(self, key: K, value: V) -> None:
raise ValueError(
"Cannot increase size of cache where all keys have been pinned."
) from None
try:
del self.keys_to_indices[evicted.key]
except KeyError: # pragma: no cover
# This can't happen, but happens nevertheless with
# id(key1) == id(key2)
# but
# hash(key1) != hash(key2)
# (see https://github.com/HypothesisWorks/hypothesis/issues/4442)
# Rebuild keys_to_indices to match data.
self.keys_to_indices.clear()
self.keys_to_indices.update(
{
entry.key: i
for i, entry in enumerate(self.data)
if entry is not evicted
}
)
assert len(self.keys_to_indices) == len(self.data) - 1
del self.keys_to_indices[evicted.key]
i = 0
self.data[0] = entry
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,11 @@ class RunIsComplete(Exception):


def _get_provider(backend: str) -> Union[type, PrimitiveProvider]:
module_name, class_name = AVAILABLE_PROVIDERS[backend].rsplit(".", 1)
provider_cls = getattr(importlib.import_module(module_name), class_name)
provider_cls = AVAILABLE_PROVIDERS[backend]
if isinstance(provider_cls, str):
module_name, class_name = provider_cls.rsplit(".", 1)
provider_cls = getattr(importlib.import_module(module_name), class_name)

if provider_cls.lifetime == "test_function":
return provider_cls(None)
elif provider_cls.lifetime == "test_case":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
from hypothesis.internal.conjecture.data import ConjectureData
from hypothesis.internal.conjecture.providers import (
COLLECTION_DEFAULT_MAX_SIZE,
HypothesisProvider,
PrimitiveProvider,
with_register_backend,
)
from hypothesis.internal.floats import SMALLEST_SUBNORMAL, sign_aware_lte
from hypothesis.internal.intervalsets import IntervalSet
Expand Down Expand Up @@ -369,119 +371,132 @@ def test_conformance():
treat those exceptions as fatal errors.
"""

@Settings(settings, suppress_health_check=[HealthCheck.too_slow])
class ProviderConformanceTest(RuleBasedStateMachine):
def __init__(self):
super().__init__()

@initialize(random=st.randoms())
def setup(self, random):
if Provider.lifetime == "test_case":
data = ConjectureData(random=random, provider=Provider)
self.provider = data.provider
else:
self.provider = Provider(None)

self.context_manager = self.provider.per_test_case_context_manager()
self.context_manager.__enter__()
self.frozen = False

def _draw(self, choice_type, constraints):
del constraints["forced"]
draw_func = getattr(self.provider, f"draw_{choice_type}")

try:
choice = draw_func(**constraints)
note(f"drew {choice_type} {choice}")
expected_type = {
"integer": int,
"float": float,
"bytes": bytes,
"string": str,
"boolean": bool,
}[choice_type]
assert isinstance(choice, expected_type)
assert choice_permitted(choice, constraints)
except context_manager_exceptions as e:
note(f"caught exception {type(e)} in context_manager_exceptions: {e}")
class CopiesRealizationProvider(HypothesisProvider):
avoid_realization = Provider.avoid_realization

with with_register_backend("copies_realization", CopiesRealizationProvider):

@Settings(
settings,
suppress_health_check=[HealthCheck.too_slow],
backend="copies_realization",
)
class ProviderConformanceTest(RuleBasedStateMachine):
def __init__(self):
super().__init__()

@initialize(random=st.randoms())
def setup(self, random):
if Provider.lifetime == "test_case":
data = ConjectureData(random=random, provider=Provider)
self.provider = data.provider
else:
self.provider = Provider(None)

self.context_manager = self.provider.per_test_case_context_manager()
self.context_manager.__enter__()
self.frozen = False

def _draw(self, choice_type, constraints):
del constraints["forced"]
draw_func = getattr(self.provider, f"draw_{choice_type}")

try:
self.context_manager.__exit__(type(e), e, None)
except BackendCannotProceed:
self.frozen = True
return None

return choice

@precondition(lambda self: not self.frozen)
@rule(constraints=integer_constraints())
def draw_integer(self, constraints):
self._draw("integer", constraints)

@precondition(lambda self: not self.frozen)
@rule(constraints=float_constraints())
def draw_float(self, constraints):
self._draw("float", constraints)

@precondition(lambda self: not self.frozen)
@rule(constraints=bytes_constraints())
def draw_bytes(self, constraints):
self._draw("bytes", constraints)

@precondition(lambda self: not self.frozen)
@rule(constraints=string_constraints())
def draw_string(self, constraints):
self._draw("string", constraints)

@precondition(lambda self: not self.frozen)
@rule(constraints=boolean_constraints())
def draw_boolean(self, constraints):
self._draw("boolean", constraints)

@precondition(lambda self: not self.frozen)
@rule(label=st.integers())
def span_start(self, label):
self.provider.span_start(label)

@precondition(lambda self: not self.frozen)
@rule(discard=st.booleans())
def span_end(self, discard):
self.provider.span_end(discard)

@precondition(lambda self: not self.frozen)
@rule()
def freeze(self):
# phase-transition, mimicking data.freeze() at the end of a test case.
self.frozen = True
self.context_manager.__exit__(None, None, None)

@precondition(lambda self: self.frozen)
@rule(value=_realize_objects)
def realize(self, value):
# filter out nans and weirder things
try:
assume(value == value)
except Exception:
# e.g. value = Decimal('-sNaN')
assume(False)

# if `value` is non-symbolic, the provider should return it as-is.
assert self.provider.realize(value) == value

@precondition(lambda self: self.frozen)
@rule()
def observe_test_case(self):
observations = self.provider.observe_test_case()
assert isinstance(observations, dict)

@precondition(lambda self: self.frozen)
@rule(lifetime=st.sampled_from(["test_function", "test_case"]))
def observe_information_messages(self, lifetime):
observations = self.provider.observe_information_messages(lifetime=lifetime)
for observation in observations:
assert isinstance(observation, dict)

def teardown(self):
if not self.frozen:
choice = draw_func(**constraints)
note(f"drew {choice_type} {choice}")
expected_type = {
"integer": int,
"float": float,
"bytes": bytes,
"string": str,
"boolean": bool,
}[choice_type]
assert isinstance(choice, expected_type)
assert choice_permitted(choice, constraints)
except context_manager_exceptions as e:
note(
f"caught exception {type(e)} in context_manager_exceptions: {e}"
)
try:
self.context_manager.__exit__(type(e), e, None)
except BackendCannotProceed:
self.frozen = True
return None

return choice

@precondition(lambda self: not self.frozen)
@rule(constraints=integer_constraints())
def draw_integer(self, constraints):
self._draw("integer", constraints)

@precondition(lambda self: not self.frozen)
@rule(constraints=float_constraints())
def draw_float(self, constraints):
self._draw("float", constraints)

@precondition(lambda self: not self.frozen)
@rule(constraints=bytes_constraints())
def draw_bytes(self, constraints):
self._draw("bytes", constraints)

@precondition(lambda self: not self.frozen)
@rule(constraints=string_constraints())
def draw_string(self, constraints):
self._draw("string", constraints)

@precondition(lambda self: not self.frozen)
@rule(constraints=boolean_constraints())
def draw_boolean(self, constraints):
self._draw("boolean", constraints)

@precondition(lambda self: not self.frozen)
@rule(label=st.integers())
def span_start(self, label):
self.provider.span_start(label)

@precondition(lambda self: not self.frozen)
@rule(discard=st.booleans())
def span_end(self, discard):
self.provider.span_end(discard)

@precondition(lambda self: not self.frozen)
@rule()
def freeze(self):
# phase-transition, mimicking data.freeze() at the end of a test case.
self.frozen = True
self.context_manager.__exit__(None, None, None)

ProviderConformanceTest.TestCase().runTest()
@precondition(lambda self: self.frozen)
@rule(value=_realize_objects)
def realize(self, value):
# filter out nans and weirder things
try:
assume(value == value)
except Exception:
# e.g. value = Decimal('-sNaN')
assume(False)

# if `value` is non-symbolic, the provider should return it as-is.
assert self.provider.realize(value) == value

@precondition(lambda self: self.frozen)
@rule()
def observe_test_case(self):
observations = self.provider.observe_test_case()
assert isinstance(observations, dict)

@precondition(lambda self: self.frozen)
@rule(lifetime=st.sampled_from(["test_function", "test_case"]))
def observe_information_messages(self, lifetime):
observations = self.provider.observe_information_messages(
lifetime=lifetime
)
for observation in observations:
assert isinstance(observation, dict)

def teardown(self):
if not self.frozen:
self.context_manager.__exit__(None, None, None)

ProviderConformanceTest.TestCase().runTest()
Loading