diff --git a/tests/__init__.py b/tests/__init__.py index 4c04a0fc8..3e43e94d6 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,5 @@ import importlib -from contextlib import contextmanager +from contextlib import nullcontext import numpy as np import packaging.version @@ -46,6 +46,7 @@ def LooseVersion(vstring): has_dask, requires_dask = _importorskip("dask") +has_numba, requires_numba = _importorskip("numba") has_xarray, requires_xarray = _importorskip("xarray") @@ -67,15 +68,10 @@ def __call__(self, dsk, keys, **kwargs): return dask.get(dsk, keys, **kwargs) -@contextmanager -def dummy_context(): - yield None - - def raise_if_dask_computes(max_computes=0): # return a dummy context manager so that this can be used for non-dask objects if not has_dask: - return dummy_context() + return nullcontext() scheduler = CountingScheduler(max_computes) return dask.config.set(scheduler=scheduler) diff --git a/tests/conftest.py b/tests/conftest.py index 5c3bb81f6..eb5971784 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,10 @@ import pytest +from . import requires_numba -@pytest.fixture(scope="module", params=["flox", "numpy", "numba"]) + +@pytest.fixture( + scope="module", params=["flox", "numpy", pytest.param("numba", marks=requires_numba)] +) def engine(request): - if request.param == "numba": - try: - import numba # noqa - except ImportError: - pytest.xfail() return request.param diff --git a/tests/test_core.py b/tests/test_core.py index 83b823b07..453958b2d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -105,7 +105,7 @@ def test_alignment_error(): @pytest.mark.parametrize("dtype", (float, int)) -@pytest.mark.parametrize("chunk", [False, True]) +@pytest.mark.parametrize("chunk", [False, pytest.param(True, marks=requires_dask)]) # TODO: make this intp when python 3.8 is dropped @pytest.mark.parametrize("expected_groups", [None, [0, 1, 2], np.array([0, 1, 2], dtype=np.int64)]) @pytest.mark.parametrize( @@ -145,7 +145,7 @@ def test_groupby_reduce( ) -> None: array = array.astype(dtype) if chunk: - if not has_dask or expected_groups is None: + if expected_groups is None: pytest.skip() array = da.from_array(array, chunks=(3,) if array.ndim == 1 else (1, 3)) by = da.from_array(by, chunks=(3,) if by.ndim == 1 else (1, 3)) @@ -166,7 +166,7 @@ def test_groupby_reduce( engine=engine, ) # we use pd.Index(expected_groups).to_numpy() which is always int64 - # for the values in this tests + # for the values in this test if expected_groups is None: g_dtype = by.dtype elif isinstance(expected_groups, np.ndarray): @@ -191,14 +191,20 @@ def gen_array_by(size, func): return array, by -@pytest.mark.parametrize("chunks", [None, -1, 3, 4]) +@pytest.mark.parametrize( + "chunks", + [ + None, + pytest.param(-1, marks=requires_dask), + pytest.param(3, marks=requires_dask), + pytest.param(4, marks=requires_dask), + ], +) @pytest.mark.parametrize("nby", [1, 2, 3]) @pytest.mark.parametrize("size", ((12,), (12, 9))) @pytest.mark.parametrize("add_nan_by", [True, False]) @pytest.mark.parametrize("func", ALL_FUNCS) def test_groupby_reduce_all(nby, size, chunks, func, add_nan_by, engine): - if chunks is not None and not has_dask: - pytest.skip() if "arg" in func and engine == "flox": pytest.skip() @@ -390,16 +396,16 @@ def test_numpy_reduce_nd_md(): def test_groupby_agg_dask(func, shape, array_chunks, group_chunks, add_nan, dtype, engine, reindex): """Tests groupby_reduce with dask arrays against groupby_reduce with numpy arrays""" - rng = np.random.default_rng(12345) - array = dask.array.from_array(rng.random(shape), chunks=array_chunks).astype(dtype) - array = dask.array.ones(shape, chunks=array_chunks) - if func in ["first", "last"]: pytest.skip() if "arg" in func and (engine == "flox" or reindex): pytest.skip() + rng = np.random.default_rng(12345) + array = dask.array.from_array(rng.random(shape), chunks=array_chunks).astype(dtype) + array = dask.array.ones(shape, chunks=array_chunks) + labels = np.array([0, 0, 2, 2, 2, 1, 1, 2, 2, 1, 1, 0]) if add_nan: labels = labels.astype(float) @@ -612,7 +618,14 @@ def test_groupby_reduce_axis_subset_against_numpy(func, axis, engine): assert_equal(actual, expected, tolerance) -@pytest.mark.parametrize("reindex,chunks", [(None, None), (False, (2, 2, 3)), (True, (2, 2, 3))]) +@pytest.mark.parametrize( + "reindex, chunks", + [ + (None, None), + pytest.param(False, (2, 2, 3), marks=requires_dask), + pytest.param(True, (2, 2, 3), marks=requires_dask), + ], +) @pytest.mark.parametrize( "axis, groups, expected_shape", [ @@ -624,8 +637,6 @@ def test_groupby_reduce_axis_subset_against_numpy(func, axis, engine): def test_groupby_reduce_nans(reindex, chunks, axis, groups, expected_shape, engine): def _maybe_chunk(arr): if chunks: - if not has_dask: - pytest.skip() return da.from_array(arr, chunks=chunks) else: return arr @@ -739,7 +750,14 @@ def test_npg_nanarg_bug(func): ) @pytest.mark.parametrize("method", ["cohorts", "map-reduce"]) @pytest.mark.parametrize("chunk_labels", [False, True]) -@pytest.mark.parametrize("chunks", ((), (1,), (2,))) +@pytest.mark.parametrize( + "chunks", + ( + (), + pytest.param((1,), marks=requires_dask), + pytest.param((2,), marks=requires_dask), + ), +) def test_groupby_bins(chunk_labels, kwargs, chunks, engine, method) -> None: array = [1, 1, 1, 1, 1, 1] labels = [0.2, 1.5, 1.9, 2, 3, 20] @@ -748,8 +766,6 @@ def test_groupby_bins(chunk_labels, kwargs, chunks, engine, method) -> None: pytest.xfail() if chunks: - if not has_dask: - pytest.skip() array = dask.array.from_array(array, chunks=chunks) if chunk_labels: labels = dask.array.from_array(labels, chunks=chunks) @@ -825,7 +841,7 @@ def test_rechunk_for_cohorts(chunk_at, expected): assert rechunked.chunks == expected -@pytest.mark.parametrize("chunks", [None, 3]) +@pytest.mark.parametrize("chunks", [None, pytest.param(3, marks=requires_dask)]) @pytest.mark.parametrize("fill_value", [123, np.nan]) @pytest.mark.parametrize("func", ALL_FUNCS) def test_fill_value_behaviour(func, chunks, fill_value, engine): @@ -833,8 +849,6 @@ def test_fill_value_behaviour(func, chunks, fill_value, engine): # This is used by xarray if func in ["all", "any"] or "arg" in func: pytest.skip() - if chunks is not None and not has_dask: - pytest.skip() npfunc = _get_array_func(func) by = np.array([1, 2, 3, 1, 2, 3]) @@ -1050,11 +1064,8 @@ def test_factorize_values_outside_bins(): assert_equal(expected, actual) -@pytest.mark.parametrize("chunk", [True, False]) +@pytest.mark.parametrize("chunk", [pytest.param(True, marks=requires_dask), False]) def test_multiple_groupers_bins(chunk) -> None: - if chunk and not has_dask: - pytest.skip() - xp = dask.array if chunk else np array_kwargs = {"chunks": 2} if chunk else {} array = xp.ones((5, 2), **array_kwargs, dtype=np.int64) @@ -1087,9 +1098,9 @@ def test_multiple_groupers_bins(chunk) -> None: np.arange(2, 4).reshape(1, 2), ], ) -@pytest.mark.parametrize("chunk", [True, False]) +@pytest.mark.parametrize("chunk", [pytest.param(True, marks=requires_dask), False]) def test_multiple_groupers(chunk, by1, by2, expected_groups) -> None: - if chunk and (not has_dask or expected_groups is None): + if chunk and expected_groups is None: pytest.skip() xp = dask.array if chunk else np @@ -1233,7 +1244,7 @@ def test_dtype(func, dtype, engine): pytest.skip() arr = np.ones((4, 12), dtype=dtype) labels = np.array(["a", "a", "c", "c", "c", "b", "b", "c", "c", "b", "b", "f"]) - actual, _ = groupby_reduce(arr, labels, func=func, dtype=np.float64) + actual, _ = groupby_reduce(arr, labels, func=func, dtype=np.float64, engine=engine) assert actual.dtype == np.dtype("float64") diff --git a/tests/test_xarray.py b/tests/test_xarray.py index 7a343d962..8f006e5f3 100644 --- a/tests/test_xarray.py +++ b/tests/test_xarray.py @@ -16,7 +16,7 @@ dask.config.set(scheduler="sync") try: - # Should test against legacy xarray implementation + # test against legacy xarray implementation xr.set_options(use_flox=False) except ValueError: pass @@ -31,15 +31,15 @@ @pytest.mark.parametrize("add_nan", [True, False]) @pytest.mark.parametrize("skipna", [True, False]) def test_xarray_reduce(skipna, add_nan, min_count, engine, reindex): + if skipna is False and min_count is not None: + pytest.skip() + arr = np.ones((4, 12)) if add_nan: arr[1, ...] = np.nan arr[[0, 2], [3, 4]] = np.nan - if skipna is False and min_count is not None: - pytest.skip() - labels = np.array(["a", "a", "c", "c", "c", "b", "b", "c", "c", "b", "b", "f"]) labels = np.array(labels) labels2 = np.array([1, 2, 2, 1]) @@ -77,11 +77,8 @@ def test_xarray_reduce(skipna, add_nan, min_count, engine, reindex): # TODO: sort @pytest.mark.parametrize("pass_expected_groups", [True, False]) -@pytest.mark.parametrize("chunk", (True, False)) +@pytest.mark.parametrize("chunk", (pytest.param(True, marks=requires_dask), False)) def test_xarray_reduce_multiple_groupers(pass_expected_groups, chunk, engine): - if not has_dask and chunk: - pytest.skip() - if chunk and pass_expected_groups is False: pytest.skip() @@ -126,11 +123,8 @@ def test_xarray_reduce_multiple_groupers(pass_expected_groups, chunk, engine): @pytest.mark.parametrize("pass_expected_groups", [True, False]) -@pytest.mark.parametrize("chunk", (True, False)) +@pytest.mark.parametrize("chunk", (pytest.param(True, marks=requires_dask), False)) def test_xarray_reduce_multiple_groupers_2(pass_expected_groups, chunk, engine): - if not has_dask and chunk: - pytest.skip() - if chunk and pass_expected_groups is False: pytest.skip() @@ -317,14 +311,12 @@ def test_multi_index_groupby_sum(engine): assert_equal(expected, actual) -@pytest.mark.parametrize("chunks", (None, 2)) +@pytest.mark.parametrize("chunks", (None, pytest.param(2, marks=requires_dask))) def test_xarray_groupby_bins(chunks, engine): array = xr.DataArray([1, 1, 1, 1, 1], dims="x") labels = xr.DataArray([1, 1.5, 1.9, 2, 3], dims="x", name="labels") if chunks: - if not has_dask: - pytest.skip() array = array.chunk({"x": chunks}) labels = labels.chunk({"x": chunks}) @@ -472,11 +464,8 @@ def test_alignment_error(): @pytest.mark.parametrize("add_nan", [True, False]) @pytest.mark.parametrize("dtype_out", [np.float64, "float64", np.dtype("float64")]) @pytest.mark.parametrize("dtype", [np.float32, np.float64]) -@pytest.mark.parametrize("chunk", (True, False)) +@pytest.mark.parametrize("chunk", (pytest.param(True, marks=requires_dask), False)) def test_dtype(add_nan, chunk, dtype, dtype_out, engine): - if chunk and not has_dask: - pytest.skip() - xp = dask.array if chunk else np data = xp.linspace(0, 1, 48, dtype=dtype).reshape((4, 12)) @@ -508,12 +497,9 @@ def test_dtype(add_nan, chunk, dtype, dtype_out, engine): xr.testing.assert_allclose(expected, actual.transpose("labels", ...), **tolerance64) -@pytest.mark.parametrize("chunk", [True, False]) +@pytest.mark.parametrize("chunk", [pytest.param(True, marks=requires_dask), False]) @pytest.mark.parametrize("use_flox", [True, False]) def test_dtype_accumulation(use_flox, chunk): - if chunk and not has_dask: - pytest.skip() - datetimes = pd.date_range("2010-01", "2015-01", freq="6H", inclusive="left") samples = 10 + np.cos(2 * np.pi * 0.001 * np.arange(len(datetimes))) * 1 samples += np.random.randn(len(datetimes))