Skip to content

Commit

Permalink
Mocked array module in extra.array_api
Browse files Browse the repository at this point in the history
  • Loading branch information
honno committed Aug 31, 2021
1 parent e9a9c55 commit 4cd19d9
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 79 deletions.
43 changes: 43 additions & 0 deletions hypothesis-python/src/hypothesis/extra/array_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -850,3 +850,46 @@ def floating_dtypes(
mutually_broadcastable_shapes=mutually_broadcastable_shapes,
indices=indices,
)


try:
import numpy as np

mock_xp = SimpleNamespace(
__name__="mockpy",
# Data types
int8=np.int8,
int16=np.int16,
int32=np.int32,
int64=np.int64,
uint8=np.uint8,
uint16=np.uint16,
uint32=np.uint32,
uint64=np.uint64,
float32=np.float32,
float64=np.float64,
bool=np.bool_,
# Methods
iinfo=np.iinfo,
finfo=np.finfo,
asarray=np.asarray,
reshape=np.reshape,
empty=np.empty,
zeros=np.zeros,
ones=np.ones,
arange=np.arange,
full=np.full,
any=np.any,
all=np.all,
isfinite=np.isfinite,
nonzero=np.nonzero,
unique=np.unique,
sum=np.sum,
isnan=np.isnan,
broadcast_arrays=np.broadcast_arrays,
logical_or=np.logical_or,
# Constants
nan=np.nan,
)
except ImportError:
pass
Original file line number Diff line number Diff line change
Expand Up @@ -13,73 +13,21 @@
#
# END HEADER

from copy import copy
from functools import lru_cache
from types import SimpleNamespace
from typing import Tuple

import numpy as np
import pytest

from hypothesis.errors import HypothesisWarning
from hypothesis.extra.array_api import make_strategies_namespace
from hypothesis.extra.array_api import make_strategies_namespace, mock_xp

__all__ = [
"xp",
"xps",
"create_array_module",
"MOCK_NAME",
"MOCK_WARN_MSG",
"COMPLIANT_XP",
]


MOCK_NAME = "mockpy"

ATTRIBUTES = {
"__name__": MOCK_NAME,
# Data types
"int8": np.int8,
"int16": np.int16,
"int32": np.int32,
"int64": np.int64,
"uint8": np.uint8,
"uint16": np.uint16,
"uint32": np.uint32,
"uint64": np.uint64,
"float32": np.float32,
"float64": np.float64,
"bool": np.bool_,
# Methods
"iinfo": np.iinfo,
"finfo": np.finfo,
"asarray": np.asarray,
"reshape": np.reshape,
"empty": np.empty,
"zeros": np.zeros,
"ones": np.ones,
"arange": np.arange,
"full": np.full,
"any": np.any,
"all": np.all,
"isfinite": np.isfinite,
"nonzero": np.nonzero,
"unique": np.unique,
"sum": np.sum,
"isnan": np.isnan,
"broadcast_arrays": np.broadcast_arrays,
"logical_or": np.logical_or,
# Constants
"nan": np.nan,
}


@lru_cache()
def create_array_module(*, exclude: Tuple[str, ...] = ()) -> SimpleNamespace:
attributes = copy(ATTRIBUTES)
for attr in exclude:
del attributes[attr]
return SimpleNamespace(**attributes)

MOCK_WARN_MSG = f"determine.*{mock_xp.__name__}.*Array API"

# We try importing the Array API namespace from NumPy first, which modern
# versions should include. If not available we default to our own mocked module,
Expand All @@ -91,7 +39,7 @@ def create_array_module(*, exclude: Tuple[str, ...] = ()) -> SimpleNamespace:
xps = make_strategies_namespace(xp)
COMPLIANT_XP = True
except ImportError:
xp = create_array_module()
xp = mock_xp
with pytest.warns(HypothesisWarning):
xps = make_strategies_namespace(xp)
COMPLIANT_XP = False
8 changes: 3 additions & 5 deletions hypothesis-python/tests/array_api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,18 @@

import pytest

from tests.array_api.xputils import COMPLIANT_XP, MOCK_NAME
from tests.array_api.common import COMPLIANT_XP, MOCK_WARN_MSG


def pytest_configure(config):
config.addinivalue_line(
"markers", "mockable_xp: mocked array module may be used in test"
"markers", "mockable_xp: mocked array module possibly used in test"
)


def pytest_collection_modifyitems(config, items):
if not COMPLIANT_XP:
mark = pytest.mark.filterwarnings(
f"ignore:.*determine.*{MOCK_NAME}.*Array API.*"
)
mark = pytest.mark.filterwarnings(f"ignore:.*{MOCK_WARN_MSG}.*")
for item in items:
if "mockable_xp" in item.keywords:
item.add_marker(mark)
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from hypothesis.errors import InvalidArgument

from tests.array_api.xputils import xp, xps
from tests.array_api.common import xp, xps

pytestmark = [pytest.mark.mockable_xp]

Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/tests/array_api/test_arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from hypothesis.errors import InvalidArgument, Unsatisfiable
from hypothesis.extra.array_api import DTYPE_NAMES, NUMERIC_NAMES

from tests.array_api.xputils import COMPLIANT_XP, xp, xps
from tests.array_api.common import COMPLIANT_XP, xp, xps
from tests.common.debug import find_any, minimal
from tests.common.utils import fails_with, flaky

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 @@ -20,7 +20,7 @@
from hypothesis import given, strategies as st
from hypothesis.extra.array_api import DTYPE_NAMES, find_castable_builtin_for_dtype

from tests.array_api.xputils import xp, xps
from tests.array_api.common import xp, xps
from tests.common.debug import minimal

pytestmark = [pytest.mark.mockable_xp]
Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/tests/array_api/test_indices.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from hypothesis import assume, given, strategies as st

from tests.array_api.xputils import xp, xps
from tests.array_api.common import xp, xps
from tests.common.debug import find_any

pytestmark = [pytest.mark.mockable_xp]
Expand Down
38 changes: 26 additions & 12 deletions hypothesis-python/tests/array_api/test_partial_adoptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
#
# END HEADER

from copy import copy
from functools import lru_cache
from types import SimpleNamespace
from typing import Tuple

import pytest

from hypothesis import given, strategies as st
Expand All @@ -23,39 +28,48 @@
INT_NAMES,
UINT_NAMES,
make_strategies_namespace,
mock_xp,
)

from tests.array_api.xputils import MOCK_NAME, create_array_module
from tests.array_api.common import MOCK_WARN_MSG


@lru_cache()
def make_mock_xp(exclude: Tuple[str, ...] = ()) -> SimpleNamespace:
xp = copy(mock_xp)
for attr in exclude:
delattr(xp, attr)
return xp


def test_warning_on_noncompliant_xp():
"""Using non-compliant array modules raises helpful warning"""
xp = create_array_module()
with pytest.warns(HypothesisWarning, match=f"determine.*{MOCK_NAME}.*Array API"):
xp = make_mock_xp()
with pytest.warns(HypothesisWarning, match=MOCK_WARN_MSG):
make_strategies_namespace(xp)


@pytest.mark.filterwarnings(f"ignore:.*determine.*{MOCK_NAME}.*Array API.*")
@pytest.mark.filterwarnings(f"ignore:.*{MOCK_WARN_MSG}.*")
@pytest.mark.parametrize(
"stratname, args, attr",
[("from_dtype", ["int8"], "iinfo"), ("arrays", ["int8", 5], "full")],
)
def test_error_on_missing_attr(stratname, args, attr):
"""Strategies raise helpful error when using array modules that lack
required attributes."""
xp = create_array_module(exclude=(attr,))
xp = make_mock_xp(exclude=(attr,))
xps = make_strategies_namespace(xp)
func = getattr(xps, stratname)
with pytest.raises(InvalidArgument, match=f"{MOCK_NAME}.*required.*{attr}"):
with pytest.raises(InvalidArgument, match=f"{mock_xp.__name__}.*required.*{attr}"):
func(*args).example()


dtypeless_xp = create_array_module(exclude=tuple(DTYPE_NAMES))
dtypeless_xp = make_mock_xp(exclude=tuple(DTYPE_NAMES))
with pytest.warns(HypothesisWarning):
dtypeless_xps = make_strategies_namespace(dtypeless_xp)


@pytest.mark.filterwarnings(f"ignore:.*determine.*{MOCK_NAME}.*Array API.*")
@pytest.mark.filterwarnings(f"ignore:.*{MOCK_WARN_MSG}.*")
@pytest.mark.parametrize(
"stratname",
[
Expand All @@ -71,11 +85,11 @@ def test_error_on_missing_dtypes(stratname):
"""Strategies raise helpful error when using array modules that lack
required dtypes."""
func = getattr(dtypeless_xps, stratname)
with pytest.raises(InvalidArgument, match=f"{MOCK_NAME}.*dtype.*namespace"):
with pytest.raises(InvalidArgument, match=f"{mock_xp.__name__}.*dtype.*namespace"):
func().example()


@pytest.mark.filterwarnings(f"ignore:.*determine.*{MOCK_NAME}.*Array API.*")
@pytest.mark.filterwarnings(f"ignore:.*{MOCK_WARN_MSG}.*")
@pytest.mark.parametrize(
"stratname, keep_anys",
[
Expand All @@ -102,8 +116,8 @@ def test_warning_on_partial_dtypes(stratname, keep_anys, data):
)
)
)
xp = create_array_module(exclude=tuple(exclude))
xp = make_mock_xp(exclude=tuple(exclude))
xps = make_strategies_namespace(xp)
func = getattr(xps, stratname)
with pytest.warns(HypothesisWarning, match=f"{MOCK_NAME}.*dtype.*namespace"):
with pytest.warns(HypothesisWarning, match=f"{mock_xp.__name__}.*dtype.*namespace"):
data.draw(func())
2 changes: 1 addition & 1 deletion hypothesis-python/tests/array_api/test_pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import pytest

from tests.array_api.xputils import xp, xps
from tests.array_api.common import xp, xps

pytestmark = [pytest.mark.mockable_xp]

Expand Down
2 changes: 1 addition & 1 deletion hypothesis-python/tests/array_api/test_scalar_dtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from hypothesis import given
from hypothesis.extra.array_api import DTYPE_NAMES, INT_NAMES, NUMERIC_NAMES, UINT_NAMES

from tests.array_api.xputils import xp, xps
from tests.array_api.common import xp, xps
from tests.common.debug import minimal

pytestmark = [pytest.mark.mockable_xp]
Expand Down

0 comments on commit 4cd19d9

Please sign in to comment.