Skip to content

Commit

Permalink
Merge pull request #2025 from Zac-HD/example-on-given
Browse files Browse the repository at this point in the history
Implement `s.example()` in terms of `@given`, not `find()`
  • Loading branch information
Zac-HD committed Jul 8, 2019
2 parents 1731e66 + 7ac95ac commit a405c1c
Show file tree
Hide file tree
Showing 13 changed files with 76 additions and 79 deletions.
9 changes: 9 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
RELEASE_TYPE: minor

This release refactors the implementation of the ``.example()`` method,
to more accurately represent the data which will be generated by
:func:`@given <hypothesis.given>`.

As a result, calling ``s.example()`` on an empty strategy ``s``
(such as :func:`~hypothesis.strategies.nothing`) now raises ``Unsatisfiable``
instead of the old ``NoExamples`` exception.
6 changes: 0 additions & 6 deletions hypothesis-python/src/hypothesis/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,6 @@ def __init__(self, condition_string, extra=""):
)


class NoExamples(HypothesisException):
"""Raised when example() is called on a strategy but we cannot find any
examples after enough tries that we really should have been able to if this
was ever going to work."""


class Unsatisfiable(HypothesisException):
"""We ran out of time or examples before we could find enough examples
which satisfy the assumptions of this hypothesis.
Expand Down
75 changes: 34 additions & 41 deletions hypothesis-python/src/hypothesis/searchstrategy/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@
from __future__ import absolute_import, division, print_function

from collections import defaultdict
from random import choice as random_choice

import hypothesis.internal.conjecture.utils as cu
from hypothesis._settings import Phase
from hypothesis.control import _current_build_context, assume
from hypothesis.errors import (
HypothesisException,
NoExamples,
NoSuchExample,
Unsatisfiable,
UnsatisfiedAssumption,
from hypothesis._settings import (
HealthCheck,
Phase,
Verbosity,
not_set,
note_deprecation,
settings,
)
from hypothesis.control import _current_build_context, assume
from hypothesis.errors import HypothesisException, UnsatisfiedAssumption
from hypothesis.internal.compat import bit_length, hrange
from hypothesis.internal.conjecture.utils import (
LABEL_MASK,
Expand All @@ -43,12 +45,12 @@
from hypothesis.utils.conventions import UniqueIdentifier

try:
from random import Random # noqa
from typing import List, Callable, TypeVar, Generic, Optional # noqa

Ex = TypeVar("Ex", covariant=True)
T = TypeVar("T")

from hypothesis._settings import UniqueIdentifier # noqa
from hypothesis.internal.conjecture.data import ConjectureData # noqa

except ImportError: # pragma: no cover
Expand Down Expand Up @@ -252,8 +254,8 @@ def calc_is_empty(self, recur):
def calc_has_reusable_values(self, recur):
return False

def example(self, random=None):
# type: (Random) -> Ex
def example(self, random=not_set):
# type: (UniqueIdentifier) -> Ex
"""Provide an example of the sort of value that this strategy
generates. This is biased to be slightly simpler than is typical for
values from this strategy, for clarity purposes.
Expand All @@ -263,6 +265,9 @@ def example(self, random=None):
This method is part of the public API.
"""
if random is not not_set:
note_deprecation("The random argument does nothing", since="RELEASEDAY")

context = _current_build_context.value
if context is not None:
if context.data is not None and context.data.depth > 0:
Expand All @@ -284,37 +289,25 @@ def example(self, random=None):
"#drawing-interactively-in-tests for more details."
)

from hypothesis import find, settings, Verbosity

# Conjecture will always try the zero example first. This would result
# in us producing the same example each time, which is boring, so we
# deliberately skip the first example it feeds us.
first = [] # type: list

def condition(x):
if first:
return True
else:
first.append(x)
return False
from hypothesis.core import given

# Note: this function has a weird name because it might appear in
# tracebacks, and we want users to know that they can ignore it.
@given(self)
@settings(
database=None,
max_examples=10,
deadline=None,
verbosity=Verbosity.quiet,
phases=(Phase.generate,),
suppress_health_check=HealthCheck.all(),
)
def example_generating_inner_function(ex):
examples.append(ex)

try:
return find(
self,
condition,
random=random,
settings=settings(
database=None,
verbosity=Verbosity.quiet,
phases=tuple(set(Phase) - {Phase.shrink}),
),
)
except (NoSuchExample, Unsatisfiable):
# This can happen when a strategy has only one example. e.g.
# st.just(x). In that case we wanted the first example after all.
if first:
return first[0]
raise NoExamples(u"Could not find any valid examples in 100 tries")
examples = [] # type: List[Ex]
example_generating_inner_function()
return random_choice(examples)

def map(self, pack):
# type: (Callable[[Ex], T]) -> SearchStrategy[T]
Expand Down
4 changes: 4 additions & 0 deletions hypothesis-python/tests/cover/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,7 @@ def inner(x):
with pytest.raises(Skipped):
inner()
assert len([x for x in values if x > 100]) == 1


def test_can_find_with_db_eq_none():
find(s.integers(), bool, settings(database=None))
5 changes: 5 additions & 0 deletions hypothesis-python/tests/cover/test_deferred_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,8 @@ def test_recursion_in_middle():
# to determine the non-emptiness of the tuples.
x = st.deferred(lambda: st.tuples(st.none(), x, st.integers().map(abs)) | st.none())
assert not x.is_empty


def test_deferred_supports_find():
nested = st.deferred(lambda: st.integers() | st.lists(nested))
assert nested.supports_find
19 changes: 7 additions & 12 deletions hypothesis-python/tests/cover/test_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,14 @@
import pytest

import hypothesis.strategies as st
from hypothesis import example, find, given, settings
from hypothesis.control import _current_build_context
from hypothesis.errors import HypothesisException, NoExamples
from tests.common.utils import fails_with
from hypothesis import example, find, given
from hypothesis.errors import HypothesisException, Unsatisfiable
from tests.common.utils import checks_deprecated_behaviour, fails_with


@settings(deadline=None)
@given(st.integers())
def test_deterministic_examples_are_deterministic(seed):
with _current_build_context.with_value(None):
assert st.lists(st.integers()).example(Random(seed)) == st.lists(
st.integers()
).example(Random(seed))
@checks_deprecated_behaviour
def test_deterministic_examples_are_deprecated():
st.integers().example(Random())


def test_example_of_none_is_none():
Expand All @@ -52,7 +47,7 @@ def test_does_not_always_give_the_same_example():


def test_raises_on_no_examples():
with pytest.raises(NoExamples):
with pytest.raises(Unsatisfiable):
st.nothing().example()


Expand Down
5 changes: 0 additions & 5 deletions hypothesis-python/tests/cover/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,6 @@ def test_functions_lambda_with_arg(f):
assert isinstance(f(1), bool)


def test_functions_example_is_invalid():
with pytest.raises(InvalidArgument):
functions().example()


@pytest.mark.parametrize("like,returns", [(None, booleans()), (lambda: None, None)])
def test_invalid_arguments(like, returns):
with pytest.raises(InvalidArgument):
Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/tests/cover/test_slices.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def test_slices_will_shrink(size):
@given(st.integers(1, 1000))
@settings(deadline=None)
def test_step_will_be_negative(size):
find_any(st.slices(size), lambda x: x.step < 0)
find_any(st.slices(size), lambda x: x.step < 0, settings(max_examples=10 ** 6))


@given(st.integers(1, 1000))
Expand Down
16 changes: 9 additions & 7 deletions hypothesis-python/tests/nocover/test_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,28 @@

from __future__ import absolute_import, division, print_function

import warnings
import pytest

import hypothesis.strategies as st
from hypothesis import given
from hypothesis._settings import note_deprecation
from hypothesis.errors import HypothesisDeprecationWarning
from hypothesis.strategies import composite, integers


def test_note_deprecation_blames_right_code_issue_652():
msg = "this is an arbitrary deprecation warning message"

@composite
@st.composite
def deprecated_strategy(draw):
draw(integers())
draw(st.none())
note_deprecation(msg, since="RELEASEDAY")

with warnings.catch_warnings(record=True) as log:
warnings.simplefilter("always")
deprecated_strategy().example()
@given(deprecated_strategy())
def f(x):
pass

with pytest.warns(HypothesisDeprecationWarning) as log:
f()

assert len(log) == 1
record, = log
Expand Down
4 changes: 2 additions & 2 deletions hypothesis-python/tests/numpy/test_gen_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import hypothesis.extra.numpy as nps
import hypothesis.strategies as st
from hypothesis import assume, given, note, settings
from hypothesis import HealthCheck, assume, given, note, settings
from hypothesis.errors import InvalidArgument
from hypothesis.internal.compat import binary_type, text_type
from hypothesis.searchstrategy import SearchStrategy
Expand Down Expand Up @@ -517,7 +517,7 @@ def test_minimize_negative_tuple_axes(ndim, data):
assert len(smallest) == min_size


@settings(deadline=None)
@settings(deadline=None, suppress_health_check=[HealthCheck.too_slow])
@given(
shape=nps.array_shapes(min_side=0, max_side=4, min_dims=0, max_dims=3),
data=st.data(),
Expand Down
4 changes: 2 additions & 2 deletions hypothesis-python/tests/pandas/test_indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import hypothesis.extra.pandas as pdst
import hypothesis.strategies as st
from hypothesis import HealthCheck, given, reject, settings
from hypothesis.errors import NoExamples
from hypothesis.errors import Unsatisfiable
from tests.pandas.helpers import supported_by_pandas


Expand All @@ -40,7 +40,7 @@ def test_gets_right_dtype_for_empty_indices_with_elements(ix):


def test_does_not_generate_impossible_conditions():
with pytest.raises(NoExamples):
with pytest.raises(Unsatisfiable):
pdst.indexes(min_size=3, max_size=3, dtype=bool).example()


Expand Down
4 changes: 2 additions & 2 deletions hypothesis-python/tests/py3/test_lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

import hypothesis.strategies as st
from hypothesis import HealthCheck, assume, given, infer, settings
from hypothesis.errors import InvalidArgument, NoExamples, ResolutionFailed
from hypothesis.errors import InvalidArgument, ResolutionFailed, Unsatisfiable
from hypothesis.internal.compat import (
ForwardRef,
get_type_hints,
Expand Down Expand Up @@ -173,7 +173,7 @@ def test_variable_length_tuples(n):
type_ = typing.Tuple[int, ...]
try:
from_type(type_).filter(lambda ex: len(ex) == n).example()
except NoExamples:
except Unsatisfiable:
if sys.version_info[:2] < (3, 6):
pytest.skip()
raise
Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ commands =
python -m coverage --version
python -m coverage debug sys
python -m coverage run --rcfile=.coveragerc -m pytest -n0 --strict tests/cover tests/datetime tests/py3 tests/numpy tests/pandas tests/lark --ff {posargs}
python -m coverage report -m --fail-under=100 --show-missing
python -m coverage report -m --fail-under=100 --show-missing --skip-covered
python scripts/validate_branch_check.py


Expand Down

0 comments on commit a405c1c

Please sign in to comment.