diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index f1d8547c75..28917ae408 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -144,6 +144,10 @@ This document explains the changes made to Iris for this release Cube/Coord summary to use ``str`` representation instead of ``repr``. (:pull:`6966`, :issue:`6692`) +#. `@ESadek-MO` and `@pp-mo`_ removed unit test reliance on all optional dependencies + except for mo_pack. + (:issue:`6832`, :pull:`6976`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, core dev names are automatically included by the common_links.inc: diff --git a/lib/iris/tests/_shared_utils.py b/lib/iris/tests/_shared_utils.py index 3e0ed6ccc4..3d47b0c618 100644 --- a/lib/iris/tests/_shared_utils.py +++ b/lib/iris/tests/_shared_utils.py @@ -979,12 +979,6 @@ class MyGeoTiffTests(test.IrisTest): ) -skip_stratify = pytest.mark.skipif( - not STRATIFY_AVAILABLE, - reason='Test(s) require "python-stratify", which is not available.', -) - - def no_warnings(func): """Provides a decorator to ensure that there are no warnings raised within the test, otherwise the test will fail. diff --git a/lib/iris/tests/integration/_shapefiles/__init__.py b/lib/iris/tests/integration/_shapefiles/__init__.py new file mode 100644 index 0000000000..4c8a3cd5d5 --- /dev/null +++ b/lib/iris/tests/integration/_shapefiles/__init__.py @@ -0,0 +1,5 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. +"""Integration tests for the :mod:`iris._shapefiles` package.""" diff --git a/lib/iris/tests/unit/_shapefiles/test_create_shape_mask.py b/lib/iris/tests/integration/_shapefiles/test_create_shape_mask.py similarity index 99% rename from lib/iris/tests/unit/_shapefiles/test_create_shape_mask.py rename to lib/iris/tests/integration/_shapefiles/test_create_shape_mask.py index 32ca99d48d..fb7415da1a 100644 --- a/lib/iris/tests/unit/_shapefiles/test_create_shape_mask.py +++ b/lib/iris/tests/integration/_shapefiles/test_create_shape_mask.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for :func:`iris._shapefiles.create_shape_mask`.""" +"""Integration tests for :func:`iris._shapefiles.create_shape_mask`.""" import numpy as np from pyproj import CRS diff --git a/lib/iris/tests/unit/_shapefiles/test_get_weighted_mask.py b/lib/iris/tests/integration/_shapefiles/test_get_weighted_mask.py similarity index 97% rename from lib/iris/tests/unit/_shapefiles/test_get_weighted_mask.py rename to lib/iris/tests/integration/_shapefiles/test_get_weighted_mask.py index 6863fb1847..963d321204 100644 --- a/lib/iris/tests/unit/_shapefiles/test_get_weighted_mask.py +++ b/lib/iris/tests/integration/_shapefiles/test_get_weighted_mask.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for :func:`iris._shapefiles._get_weighted_mask`.""" +"""Integration tests for :func:`iris._shapefiles._get_weighted_mask`.""" import numpy as np import pytest diff --git a/lib/iris/tests/unit/_shapefiles/test_is_geometry_valid.py b/lib/iris/tests/integration/_shapefiles/test_is_geometry_valid.py similarity index 98% rename from lib/iris/tests/unit/_shapefiles/test_is_geometry_valid.py rename to lib/iris/tests/integration/_shapefiles/test_is_geometry_valid.py index 8605d72d8b..6902ad22e5 100644 --- a/lib/iris/tests/unit/_shapefiles/test_is_geometry_valid.py +++ b/lib/iris/tests/integration/_shapefiles/test_is_geometry_valid.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for :func:`iris._shapefiles.is_geometry_valid`.""" +"""Integration tests for :func:`iris._shapefiles.is_geometry_valid`.""" from pyproj import CRS import pytest diff --git a/lib/iris/tests/unit/_shapefiles/test_make_raster_cube_transform.py b/lib/iris/tests/integration/_shapefiles/test_make_raster_cube_transform.py similarity index 96% rename from lib/iris/tests/unit/_shapefiles/test_make_raster_cube_transform.py rename to lib/iris/tests/integration/_shapefiles/test_make_raster_cube_transform.py index 239cce15b2..253da96796 100644 --- a/lib/iris/tests/unit/_shapefiles/test_make_raster_cube_transform.py +++ b/lib/iris/tests/integration/_shapefiles/test_make_raster_cube_transform.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for :func:`iris._shapefiles._make_raster_cube_transform`.""" +"""Integration tests for :func:`iris._shapefiles._make_raster_cube_transform`.""" from affine import Affine import numpy as np diff --git a/lib/iris/tests/unit/_shapefiles/test_transform_geometry.py b/lib/iris/tests/integration/_shapefiles/test_transform_geometry.py similarity index 98% rename from lib/iris/tests/unit/_shapefiles/test_transform_geometry.py rename to lib/iris/tests/integration/_shapefiles/test_transform_geometry.py index 6aff0931c5..d56a010d67 100644 --- a/lib/iris/tests/unit/_shapefiles/test_transform_geometry.py +++ b/lib/iris/tests/integration/_shapefiles/test_transform_geometry.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for :func:`iris._shapefiles._transform_geometry`.""" +"""Integration tests for :func:`iris._shapefiles._transform_geometry`.""" import numpy as np import pyproj diff --git a/lib/iris/tests/unit/pandas/__init__.py b/lib/iris/tests/integration/experimental/regrid/__init__.py similarity index 76% rename from lib/iris/tests/unit/pandas/__init__.py rename to lib/iris/tests/integration/experimental/regrid/__init__.py index 2ee1fb1cfe..e3983bc695 100644 --- a/lib/iris/tests/unit/pandas/__init__.py +++ b/lib/iris/tests/integration/experimental/regrid/__init__.py @@ -2,4 +2,4 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for the :mod:`iris.pandas` module.""" +"""Regridding code is tested in this package.""" diff --git a/lib/iris/tests/experimental/regrid/test_regrid_conservative_via_esmpy.py b/lib/iris/tests/integration/experimental/regrid/test_regrid_conservative_via_esmpy.py similarity index 100% rename from lib/iris/tests/experimental/regrid/test_regrid_conservative_via_esmpy.py rename to lib/iris/tests/integration/experimental/regrid/test_regrid_conservative_via_esmpy.py diff --git a/lib/iris/tests/integration/experimental/stratify/__init__.py b/lib/iris/tests/integration/experimental/stratify/__init__.py new file mode 100644 index 0000000000..e31d61ba10 --- /dev/null +++ b/lib/iris/tests/integration/experimental/stratify/__init__.py @@ -0,0 +1,5 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. +"""Integration tests for the :mod:`iris.experimental.stratify` package.""" diff --git a/lib/iris/tests/integration/experimental/stratify/test_relevel.py b/lib/iris/tests/integration/experimental/stratify/test_relevel.py new file mode 100644 index 0000000000..e4665c689f --- /dev/null +++ b/lib/iris/tests/integration/experimental/stratify/test_relevel.py @@ -0,0 +1,98 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. +"""Integration tests for the :func:`iris.experimental.stratify.relevel` function.""" + +from functools import partial + +import numpy as np +import pytest +import stratify + +from iris.coords import AuxCoord, DimCoord +from iris.experimental.stratify import relevel +from iris.tests import _shared_utils +import iris.tests.stock as stock + + +class Test: + @pytest.fixture(autouse=True) + def _setup(self): + cube = stock.simple_3d()[:, :1, :1] + #: The data from which to get the levels. + self.src_levels = cube.copy() + #: The data to interpolate. + self.cube = cube.copy() + self.cube.rename("foobar") + self.cube *= 10 + self.coord = self.src_levels.coord("wibble") + self.axes = (self.coord, self.coord.name(), None, 0) + + def test_standard_input(self): + for axis in self.axes: + result = relevel(self.cube, self.src_levels, [-1, 0, 5.5], axis=axis) + _shared_utils.assert_array_equal( + result.data.flatten(), np.array([np.nan, 0, 55]) + ) + expected = DimCoord([-1, 0, 5.5], units=1, long_name="thingness") + assert expected == result.coord("thingness") + + def test_non_monotonic(self): + for axis in self.axes: + result = relevel(self.cube, self.src_levels, [2, 3, 2], axis=axis) + _shared_utils.assert_array_equal( + result.data.flatten(), np.array([20, 30, np.nan]) + ) + expected = AuxCoord([2, 3, 2], units=1, long_name="thingness") + assert result.coord("thingness") == expected + + def test_static_level(self): + for axis in self.axes: + result = relevel(self.cube, self.src_levels, [2, 2], axis=axis) + _shared_utils.assert_array_equal(result.data.flatten(), np.array([20, 20])) + + def test_coord_input(self): + source = AuxCoord(self.src_levels.data) + metadata = self.src_levels.metadata._asdict() + metadata["coord_system"] = None + metadata["climatological"] = None + source.metadata = metadata + + for axis in self.axes: + result = relevel(self.cube, source, [0, 12, 13], axis=axis) + assert result.shape == (3, 1, 1) + _shared_utils.assert_array_equal(result.data.flatten(), [0, 120, np.nan]) + + def test_custom_interpolator(self): + interpolator = partial(stratify.interpolate, interpolation="nearest") + + for axis in self.axes: + result = relevel( + self.cube, + self.src_levels, + [-1, 0, 6.5], + axis=axis, + interpolator=interpolator, + ) + _shared_utils.assert_array_equal( + result.data.flatten(), np.array([np.nan, 0, 120]) + ) + + def test_multi_dim_target_levels(self, request): + interpolator = partial( + stratify.interpolate, + interpolation="linear", + extrapolation="linear", + ) + + for axis in self.axes: + result = relevel( + self.cube, + self.src_levels, + self.src_levels.data, + axis=axis, + interpolator=interpolator, + ) + _shared_utils.assert_array_equal(result.data.flatten(), np.array([0, 120])) + _shared_utils.assert_CML(request, result) diff --git a/lib/iris/tests/unit/pandas/test_pandas.py b/lib/iris/tests/integration/test_pandas.py similarity index 99% rename from lib/iris/tests/unit/pandas/test_pandas.py rename to lib/iris/tests/integration/test_pandas.py index 5fed3ee956..2a042f5529 100644 --- a/lib/iris/tests/unit/pandas/test_pandas.py +++ b/lib/iris/tests/integration/test_pandas.py @@ -23,17 +23,10 @@ # Importing pandas has the side-effect of messing with the formatters # used by matplotlib for handling dates. default_units_registry = copy.copy(matplotlib.units.registry) -try: - import pandas as pd -except ImportError: - # Disable all these tests if pandas is not installed. - pd = None +import pandas as pd + matplotlib.units.registry = default_units_registry -skip_pandas = pytest.mark.skipif( - pd is None, - reason='Test(s) require "pandas", which is not available.', -) if pd is not None: from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord @@ -48,7 +41,6 @@ def activate_pandas_ndim(): iris.FUTURE.pandas_ndim = False -@skip_pandas @pytest.mark.filterwarnings( "ignore:.*as_series has been deprecated.*:iris._deprecation.IrisDeprecation" ) @@ -131,7 +123,6 @@ def test_copy_masked_true(self): assert cube.data[0] == 0 -@skip_pandas @pytest.mark.filterwarnings( "ignore:You are using legacy 2-dimensional behaviour.*:FutureWarning" ) @@ -261,7 +252,6 @@ def test_copy_masked_true(self): assert cube.data[0, 0] == 0 -@skip_pandas class TestAsDataFrameNDim: """Test conversion of n-dimensional cubes to Pandas using as_data_frame().""" @@ -565,7 +555,6 @@ def test_instance_error(self): _ = iris.pandas.as_data_frame(list()) -@skip_pandas @pytest.mark.filterwarnings( "ignore:.*as_cube has been deprecated.*:iris._deprecation.IrisDeprecation" ) @@ -647,7 +636,6 @@ def test_implicit_copy_true(self): assert series[5] == 0 -@skip_pandas @pytest.mark.filterwarnings( "ignore:.*as_cube has been deprecated.*:iris._deprecation.IrisDeprecation" ) @@ -750,7 +738,6 @@ def test_implicit_copy_true(self): assert data_frame.iloc[0, 0] == 0 -@skip_pandas class TestFutureAndDeprecation: def test_as_cube_deprecation_warning(self): data_frame = pd.DataFrame([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) @@ -819,7 +806,6 @@ def test_explicit_copy_false_error(self, test_function, test_input): _ = test_function(test_input, copy=False) -@skip_pandas class TestPandasAsCubes: @staticmethod def _create_pandas(index_levels=0, is_series=False): diff --git a/lib/iris/tests/integration/util/__init__.py b/lib/iris/tests/integration/util/__init__.py new file mode 100644 index 0000000000..687cf3e006 --- /dev/null +++ b/lib/iris/tests/integration/util/__init__.py @@ -0,0 +1,5 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the BSD license. +# See LICENSE in the root of the repository for full licensing details. +"""Integration tests for the :mod:`iris.util` module.""" diff --git a/lib/iris/tests/unit/util/test_mask_cube_from_shape.py b/lib/iris/tests/integration/util/test_mask_cube_from_shape.py similarity index 100% rename from lib/iris/tests/unit/util/test_mask_cube_from_shape.py rename to lib/iris/tests/integration/util/test_mask_cube_from_shape.py diff --git a/lib/iris/tests/unit/util/test_mask_cube_from_shapefile.py b/lib/iris/tests/integration/util/test_mask_cube_from_shapefile.py similarity index 98% rename from lib/iris/tests/unit/util/test_mask_cube_from_shapefile.py rename to lib/iris/tests/integration/util/test_mask_cube_from_shapefile.py index 845867ebae..345889dacc 100644 --- a/lib/iris/tests/unit/util/test_mask_cube_from_shapefile.py +++ b/lib/iris/tests/integration/util/test_mask_cube_from_shapefile.py @@ -2,7 +2,7 @@ # # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. -"""Unit tests for :func:`iris.util.mask_cube_from_shapefile`.""" +"""Integration tests for :func:`iris.util.mask_cube_from_shapefile`.""" import numpy as np import pytest diff --git a/lib/iris/tests/results/unit/experimental/stratify/relevel/multi_dim_target_levels.cml b/lib/iris/tests/results/integration/experimental/stratify/relevel/multi_dim_target_levels.cml similarity index 100% rename from lib/iris/tests/results/unit/experimental/stratify/relevel/multi_dim_target_levels.cml rename to lib/iris/tests/results/integration/experimental/stratify/relevel/multi_dim_target_levels.cml diff --git a/lib/iris/tests/test_constraints.py b/lib/iris/tests/test_constraints.py index 9a7ab5fbdf..5adaa9cef8 100644 --- a/lib/iris/tests/test_constraints.py +++ b/lib/iris/tests/test_constraints.py @@ -305,12 +305,21 @@ def load_match(self, files, constraints): return cubes +@pytest.fixture +def _skip_sample_data_path(): + try: + fname = iris.sample_data_path("atlantic_profiles.nc") + cubes = iris.load(fname) + except ImportError: + cubes = iris.cube.CubeList([stock.simple_pp(), stock.simple_3d()]) + return cubes + + @_shared_utils.skip_data class TestCubeExtract__names(ConstraintMixin): @pytest.fixture(autouse=True) - def _setup(self, _setup_mixin): - fname = iris.sample_data_path("atlantic_profiles.nc") - self.cubes = iris.load(fname) + def _setup(self, _setup_mixin, _skip_sample_data_path): + self.cubes = _skip_sample_data_path cube = iris.load_cube(self.theta_path) # Expected names... self.standard_name = "air_potential_temperature" @@ -365,9 +374,8 @@ def test_unknown(self): @_shared_utils.skip_data class TestCubeExtract__name_constraint(ConstraintMixin): @pytest.fixture(autouse=True) - def _setup(self, _setup_mixin): - fname = iris.sample_data_path("atlantic_profiles.nc") - self.cubes = iris.load(fname) + def _setup(self, _setup_mixin, _skip_sample_data_path): + self.cubes = _skip_sample_data_path cube = iris.load_cube(self.theta_path) # Expected names... self.standard_name = "air_potential_temperature" diff --git a/lib/iris/tests/unit/experimental/geovista/test_cube_to_polydata.py b/lib/iris/tests/unit/experimental/geovista/test_cube_to_polydata.py index cecbba373b..effb55ecd7 100644 --- a/lib/iris/tests/unit/experimental/geovista/test_cube_to_polydata.py +++ b/lib/iris/tests/unit/experimental/geovista/test_cube_to_polydata.py @@ -6,7 +6,6 @@ from typing import ClassVar -from geovista import Transform import numpy as np import pytest @@ -67,8 +66,8 @@ def cube_with_crs(self, default_cs, cube): @pytest.fixture def mocked_operation(self, mocker): - mocking = mocker.Mock() - setattr(Transform, self.MOCKED_OPERATION, mocking) + target = f"geovista.Transform.{self.MOCKED_OPERATION}" + mocking = mocker.patch(target, mocker.Mock()) return mocking @staticmethod diff --git a/lib/iris/tests/unit/experimental/geovista/test_extract_unstructured_region.py b/lib/iris/tests/unit/experimental/geovista/test_extract_unstructured_region.py index b7acd2412f..51688f9f6d 100644 --- a/lib/iris/tests/unit/experimental/geovista/test_extract_unstructured_region.py +++ b/lib/iris/tests/unit/experimental/geovista/test_extract_unstructured_region.py @@ -4,7 +4,6 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.experimental.geovista.extract_unstructured_region` function.""" -from geovista.common import VTK_CELL_IDS, VTK_POINT_IDS import numpy as np import pytest @@ -13,6 +12,9 @@ from iris.tests.stock import sample_2d_latlons from iris.tests.stock.mesh import sample_mesh_cube +VTK_CELL_IDS = "vtkOriginalCellIds" +VTK_POINT_IDS = "vtkOriginalPointIds" + class TestRegionExtraction: @pytest.fixture diff --git a/lib/iris/tests/unit/experimental/stratify/test_relevel.py b/lib/iris/tests/unit/experimental/stratify/test_relevel.py index 5a9c192d73..0b8881de68 100644 --- a/lib/iris/tests/unit/experimental/stratify/test_relevel.py +++ b/lib/iris/tests/unit/experimental/stratify/test_relevel.py @@ -4,27 +4,35 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :func:`iris.experimental.stratify.relevel` function.""" -from functools import partial +import sys +import types import numpy as np import pytest from iris.coords import AuxCoord, DimCoord -from iris.tests import _shared_utils import iris.tests.stock as stock -try: - import stratify - from iris.experimental.stratify import relevel -except ImportError: - stratify = None +@pytest.fixture(autouse=True, scope="module") +def fake_stratify(): + try: + import stratify + except: + fake = types.ModuleType("stratify") + fake.interpolate = lambda *a, **k: None + sys.modules["stratify"] = fake + + try: + yield + finally: + # Remove fake after tests in this module complete + sys.modules.pop("stratify", None) -@_shared_utils.skip_stratify class Test: @pytest.fixture(autouse=True) - def _setup(self): + def _setup(self, mocker): cube = stock.simple_3d()[:, :1, :1] #: The data from which to get the levels. self.src_levels = cube.copy() @@ -34,43 +42,39 @@ def _setup(self): self.cube *= 10 self.coord = self.src_levels.coord("wibble") self.axes = (self.coord, self.coord.name(), None, 0) + self.patch_interpolate = mocker.patch( + "iris.experimental.stratify.stratify.interpolate" + ) + self.patch_interpolate.return_value = np.ones((3, 1, 1)) def test_broadcast_fail_src_levels(self): + from iris.experimental.stratify import relevel + emsg = "Cannot broadcast the cube and src_levels" data = np.arange(60).reshape(3, 4, 5) with pytest.raises(ValueError, match=emsg): relevel(self.cube, AuxCoord(data), [1, 2, 3]) def test_broadcast_fail_tgt_levels(self): + from iris.experimental.stratify import relevel + emsg = "Cannot broadcast the cube and tgt_levels" data = np.arange(60).reshape(3, 4, 5) with pytest.raises(ValueError, match=emsg): relevel(self.cube, self.coord, data) def test_standard_input(self): + from iris.experimental.stratify import relevel + for axis in self.axes: result = relevel(self.cube, self.src_levels, [-1, 0, 5.5], axis=axis) - _shared_utils.assert_array_equal( - result.data.flatten(), np.array([np.nan, 0, 55]) - ) expected = DimCoord([-1, 0, 5.5], units=1, long_name="thingness") assert expected == result.coord("thingness") - - def test_non_monotonic(self): - for axis in self.axes: - result = relevel(self.cube, self.src_levels, [2, 3, 2], axis=axis) - _shared_utils.assert_array_equal( - result.data.flatten(), np.array([20, 30, np.nan]) - ) - expected = AuxCoord([2, 3, 2], units=1, long_name="thingness") - assert result.coord("thingness") == expected - - def test_static_level(self): - for axis in self.axes: - result = relevel(self.cube, self.src_levels, [2, 2], axis=axis) - _shared_utils.assert_array_equal(result.data.flatten(), np.array([20, 20])) + self.patch_interpolate.assert_called() def test_coord_input(self): + from iris.experimental.stratify import relevel + source = AuxCoord(self.src_levels.data) metadata = self.src_levels.metadata._asdict() metadata["coord_system"] = None @@ -80,37 +84,22 @@ def test_coord_input(self): for axis in self.axes: result = relevel(self.cube, source, [0, 12, 13], axis=axis) assert result.shape == (3, 1, 1) - _shared_utils.assert_array_equal(result.data.flatten(), [0, 120, np.nan]) + self.patch_interpolate.assert_called() - def test_custom_interpolator(self): - interpolator = partial(stratify.interpolate, interpolation="nearest") + def test_custom_interpolator(self, mocker): + from iris.experimental.stratify import relevel - for axis in self.axes: - result = relevel( - self.cube, - self.src_levels, - [-1, 0, 6.5], - axis=axis, - interpolator=interpolator, - ) - _shared_utils.assert_array_equal( - result.data.flatten(), np.array([np.nan, 0, 120]) - ) + mock_interpolate = mocker.Mock() + mock_interpolate.return_value = np.ones((3, 1, 1)) - def test_multi_dim_target_levels(self, request): - interpolator = partial( - stratify.interpolate, - interpolation="linear", - extrapolation="linear", - ) + interpolator = mock_interpolate for axis in self.axes: - result = relevel( + _ = relevel( self.cube, self.src_levels, - self.src_levels.data, + [-1, 0, 6.5], axis=axis, interpolator=interpolator, ) - _shared_utils.assert_array_equal(result.data.flatten(), np.array([0, 120])) - _shared_utils.assert_CML(request, result) + mock_interpolate.assert_called()