diff --git a/ci/requirements/environment.yaml b/ci/requirements/environment.yaml index af4f6f8..51bdd94 100644 --- a/ci/requirements/environment.yaml +++ b/ci/requirements/environment.yaml @@ -2,11 +2,11 @@ name: xarray-array-testing-tests channels: - conda-forge dependencies: - - python=3.12 - ipython - pre-commit - pytest - pytest-reportlog + - pytest-cov - hypothesis - xarray - numpy diff --git a/pyproject.toml b/pyproject.toml index cb76175..2d387a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xarray-array-testing" -requires-python = ">= 3.12" +requires-python = ">= 3.11" license = {text = "Apache-2.0"} dependencies = [ "hypothesis", @@ -51,7 +51,7 @@ ignore = [ "E501", # E501: line too long - let black worry about that "E731", # E731: do not assign a lambda expression, use a def ] -fixable = ["I"] +fixable = ["I", "TID"] extend-safe-fixes = [ "TID252", # absolute imports ] diff --git a/xarray_array_testing/base.py b/xarray_array_testing/base.py new file mode 100644 index 0000000..2d04dbd --- /dev/null +++ b/xarray_array_testing/base.py @@ -0,0 +1,27 @@ +import abc +from abc import ABC +from types import ModuleType + +import numpy.testing as npt +from xarray.namedarray._typing import duckarray + + +class DuckArrayTestMixin(ABC): + @property + @abc.abstractmethod + def xp() -> ModuleType: + pass + + @property + @abc.abstractmethod + def array_type(self) -> type[duckarray]: + pass + + @staticmethod + @abc.abstractmethod + def array_strategy_fn(*, shape, dtype): + raise NotImplementedError("has to be overridden") + + @staticmethod + def assert_equal(a, b): + npt.assert_equal(a, b) diff --git a/xarray_array_testing/creation.py b/xarray_array_testing/creation.py new file mode 100644 index 0000000..291e082 --- /dev/null +++ b/xarray_array_testing/creation.py @@ -0,0 +1,13 @@ +import hypothesis.strategies as st +import xarray.testing.strategies as xrst +from hypothesis import given + +from xarray_array_testing.base import DuckArrayTestMixin + + +class CreationTests(DuckArrayTestMixin): + @given(st.data()) + def test_create_variable(self, data): + variable = data.draw(xrst.variables(array_strategy_fn=self.array_strategy_fn)) + + assert isinstance(variable.data, self.array_type) diff --git a/xarray_array_testing/reduction.py b/xarray_array_testing/reduction.py new file mode 100644 index 0000000..8e58949 --- /dev/null +++ b/xarray_array_testing/reduction.py @@ -0,0 +1,33 @@ +from contextlib import nullcontext + +import hypothesis.strategies as st +import xarray.testing.strategies as xrst +from hypothesis import given + +from xarray_array_testing.base import DuckArrayTestMixin + + +class ReductionTests(DuckArrayTestMixin): + @staticmethod + def expected_errors(op, **parameters): + return nullcontext() + + @given(st.data()) + def test_variable_mean(self, data): + variable = data.draw(xrst.variables(array_strategy_fn=self.array_strategy_fn)) + + with self.expected_errors("mean", variable=variable): + actual = variable.mean().data + expected = self.xp.mean(variable.data) + + self.assert_equal(actual, expected) + + @given(st.data()) + def test_variable_prod(self, data): + variable = data.draw(xrst.variables(array_strategy_fn=self.array_strategy_fn)) + + with self.expected_errors("prod", variable=variable): + actual = variable.prod().data + expected = self.xp.prod(variable.data) + + self.assert_equal(actual, expected) diff --git a/xarray_array_testing/tests/test_numpy.py b/xarray_array_testing/tests/test_numpy.py new file mode 100644 index 0000000..2a9d95b --- /dev/null +++ b/xarray_array_testing/tests/test_numpy.py @@ -0,0 +1,34 @@ +from types import ModuleType + +import hypothesis.strategies as st +import numpy as np + +from xarray_array_testing.base import DuckArrayTestMixin +from xarray_array_testing.creation import CreationTests +from xarray_array_testing.reduction import ReductionTests + + +def create_numpy_array(*, shape, dtype): + return st.builds(np.ones, shape=st.just(shape), dtype=st.just(dtype)) + + +class NumpyTestMixin(DuckArrayTestMixin): + @property + def xp(self) -> ModuleType: + return np + + @property + def array_type(self) -> type[np.ndarray]: + return np.ndarray + + @staticmethod + def array_strategy_fn(*, shape, dtype): + return create_numpy_array(shape=shape, dtype=dtype) + + +class TestCreationNumpy(CreationTests, NumpyTestMixin): + pass + + +class TestReductionNumpy(ReductionTests, NumpyTestMixin): + pass