Skip to content

Commit

Permalink
Merge pull request #3123 from honno/arrays-shape-error
Browse files Browse the repository at this point in the history
Raise `InvalidArgument` with bad `shape` argument in `xps.arrays()`
  • Loading branch information
Zac-HD authored Oct 20, 2021
2 parents 4fe7e3c + d997b96 commit b66d75f
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 19 deletions.
4 changes: 4 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RELEASE_TYPE: patch

This patch adds an error for when ``shapes`` in :func:`xps.arrays()` is not
passed as either a valid shape or strategy.
15 changes: 7 additions & 8 deletions hypothesis-python/src/hypothesis/extra/array_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,16 +468,17 @@ def _arrays(
return dtype.flatmap(
lambda d: _arrays(xp, d, shape, elements=elements, fill=fill, unique=unique)
)
elif isinstance(dtype, str):
dtype = dtype_from_name(xp, dtype)

if isinstance(shape, st.SearchStrategy):
return shape.flatmap(
lambda s: _arrays(xp, dtype, s, elements=elements, fill=fill, unique=unique)
)

if isinstance(dtype, str):
dtype = dtype_from_name(xp, dtype)

if isinstance(shape, int):
elif isinstance(shape, int):
shape = (shape,)
elif not isinstance(shape, tuple):
raise InvalidArgument(f"shape={shape} is not a valid shape or strategy")
check_argument(
all(isinstance(x, int) and x >= 0 for x in shape),
f"shape={shape!r}, but all dimensions must be non-negative integers.",
Expand Down Expand Up @@ -584,7 +585,6 @@ def _unsigned_integer_dtypes(
``sizes`` contains the unsigned integer sizes in bits, defaulting to
``(8, 16, 32, 64)`` which covers all valid sizes.
"""

if isinstance(sizes, int):
sizes = (sizes,)
check_valid_sizes("int", sizes, (8, 16, 32, 64))
Expand All @@ -605,7 +605,6 @@ def _floating_dtypes(
``sizes`` contains the floating-point sizes in bits, defaulting to
``(32, 64)`` which covers all valid sizes.
"""

if isinstance(sizes, int):
sizes = (sizes,)
check_valid_sizes("int", sizes, (32, 64))
Expand Down Expand Up @@ -662,7 +661,7 @@ def indices(
allow_ellipsis: bool = True,
) -> st.SearchStrategy[BasicIndex]:
"""Return a strategy for :xp-ref:`valid indices <indexing.html>` of
arrays with the specified shape.
arrays with the specified shape, which may include dimensions of size zero.
It generates tuples containing some mix of integers, :obj:`python:slice`
objects, and ``...`` (an ``Ellipsis``). When a length-one tuple would be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def e(a, **kwargs):
e(xps.arrays, dtype=xp.int8, shape=(0.5,)),
e(xps.arrays, dtype=xp.int8, shape=1, fill=3),
e(xps.arrays, dtype=xp.int8, shape=1, elements="not a strategy"),
e(xps.arrays, dtype=xp.int8, shape="not a shape or strategy"),
e(xps.array_shapes, min_side=2, max_side=1),
e(xps.array_shapes, min_dims=3, max_dims=2),
e(xps.array_shapes, min_dims=-1),
Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/tests/array_api/test_from_dtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,4 @@ def test_from_dtype_with_kwargs(data, dtype, kwargs, predicate):
def test_can_minimize_floats():
"""Inferred float strategy minimizes to a good example."""
smallest = minimal(xps.from_dtype(xp.float32), lambda n: n >= 1.0)
assert smallest in (1, 50)
assert smallest == 1
30 changes: 20 additions & 10 deletions hypothesis-python/tests/array_api/test_indices.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,41 @@
from tests.common.debug import find_any


def test_indices_options():
indexers = (
def test_generate_indices_with_and_without_ellipsis():
"""Strategy can generate indices with and without Ellipsis."""
strat = (
xps.array_shapes(min_dims=1, max_dims=32)
.flatmap(xps.indices)
.map(lambda idx: idx if isinstance(idx, tuple) else (idx,))
)
find_any(indexers, lambda ix: Ellipsis in ix)
find_any(indexers, lambda ix: Ellipsis not in ix)
find_any(strat, lambda ix: Ellipsis in ix)
find_any(strat, lambda ix: Ellipsis not in ix)


def test_indices_can_generate_empty_tuple():
def test_generate_empty_tuple():
"""Strategy generates empty tuples as indices."""
find_any(xps.indices(shape=(0, 0), allow_ellipsis=True), lambda ix: ix == ())


def test_indices_can_generate_non_tuples():
def test_generate_non_tuples():
"""Strategy generates non-tuples as indices."""
find_any(
xps.indices(shape=(0, 0), allow_ellipsis=True),
lambda ix: not isinstance(ix, tuple),
)


def test_indices_can_generate_long_ellipsis():
# Runs of slice(None) - such as [0,:,:,:,0] - can be replaced by e.g. [0,...,0]
def test_generate_long_ellipsis():
"""Strategy can replace runs of slice(None) with Ellipsis.
We specifically test if [0,...,0] is generated alongside [0,:,:,:,0]
"""
strat = xps.indices(shape=(1, 0, 0, 0, 1), max_dims=3, allow_ellipsis=True)
find_any(strat, lambda ix: len(ix) == 3 and ix[1] == Ellipsis)
find_any(
xps.indices(shape=(1, 0, 0, 0, 1), max_dims=3, allow_ellipsis=True),
lambda ix: len(ix) == 3 and ix[1] == Ellipsis,
strat,
lambda ix: len(ix) == 5
and all(isinstance(key, slice) and key == slice(None) for key in ix[1:3]),
)


Expand Down Expand Up @@ -74,6 +83,7 @@ def test_indices_effeciently_generate_indexers(_):
data=st.data(),
)
def test_indices_generate_valid_indexers(shape, allow_ellipsis, data):
"""Strategy generates valid indices."""
min_dims = data.draw(st.integers(0, len(shape)), label="min_dims")
max_dims = data.draw(
st.none() | st.integers(min_dims, len(shape)), label="max_dims"
Expand Down

0 comments on commit b66d75f

Please sign in to comment.