Skip to content

Commit

Permalink
Merge pull request #3910 from Zac-HD/fix-lists-error
Browse files Browse the repository at this point in the history
Fix missing-`fill` problem in `arrays()`
  • Loading branch information
Zac-HD committed Mar 9, 2024
2 parents 059357d + 24be4fb commit e5113d9
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 5 deletions.
5 changes: 5 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RELEASE_TYPE: patch

This patch fixes :issue:`3900`, a performance regression for
:func:`~hypothesis.extra.numpy.arrays` due to the interaction of
:ref:`v6.98.12` and :ref:`v6.97.1`.
8 changes: 8 additions & 0 deletions hypothesis-python/src/hypothesis/extra/numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ def __init__(self, element_strategy, shape, dtype, fill, unique):
self.unique = unique
self._check_elements = dtype.kind not in ("O", "V")

def __repr__(self):
return (
f"ArrayStrategy({self.element_strategy!r}, shape={self.shape}, "
f"dtype={self.dtype!r}, fill={self.fill!r}, unique={self.unique!r})"
)

def set_element(self, val, result, idx, *, fill=False):
try:
result[idx] = val
Expand Down Expand Up @@ -547,6 +553,8 @@ def arrays(
unwrapped = unwrap_strategies(elements)
if isinstance(unwrapped, MappedStrategy) and unwrapped.pack == dtype.type:
elements = unwrapped.mapped_strategy
if getattr(unwrapped, "force_has_reusable_values", False):
elements.force_has_reusable_values = True # type: ignore
if isinstance(shape, int):
shape = (shape,)
shape = tuple(shape)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,18 @@ def __init__(self, elements, min_size=0, max_size=float("inf")):
self.min_size = min_size or 0
self.max_size = max_size if max_size is not None else float("inf")
assert 0 <= self.min_size <= self.max_size
if min_size > BUFFER_SIZE:
raise InvalidArgument(
f"min_size={min_size:_d} is larger than Hypothesis is designed to handle"
)
self.average_size = min(
max(self.min_size * 2, self.min_size + 5),
0.5 * (self.min_size + self.max_size),
)
self.element_strategy = elements
if min_size > BUFFER_SIZE:
raise InvalidArgument(
f"{self!r} can never generate an example, because min_size is larger "
"than Hypothesis supports. Including it is at best slowing down your "
"tests for no benefit; at worst making them fail (maybe flakily) with "
"a HealthCheck error."
)

def calc_label(self):
return combine_labels(self.class_label, self.element_strategy.label)
Expand Down Expand Up @@ -193,7 +196,7 @@ def do_draw(self, data):
return result

def __repr__(self):
return "{}({!r}, min_size={!r}, max_size={!r})".format(
return "{}({!r}, min_size={:_}, max_size={:_})".format(
self.__class__.__name__, self.element_strategy, self.min_size, self.max_size
)

Expand Down
17 changes: 17 additions & 0 deletions hypothesis-python/tests/numpy/test_gen_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
)
from hypothesis.errors import InvalidArgument, UnsatisfiedAssumption
from hypothesis.extra import numpy as nps
from hypothesis.strategies._internal.lazy import unwrap_strategies

from tests.common.debug import check_can_generate_examples, find_any, minimal
from tests.common.utils import fails_with, flaky
Expand Down Expand Up @@ -1240,3 +1241,19 @@ def test_no_recursion_in_multi_line_reprs_issue_3560(data):
dtype=float,
).map(lambda x: x)
)


def test_infers_elements_and_fill():
# Regression test for https://github.com/HypothesisWorks/hypothesis/issues/3900
# We only infer a fill strategy if the elements_strategy has reusable values,
# and the interaction of two performance fixes broke this. Oops...
s = unwrap_strategies(nps.arrays(dtype=np.uint32, shape=1))
assert isinstance(s, nps.ArrayStrategy)
assert repr(s.element_strategy) == f"integers(0, {2**32-1})"
assert repr(s.fill) == f"integers(0, {2**32-1})"

# But we _don't_ infer a fill if the elements strategy is non-reusable
elems = st.builds(lambda x: x * 2, st.integers(1, 10)).map(np.uint32)
assert not elems.has_reusable_values
s = unwrap_strategies(nps.arrays(dtype=np.uint32, shape=1, elements=elems))
assert s.fill.is_empty

0 comments on commit e5113d9

Please sign in to comment.