Skip to content

Commit

Permalink
More coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
Zac-HD committed Nov 19, 2023
1 parent 03f9eb6 commit 71f38a3
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 42 deletions.
59 changes: 30 additions & 29 deletions hypothesis-python/src/hypothesis/internal/conjecture/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
)
from hypothesis.internal.floats import (
SIGNALING_NAN,
SMALLEST_SUBNORMAL,
float_to_int,
make_float_clamper,
next_down,
Expand Down Expand Up @@ -1001,8 +1002,17 @@ def draw_integer(

sampler = Sampler(weights)
idx = sampler.sample(self._cd)
assert shrink_towards <= min_value # FIXME: reorder for good shrinking
return range(min_value, max_value + 1)[idx]

if shrink_towards <= min_value:
return min_value + idx
elif max_value <= shrink_towards:
return max_value - idx
else:
# For range -2..2, interpret idx = 0..4 as [0, 1, 2, -1, -2]
if idx <= (gap := max_value - shrink_towards):
return shrink_towards + idx
else:
return shrink_towards - (idx - gap)

if min_value is None and max_value is None:
return self._draw_unbounded_integer()
Expand Down Expand Up @@ -1041,20 +1051,15 @@ def draw_integer(
def draw_float(
self,
*,
min_value: Optional[float] = None,
max_value: Optional[float] = None,
min_value: float = -math.inf,
max_value: float = math.inf,
allow_nan: bool = True,
smallest_nonzero_magnitude: float,
# TODO: consider supporting these float widths at the IR level in the
# future.
# width: Literal[16, 32, 64] = 64,
# exclude_min and exclude_max handled higher up
) -> float:
if min_value is None:
min_value = float("-inf")
if max_value is None:
max_value = float("inf")

(
sampler,
forced_sign_bit,
Expand All @@ -1074,8 +1079,7 @@ def draw_float(
self._cd.start_example(DRAW_FLOAT_LABEL)
if i == 0:
result = self._draw_float(forced_sign_bit=forced_sign_bit)
is_negative = float_to_int(result) >> 63
if is_negative:
if math.copysign(1.0, result) == -1:
assert neg_clamper is not None
clamped = -neg_clamper(-result)
else:
Expand All @@ -1100,10 +1104,10 @@ def draw_string(
intervals: IntervalSet,
*,
min_size: int = 0,
max_size: Optional[Union[int, float]] = None,
max_size: Optional[int] = None,
) -> str:
if max_size is None:
max_size = float("inf")
max_size = 10**10 # "arbitrarily large"

average_size = min(
max(min_size * 2, min_size + 5),
Expand All @@ -1118,7 +1122,15 @@ def draw_string(
average_size=average_size,
)
while elements.more():
chars.append(self._draw_character(intervals))
if len(intervals) > 256:
if self.draw_boolean(0.2):
i = self._draw_bounded_integer(256, len(intervals) - 1)
else:
i = self._draw_bounded_integer(0, 255)
else:
i = self._draw_bounded_integer(0, len(intervals) - 1)

chars.append(intervals.char_in_shrink_order(i))

return "".join(chars)

Expand All @@ -1129,9 +1141,8 @@ def _draw_float(self, forced_sign_bit: Optional[int] = None) -> float:
"""
Helper for draw_float which draws a random 64-bit float.
"""
self._cd.start_example(DRAW_FLOAT_LABEL)
try:
# FIXME: move start_example out of the try block
self._cd.start_example(DRAW_FLOAT_LABEL)
is_negative = self._cd.draw_bits(1, forced=forced_sign_bit)
f = lex_to_float(self._cd.draw_bits(64))
return -f if is_negative else f
Expand All @@ -1143,17 +1154,6 @@ def _write_float(self, f: float) -> None:
self._cd.draw_bits(1, forced=sign)
self._cd.draw_bits(64, forced=float_to_lex(abs(f)))

def _draw_character(self, intervals: IntervalSet) -> str:
if len(intervals) > 256:
if self.draw_boolean(0.2):
i = self._draw_bounded_integer(256, len(intervals) - 1)
else:
i = self._draw_bounded_integer(0, 255)
else:
i = self._draw_bounded_integer(0, len(intervals) - 1)

return intervals.char_in_shrink_order(i)

def _draw_unbounded_integer(self) -> int:
size = INT_SIZES[INT_SIZES_SAMPLER.sample(self._cd)]
r = self._cd.draw_bits(size)
Expand Down Expand Up @@ -1442,16 +1442,17 @@ def draw_integer(

def draw_float(
self,
*,
min_value: Optional[float] = None,
max_value: Optional[float] = None,
*,
allow_nan: bool = True,
smallest_nonzero_magnitude: float,
smallest_nonzero_magnitude: float = SMALLEST_SUBNORMAL,
# TODO: consider supporting these float widths at the IR level in the
# future.
# width: Literal[16, 32, 64] = 64,
# exclude_min and exclude_max handled higher up
) -> float:
assert smallest_nonzero_magnitude > 0
return self.provider.draw_float(
min_value=min_value,
max_value=max_value,
Expand Down
20 changes: 7 additions & 13 deletions hypothesis-python/src/hypothesis/strategies/_internal/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
next_down_normal,
next_up,
next_up_normal,
sign_aware_lte,
width_smallest_normals,
)
from hypothesis.internal.validation import (
Expand Down Expand Up @@ -186,14 +185,6 @@ def __repr__(self):
self.smallest_nonzero_magnitude,
)

def permitted(self, f):
assert isinstance(f, float)
if math.isnan(f):
return self.allow_nan
if 0 < abs(f) < self.smallest_nonzero_magnitude:
return False
return sign_aware_lte(self.min_value, f) and sign_aware_lte(f, self.max_value)

def do_draw(self, data):
return data.draw_float(
min_value=self.min_value,
Expand All @@ -212,10 +203,13 @@ def filter(self, condition):
smallest_nonzero_magnitude=self.smallest_nonzero_magnitude,
)
if condition is math.isinf:
permitted_infs = [x for x in (-math.inf, math.inf) if self.permitted(x)]
if not permitted_infs:
return nothing()
return SampledFromStrategy(permitted_infs)
if permitted_infs := [
x
for x in (-math.inf, math.inf)
if self.min_value <= x <= self.max_value
]:
return SampledFromStrategy(permitted_infs)
return nothing()
if condition is math.isnan:
if not self.allow_nan:
return nothing()
Expand Down
9 changes: 9 additions & 0 deletions hypothesis-python/tests/conjecture/test_test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ def test_can_mark_invalid():
assert x.status == Status.INVALID


@given(st.data(), st.integers(1, 100))
def test_can_draw_weighted_integer_range(data, n):
weights = [1] * n + [0] * n
for _ in range(10):
# If the weights are working, then we'll never draw a value with weight=0
x = data.conjecture_data.draw_integer(1, 2 * n, weights=weights)
assert x <= n


def test_can_mark_invalid_with_why():
x = ConjectureData.for_buffer(b"")
with pytest.raises(StopTest):
Expand Down
27 changes: 27 additions & 0 deletions hypothesis-python/tests/conjecture/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from hypothesis.internal.conjecture import utils as cu
from hypothesis.internal.conjecture.data import ConjectureData, Status, StopTest
from hypothesis.internal.coverage import IN_COVERAGE_TESTS
from hypothesis.internal.intervalsets import IntervalSet


def test_does_draw_data_for_empty_range():
Expand Down Expand Up @@ -186,6 +187,32 @@ def test_restricted_bits():
assert data.draw_integer(0, 2**64 - 1) == 0


@pytest.mark.parametrize(
"lo,hi,to",
[(0, None, 0), (None, 1, 0), (None, 1, 1), (-1, 1, 0)],
)
def test_single_bounds(lo, hi, to):
data = ConjectureData.for_buffer([0] * 100)
assert data.draw_integer(lo, hi, shrink_towards=to) == to


def test_draw_string():
data = ConjectureData.for_buffer([0] * 10)
assert data.draw_string(IntervalSet([(0, 1024)]), min_size=1) == "0"


def test_draw_float():
data = ConjectureData.for_buffer([0] * 16)
x = data.draw_float(0.0, 2.0, allow_nan=False, smallest_nonzero_magnitude=1.0)
assert x == 0 or 1 <= x <= 2


def test_draw_negative_float():
data = ConjectureData.for_buffer([0] * 100)
x = data.draw_float(-2.0, -1.0, allow_nan=False, smallest_nonzero_magnitude=0.5)
assert -2 <= x <= -1


def test_sampler_shrinks():
sampler = cu.Sampler([4.0, 8.0, 1.0, 1.0, 0.5])
assert sampler.sample(ConjectureData.for_buffer([0] * 3)) == 0
Expand Down

0 comments on commit 71f38a3

Please sign in to comment.