diff --git a/docs/src/developers_guide/contributing_pytest_conversions.rst b/docs/src/developers_guide/contributing_pytest_conversions.rst new file mode 100644 index 0000000000..c6bb35c2cd --- /dev/null +++ b/docs/src/developers_guide/contributing_pytest_conversions.rst @@ -0,0 +1,56 @@ +.. include:: ../common_links.inc + +.. _contributing_pytest_conversions: + +******************************************* +Converting From ``unittest`` to ``pytest`` +******************************************* + +Conversion Checklist +-------------------- +.. note:: + Please bear in mind the following checklist is for general use; there may be + some cases which require extra context or thought before implementing these changes. + +#. Before making any manual changes, run https://github.com/dannysepler/pytestify + on the file. This does a lot of the brunt work for you! +#. Check for references to :class:`iris.tests.IrisTest`. If a class inherits + from this, remove the inheritance. Inheritance is unnecessary for + pytest tests, so :class:`iris.tests.IrisTest` has been deprecated + and its convenience methods have been moved to the + :mod:`iris.tests._shared_utils` module. +#. Check for references to ``unittest``. Many of the functions within unittest + are also in pytest, so often you can just change where the function is imported + from. +#. Check for references to ``self.assert``. Pytest has a lighter-weight syntax for + assertions, e.g. ``assert x == 2`` instead of ``assertEqual(x, 2)``. In the + case of custom :class:`~iris.tests.IrisTest` assertions, the majority of these + have been replicated in + :mod:`iris.tests._shared_utils`, but with snake_case instead of camelCase. + Some :class:`iris.tests.IrisTest` assertions have not been converted into + :mod:`iris.tests._shared_utils`, as these were deemed easy to achieve via + simple ``assert ...`` statements. +#. Check for references to ``setUp()``. Replace this with ``_setup()`` instead. + Ensure that this is decorated with ``@pytest.fixture(autouse=True)``. + + .. code-block:: python + + @pytest.fixture(autouse=True) + def _setup(self): + ... + +#. Check for references to ``@tests``. These should be changed to ``@_shared_utils``. +#. Check for references to ``with mock.patch("...")``. These should be replaced with + ``mocker.patch("...")``. Note, ``mocker.patch("...")`` is NOT a context manager. +#. Check for ``np.testing.assert...``. This can usually be swapped for + ``_shared_utils.assert...``. +#. Check for references to ``super()``. Most test classes used to inherit from + :class:`iris.tests.IrisTest`, so references to this should be removed. +#. Check for references to ``self.tmp_dir``. In pytest, ``tmp_path`` is used instead, + and can be passed into functions as a fixture. +#. Check for ``if __name__ == 'main'``. This is no longer needed with pytest. +#. Check for ``mock.patch("warnings.warn")``. This can be replaced with + ``pytest.warns(match=message)``. +#. Check the file against https://github.com/astral-sh/ruff , using ``pip install ruff`` -> + ``ruff check --select PT ``. + diff --git a/docs/src/developers_guide/contributing_running_tests.rst b/docs/src/developers_guide/contributing_running_tests.rst index f60cedba05..a72caa5881 100644 --- a/docs/src/developers_guide/contributing_running_tests.rst +++ b/docs/src/developers_guide/contributing_running_tests.rst @@ -87,7 +87,7 @@ experimental dependency not being present. SKIPPED [1] lib/iris/tests/unit/util/test_demote_dim_coord_to_aux_coord.py:29: Test(s) require external data. All Python decorators that skip tests will be defined in - ``lib/iris/tests/__init__.py`` with a function name with a prefix of + ``lib/iris/tests/_shared_utils.py`` with a function name with a prefix of ``skip_``. You can also run a specific test module. The example below runs the tests for diff --git a/docs/src/developers_guide/contributing_testing.rst b/docs/src/developers_guide/contributing_testing.rst deleted file mode 100644 index a65bcebd55..0000000000 --- a/docs/src/developers_guide/contributing_testing.rst +++ /dev/null @@ -1,147 +0,0 @@ -.. include:: ../common_links.inc - -.. _developer_test_categories: - - -Test Categories -*************** - -There are two main categories of tests within Iris: - -- :ref:`testing.unit_test` -- :ref:`testing.integration` - -Ideally, all code changes should be accompanied by one or more unit -tests, and by zero or more integration tests. - -But if in any doubt about what tests to add or how to write them please -feel free to submit a pull-request in any state and ask for assistance. - - -.. _testing.unit_test: - -Unit Tests -========== - -Code changes should be accompanied by enough unit tests to give a -high degree of confidence that the change works as expected. In -addition, the unit tests can help describe the intent behind a change. - -The docstring for each test module must state the unit under test. -For example: - - :literal:`"""Unit tests for the \`iris.experimental.raster.export_geotiff\` function."""` - -All unit tests must be placed and named according to the following -structure: - - -.. _testing.classes: - -Classes -------- - -When testing a class all the tests must reside in the module: - - :literal:`lib/iris/tests/unit//test_.py` - -Within this test module each tested method must have one or more -corresponding test classes, for example: - -* ``Test_`` -* ``Test___`` - -And within those test classes, the test methods must be named according -to the aspect of the tested method which they address. - -**Examples**: - -All unit tests for :py:class:`iris.cube.Cube` must reside in: - - :literal:`lib/iris/tests/unit/cube/test_Cube.py` - -Within that file the tests might look something like: - -.. code-block:: python - - # Tests for the Cube.xml() method. - class Test_xml(tests.IrisTest): - def test_some_general_stuff(self): - ... - - - # Tests for the Cube.xml() method, focussing on the behaviour of - # the checksums. - class Test_xml__checksum(tests.IrisTest): - def test_checksum_ignores_masked_values(self): - ... - - - # Tests for the Cube.add_dim_coord() method. - class Test_add_dim_coord(tests.IrisTest): - def test_normal_usage(self): - ... - - def test_coord_already_present(self): - ... - - -.. _testing.functions: - -Functions ---------- - -When testing a function all the tests must reside in the module: - - :literal:`lib/iris/tests/unit//test_.py` - -Within this test module there must be one or more test classes, for example: - -* ``Test`` -* ``TestAspectOfFunction`` - -And within those test classes, the test methods must be named according -to the aspect of the tested function which they address. - -**Examples**: - -All unit tests for :py:func:`iris.experimental.raster.export_geotiff` -must reside in: - - :literal:`lib/iris/tests/unit/experimental/raster/test_export_geotiff.py` - -Within that file the tests might look something like: - -.. code-block:: python - - # Tests focussing on the handling of different data types. - class TestDtypeAndValues(tests.IrisTest): - def test_int16(self): - ... - - def test_int16_big_endian(self): - ... - - - # Tests focussing on the handling of different projections. - class TestProjection(tests.IrisTest): - def test_no_ellipsoid(self): - ... - - -.. _testing.integration: - -Integration Tests -================= - -Some code changes may require tests which exercise several units in -order to demonstrate an important consequence of their interaction which -may not be apparent when considering the units in isolation. - -These tests must be placed in the ``lib/iris/tests/integration`` folder. -Unlike unit tests, there is no fixed naming scheme for integration -tests. But folders and files must be created as required to help -developers locate relevant tests. It is recommended they are named -according to the capabilities under test, e.g. -``metadata/test_pp_preservation.py``, and not named according to the -module(s) under test. diff --git a/docs/src/developers_guide/contributing_testing_index.rst b/docs/src/developers_guide/contributing_testing_index.rst index 2f5ae411e8..2d57da3d93 100644 --- a/docs/src/developers_guide/contributing_testing_index.rst +++ b/docs/src/developers_guide/contributing_testing_index.rst @@ -6,9 +6,9 @@ Testing .. toctree:: :maxdepth: 3 - contributing_testing - testing_tools + contributing_tests contributing_graphics_tests contributing_running_tests contributing_ci_tests contributing_benchmarks + contributing_pytest_conversions diff --git a/docs/src/developers_guide/contributing_tests.rst b/docs/src/developers_guide/contributing_tests.rst new file mode 100644 index 0000000000..e18a6987d2 --- /dev/null +++ b/docs/src/developers_guide/contributing_tests.rst @@ -0,0 +1,264 @@ +.. include:: ../common_links.inc + +.. _contributing_tests: + +************* +Writing Tests +************* + +.. note:: + If you're converting UnitTest tests to PyTest, check out + :ref:`contributing_pytest_conversions`. + +.. _developer_pytest_categories: + +Test Categories +=============== + +There are two main categories of tests within Iris: + +- `unit tests` +- `integration tests` + +Ideally, all code changes should be accompanied by one or more unit +tests, and by zero or more integration tests. + +Code changes should be accompanied by enough unit tests to give a +high degree of confidence that the change works as expected. In +addition, the unit tests can help describe the intent behind a change. + +The docstring for each test module must state the unit under test. +For example: + + :literal:`"""Unit tests for the \`iris.experimental.raster.export_geotiff\` function."""` + +When testing a class, all the tests must reside in the module: + + :literal:`lib/iris/tests/unit//test_.py` + +When testing a function, all the tests must reside in the module: + + :literal:`lib/iris/tests/unit//test_.py` + +Some code changes may require tests which exercise several units in +order to demonstrate an important consequence of their interaction which +may not be apparent when considering the units in isolation. These tests must +be placed in the ``lib/iris/tests/integration`` folder. + +With integration tests, folders and files must be created as required to help +developers locate relevant tests. It is recommended they are named +according to the capabilities under test, e.g. +``metadata/test_pp_preservation.py``, and not named according to the +module(s) under test. + +If in any doubt about what tests to add or how to write them please +feel free to submit a pull-request in any state and ask for assistance. + +.. _testing_style_guide: + +PyTest Style Guide +================== + +.. note:: + If you're converting UnitTest tests to PyTest, check out + :ref:`contributing_pytest_conversions`. + +This style guide should be approached pragmatically. Many of the guidelines laid out +below will not be practical in every scenario, and as such should not be considered +firm rules. + +At time of writing, some existing tests have already been written in PyTest, +so might not be abiding by these guidelines. + +`conftest.py `_ +----------------------------------------------------------------------------------------------------------------------------- + +There should be a ``conftest.py`` file in the ``root/unit`` and ``root/integration`` +folders. Additional lower level ``conftest``\s can be added if it is agreed there +is a need. + +`Fixtures `_ +------------------------------------------------------------------------------------ + +As far as is possible, the actual test function should do little else but the +actual assertion. Separating off preparation into fixtures may make the code +harder to follow, so compromises are acceptable. For example, setting up a test +``Cube`` should be a fixture, whereas creating a simple string +(``expected = "foo"``), or a single use setup, should *not* be a fixture. + + +New fixtures should always be considered for ``conftest`` when added. If it is +decided that they are not suitably reusable, they can be placed within the +local test file. + +`Parameterisation `_ +-------------------------------------------------------------------------------- + +Though it is a useful tool, we should not be complicating tests to work around +parameters; they should only be used when it is simple and apparent to implement. + +Where you are parameterising multiple tests with the same parameters, it is +usually prudent to use the `parameterisation within fixtures +`_. +When doing this, ensure within the tests that it is apparent that they are being +parameterised, either within the fixture name or with comments. + +All parameterisation benefits from +`ids `_, +and so should be used where possible. + +`Mocks `_ +-------------------------------------------------------------------- + +Any mocking should be done with ``pytest.mock``, and monkeypatching where suitable. + +.. note:: + If you think we're missing anything important here, please consider creating an + issue or discussion and share your ideas with the team! + +`Classes `_ +--------------------------------------------------------------------------------------------------- + +How and when to group tests within classes can be based on personal opinion, +we do not deem consistency on this a vital concern. + +Naming Test Classes and Functions +--------------------------------- + +When testing classes and their methods, each tested method within a test module +may have corresponding test classes, for example: + +* ``Test_`` +* ``Test___`` + +Within these test classes, the test methods must be named according +to the aspect of the tested method which they address. + +**Examples**: + +All unit tests for :py:class:`iris.cube.Cube` reside in: + + :literal:`lib/iris/tests/unit/cube/test_Cube.py` + +Within that file the tests might look something like: + +.. code-block:: python + + # A single test for the Cube.xml() method. + def test_xml_some_general_stuff(self): + ... + + + # A single test for the Cube.xml() method, focussing on the behaviour of + # the checksums. + def test_xml_checksum_ignores_masked_values(self): + ... + + + # Tests for the Cube.add_dim_coord() method. + class Test_add_dim_coord: + def test_normal_usage(self): + ... + + def test_coord_already_present(self): + ... + +When testing functions, within the test module there may be test classes, for +example: + +* ``Test`` +* ``TestAspectOfFunction`` + +Within those test classes, the test methods must be named according +to the aspect of the tested function which they address. + +**Examples**: + +All unit tests for :py:func:`iris.experimental.raster.export_geotiff` +must reside in: + + :literal:`lib/iris/tests/unit/experimental/raster/test_export_geotiff.py` + +Within that file the tests might look something like: + +.. code-block:: python + + # Tests focussing on the handling of different data types. + class TestDtypeAndValues: + def test_int16(self): + ... + + def test_int16_big_endian(self): + ... + + + # Tests focussing on the handling of different projections. + def test_no_ellipsoid(self): + ... + +There is no fixed naming scheme for integration tests. + +.. _testing_tools: + +Testing tools +============= + +.. note:: + :class:`iris.tests.IrisTest` has been deprecated, and replaced with + the :mod:`iris.tests._shared_utils` module. + +Iris has various internal convenience functions and utilities available to +support writing tests. Using these makes tests quicker and easier to write, and +also consistent with the rest of Iris (which makes it easier to work with the +code). Most of these conveniences are accessed through the +:mod:`iris.tests._shared_utils` module. + +.. tip:: + + All functions listed on this page are defined within + :mod:`iris.tests._shared_utils`. They can be accessed within a test using + ``_shared_utils.example_function``. + +Custom assertions +----------------- + +:mod:`iris.tests._shared_utils` supports a variety of custom pytest-style +assertions, such as :func:`~iris.tests._shared_utils.assert_array_equal`, and +:func:`~iris.tests._shared_utils.assert_array_almost_equal`. + +.. _create-missing: + +Saving results +-------------- + +Some tests compare the generated output to the expected result contained in a +file. Custom assertions for this include +:func:`~iris.tests._shared_utils.assert_CML_approx_data` +:func:`~iris.tests._shared_utils.assert_CDL` +:func:`~iris.tests._shared_utils.assert_CML` and +:func:`~iris.tests._shared_utils.assert_text_file`. See docstrings for more +information. + +.. note:: + + Sometimes code changes alter the results expected from a test containing the + above methods. These can be updated by removing the existing result files + and then running the file containing the test with a ``--create-missing`` + command line argument, or setting the ``IRIS_TEST_CREATE_MISSING`` + environment variable to anything non-zero. This will create the files rather + than erroring, allowing you to commit the updated results. + +Capturing exceptions and logging +-------------------------------- + +:mod:`~iris.tests._shared_utils` includes several context managers that can be used +to make test code tidier and easier to read. These include +:meth:`~iris.tests._shared_utils.assert_no_warnings_regexp` and +:meth:`~iris.tests._shared_utils.assert_logs`. + +Graphic tests +------------- + +As a package capable of generating graphical outputs, Iris has utilities for +creating and updating graphical tests - see :ref:`testing.graphics` for more +information. \ No newline at end of file diff --git a/docs/src/developers_guide/testing_tools.rst b/docs/src/developers_guide/testing_tools.rst deleted file mode 100755 index dd628d37fc..0000000000 --- a/docs/src/developers_guide/testing_tools.rst +++ /dev/null @@ -1,80 +0,0 @@ -.. include:: ../common_links.inc - -.. _testing_tools: - -Testing tools -************* - -Iris has various internal convenience functions and utilities available to -support writing tests. Using these makes tests quicker and easier to write, and -also consistent with the rest of Iris (which makes it easier to work with the -code). Most of these conveniences are accessed through the -:class:`iris.tests.IrisTest` class, from -which Iris' test classes then inherit. - -.. tip:: - - All functions listed on this page are defined within - :mod:`iris.tests.__init__.py` as methods of - :class:`iris.tests.IrisTest_nometa` (which :class:`iris.tests.IrisTest` - inherits from). They can be accessed within a test using - ``self.exampleFunction``. - -Custom assertions -================= - -:class:`iris.tests.IrisTest` supports a variety of custom unittest-style -assertions, such as :meth:`~iris.tests.IrisTest_nometa.assertArrayEqual`, -:meth:`~iris.tests.IrisTest_nometa.assertArrayAlmostEqual`. - -.. _create-missing: - -Saving results --------------- - -Some tests compare the generated output to the expected result contained in a -file. Custom assertions for this include -:meth:`~iris.tests.IrisTest_nometa.assertCMLApproxData` -:meth:`~iris.tests.IrisTest_nometa.assertCDL` -:meth:`~iris.tests.IrisTest_nometa.assertCML` and -:meth:`~iris.tests.IrisTest_nometa.assertTextFile`. See docstrings for more -information. - -.. note:: - - Sometimes code changes alter the results expected from a test containing the - above methods. These can be updated by removing the existing result files - and then running the file containing the test with a ``--create-missing`` - command line argument, or setting the ``IRIS_TEST_CREATE_MISSING`` - environment variable to anything non-zero. This will create the files rather - than erroring, allowing you to commit the updated results. - -Context managers -================ - -Capturing exceptions and logging --------------------------------- - -:class:`iris.tests.IrisTest` includes several context managers that can be used -to make test code tidier and easier to read. These include -:meth:`~iris.tests.IrisTest_nometa.assertWarnsRegexp` and -:meth:`~iris.tests.IrisTest_nometa.assertLogs`. - -Temporary files ---------------- - -It's also possible to generate temporary files in a concise fashion with -:meth:`~iris.tests.IrisTest_nometa.temp_filename`. - -Patching -======== - -:meth:`~iris.tests.IrisTest_nometa.patch` is a wrapper around ``unittest.patch`` -that will be automatically cleaned up at the end of the test. - -Graphic tests -============= - -As a package capable of generating graphical outputs, Iris has utilities for -creating and updating graphical tests - see :ref:`testing.graphics` for more -information. \ No newline at end of file diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 9022446cb8..7798e46481 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -42,7 +42,9 @@ This document explains the changes made to Iris for this release 💣 Incompatible Changes ======================= -#. N/A +#. :class:`iris.tests.IrisTest` is being replaced by :mod:`iris.tests._shared_utils`. + Once conversion from unittest to pytest is completed, :class:`iris.tests.IrisTest` + class will be deprecated. 🚀 Performance Enhancements @@ -66,13 +68,21 @@ This document explains the changes made to Iris for this release 📚 Documentation ================ -#. N/A +#. `@ESadek-MO`_ and `@trexfeathers`_ created :ref:`contributing_pytest_conversions` + as a guide for converting from ``unittest`` to ``pytest``. (:pull:`5785`) + +#. `@ESadek-MO`_ and `@trexfeathers`_ created a style guide for ``pytest`` tests, + and consolidated ``Test Categories`` and ``Testing Tools`` into + :ref:`contributing_tests` (:issue:`5574`, :pull:`5785`) 💼 Internal =========== -#. N/A +#. `@ESadek-MO`_ `@pp-mo`_ `@bjlittle`_ `@trexfeathers`_ and `@HGWright`_ have + converted around a third of Iris' ``unittest`` style tests to ``pytest``. This is + part of an ongoing effort to move from ``unittest`` to ``pytest``. (:pull:`6207`, + part of :issue:`6212`) .. comment diff --git a/lib/iris/tests/_shared_utils.py b/lib/iris/tests/_shared_utils.py new file mode 100644 index 0000000000..4a0d275cdd --- /dev/null +++ b/lib/iris/tests/_shared_utils.py @@ -0,0 +1,998 @@ +# 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. +"""Provides testing capabilities and customisations specific to Iris.""" + +import collections +from collections.abc import Mapping +import contextlib +import difflib +import filecmp +import functools +import gzip +import json +import math +import os +import os.path +from pathlib import Path +import re +import shutil +import subprocess +from typing import Optional +import warnings +import xml.dom.minidom +import zlib + +import numpy as np +import numpy.ma as ma +import pytest +import requests + +import iris.config +import iris.cube +import iris.fileformats +import iris.tests +import iris.tests.graphics as graphics +import iris.util + +MPL_AVAILABLE = graphics.MPL_AVAILABLE + + +try: + from osgeo import gdal # noqa +except ImportError: + GDAL_AVAILABLE = False +else: + GDAL_AVAILABLE = True + +try: + import iris_sample_data # noqa +except ImportError: + SAMPLE_DATA_AVAILABLE = False +else: + SAMPLE_DATA_AVAILABLE = True + +try: + import nc_time_axis # noqa + + NC_TIME_AXIS_AVAILABLE = True +except ImportError: + NC_TIME_AXIS_AVAILABLE = False + +try: + # Added a timeout to stop the call to requests.get hanging when running + # on a platform which has restricted/no internet access. + requests.get("https://github.com/SciTools/iris", timeout=10.0) + INET_AVAILABLE = True +except requests.exceptions.ConnectionError: + INET_AVAILABLE = False + +try: + import stratify # noqa + + STRATIFY_AVAILABLE = True +except ImportError: + STRATIFY_AVAILABLE = False + +#: Basepath for test results. +_RESULT_PATH = os.path.join(os.path.dirname(__file__), "results") + + +def _assert_masked_array(assertion, a, b, strict, **kwargs): + # Compare masks. + a_mask, b_mask = ma.getmaskarray(a), ma.getmaskarray(b) + np.testing.assert_array_equal(a_mask, b_mask) # pytest already? + + if strict: + # Compare all data values. + assertion(a.data, b.data, **kwargs) + else: + # Compare only unmasked data values. + assertion( + ma.compressed(a), + ma.compressed(b), + **kwargs, + ) + + +def assert_masked_array_equal(a, b, strict=False): + """Check that masked arrays are equal. This requires the + unmasked values and masks to be identical. + + Parameters + ---------- + a, b : array-like + Two arrays to compare. + strict : bool, optional + If True, perform a complete mask and data array equality check. + If False (default), the data array equality considers only unmasked + elements. + + """ + _assert_masked_array(np.testing.assert_array_equal, a, b, strict) + + +def assert_masked_array_almost_equal(a, b, decimal=6, strict=False): + """Check that masked arrays are almost equal. This requires the + masks to be identical, and the unmasked values to be almost + equal. + + Parameters + ---------- + a, b : array-like + Two arrays to compare. + strict : bool, optional + If True, perform a complete mask and data array equality check. + If False (default), the data array equality considers only unmasked + elements. + decimal : int, optional, default=6 + Equality tolerance level for + :meth:`numpy.testing.assert_array_almost_equal`, with the meaning + 'abs(desired-actual) < 0.5 * 10**(-decimal)' + + """ + _assert_masked_array( + np.testing.assert_array_almost_equal, a, b, strict, decimal=decimal + ) + + +def _assert_str_same( + reference_str, + test_str, + reference_filename, + type_comparison_name="Strings", +): + diff = "".join( + difflib.unified_diff( + reference_str.splitlines(1), + test_str.splitlines(1), + "Reference", + "Test result", + "", + "", + 0, + ) + ) + fail_string = ( + f"{type_comparison_name} do not match: {reference_filename}\n" f"{diff}" + ) + assert reference_str == test_str, fail_string + + +def get_data_path(relative_path): + """Return the absolute path to a data file when given the relative path + as a string, or sequence of strings. + + """ + if not isinstance(relative_path, str): + relative_path = os.path.join(*relative_path) + test_data_dir = iris.config.TEST_DATA_DIR + if test_data_dir is None: + test_data_dir = "" + data_path = os.path.join(test_data_dir, relative_path) + + if iris.tests._EXPORT_DATAPATHS_FILE is not None: + iris.tests._EXPORT_DATAPATHS_FILE.write(data_path + "\n") + + if isinstance(data_path, str) and not os.path.exists(data_path): + # if the file is gzipped, ungzip it and return the path of the ungzipped + # file. + gzipped_fname = data_path + ".gz" + if os.path.exists(gzipped_fname): + with gzip.open(gzipped_fname, "rb") as gz_fh: + try: + with open(data_path, "wb") as fh: + fh.writelines(gz_fh) + except IOError: + # Put ungzipped data file in a temporary path, since we + # can't write to the original path (maybe it is owned by + # the system.) + _, ext = os.path.splitext(data_path) + data_path = iris.util.create_temp_filename(suffix=ext) + with open(data_path, "wb") as fh: + fh.writelines(gz_fh) + + return data_path + + +def get_result_path(relative_path): + """Returns the absolute path to a result file when given the relative path + as a string, or sequence of strings. + + """ + if not isinstance(relative_path, str): + relative_path = os.path.join(*relative_path) + return os.path.abspath(os.path.join(_RESULT_PATH, relative_path)) + + +def _check_for_request_fixture(request, func_name: str): + """Raise an error if the first argument is not a pytest.FixtureRequest. + + Written to provide the clearest possible message for devs refactoring from + the deprecated IrisTest style tests. + """ + if not hasattr(request, "fixturenames"): + message = ( + f"{func_name}() expected: pytest.FixtureRequest instance, got: " + f"{request}" + ) + raise ValueError(message) + + +def result_path(request: pytest.FixtureRequest, basename=None, ext=""): + """Generate the path to a test result; from the calling file, class, method. + + Parameters + ---------- + request : pytest.FixtureRequest + A pytest ``request`` fixture passed down from the calling test. Is + interpreted for the automatic generation of a result path. See Examples + for how to access the ``request`` fixture. + basename : optional, default=None + File basename. If omitted, this is generated from the calling method. + ext : str, optional, default="" + Appended file extension. + + Examples + -------- + The PyTest ``request`` fixture is always available as a test argument: + + >>> def test_one(request): + ... path_one = (result_path(request)) + + """ + _check_for_request_fixture(request, "result_path") + + if __package__ != "iris.tests": + # Relying on this being the location so that we can derive the full + # path of the tests root. + # Would normally use assert, but this means something to PyTest. + message = "result_path() must be in the iris.tests root to function." + raise RuntimeError(message) + tests_root = Path(__file__).parent + + if ext and not ext.startswith("."): + ext = f".{ext}" + + def remove_test(string: str): + result = string + result = re.sub(r"(?i)test_", "", result) + result = re.sub(r"(?i)test", "", result) + return result + + # Generate the directory name from the calling file name. + output_path = get_result_path("") / request.path.relative_to(tests_root) + output_path = output_path.with_suffix("") + output_path = output_path.with_name(remove_test(output_path.name)) + + # Optionally add a class subdirectory if called from a class. + if request.cls is not None: + output_class = remove_test(request.cls.__name__) + output_path = output_path / output_class + + # Generate the file name from the calling function name. + node_name = request.node.originalname + if basename is not None: + output_func = basename + elif node_name == "": + output_func = "" + else: + output_func = remove_test(node_name) + output_path = output_path / output_func + + # Optionally use parameter values as the file name if parameterised. + # (The function becomes a subdirectory in this case). + if hasattr(request.node, "callspec"): + output_path = output_path / request.node.callspec.id + + output_path = output_path.with_suffix(ext) + + return str(output_path) + + +def assert_CML_approx_data( + request: pytest.FixtureRequest, cubes, reference_filename=None, **kwargs +): + # passes args and kwargs on to approx equal + # See result_path() Examples for how to access the ``request`` fixture. + + _check_for_request_fixture(request, "assert_CML_approx_data") + + if isinstance(cubes, iris.cube.Cube): + cubes = [cubes] + if reference_filename is None: + reference_filename = result_path(request, None, "cml") + reference_filename = [get_result_path(reference_filename)] + for i, cube in enumerate(cubes): + fname = list(reference_filename) + # don't want the ".cml" for the json stats file + if fname[-1].endswith(".cml"): + fname[-1] = fname[-1][:-4] + fname[-1] += ".data.%d.json" % i + assert_data_almost_equal(cube.data, fname, **kwargs) + assert_CML(request, cubes, reference_filename, checksum=False) + + +def assert_CDL( + request: pytest.FixtureRequest, netcdf_filename, reference_filename=None, flags="-h" +): + """Test that the CDL for the given netCDF file matches the contents + of the reference file. + + If the environment variable IRIS_TEST_CREATE_MISSING is + non-empty, the reference file is created if it doesn't exist. + + Parameters + ---------- + request : pytest.FixtureRequest + A pytest ``request`` fixture passed down from the calling test. Is + required by :func:`result_path`. See :func:`result_path` Examples + for how to access the ``request`` fixture. + netcdf_filename : + The path to the netCDF file. + reference_filename : optional, default=None + The relative path (relative to the test results directory). + If omitted, the result is generated from the calling + method's name, class, and module using + :meth:`iris.tests.IrisTest.result_path`. + flags : str, optional + Command-line flags for `ncdump`, as either a whitespace + separated string or an iterable. Defaults to '-h'. + + """ + _check_for_request_fixture(request, "assert_CDL") + + if reference_filename is None: + reference_path = result_path(request, None, "cdl") + else: + reference_path = get_result_path(reference_filename) + + # Convert the netCDF file to CDL file format. + if flags is None: + flags = [] + elif isinstance(flags, str): + flags = flags.split() + else: + flags = list(map(str, flags)) + + try: + exe_path = env_bin_path("ncdump") + args = [exe_path] + flags + [netcdf_filename] + cdl = subprocess.check_output(args, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + print(exc.output) + raise + + # Ingest the CDL for comparison, excluding first line. + lines = cdl.decode("ascii").splitlines() + lines = lines[1:] + + # Ignore any lines of the general form "... :_NCProperties = ..." + # (an extra global attribute, displayed by older versions of ncdump). + re_ncprop = re.compile(r"^\s*:_NCProperties *=") + lines = [line for line in lines if not re_ncprop.match(line)] + + # Sort the dimensions (except for the first, which can be unlimited). + # This gives consistent CDL across different platforms. + def sort_key(line): + return ("UNLIMITED" not in line, line) + + dimension_lines = slice(lines.index("dimensions:") + 1, lines.index("variables:")) + lines[dimension_lines] = sorted(lines[dimension_lines], key=sort_key) + cdl = "\n".join(lines) + "\n" # type: ignore[assignment] + + _check_same(cdl, reference_path, type_comparison_name="CDL") + + +def assert_CML( + request: pytest.FixtureRequest, cubes, reference_filename=None, checksum=True +): + """Test that the CML for the given cubes matches the contents of + the reference file. + + If the environment variable IRIS_TEST_CREATE_MISSING is + non-empty, the reference file is created if it doesn't exist. + + Parameters + ---------- + request : pytest.FixtureRequest + A pytest ``request`` fixture passed down from the calling test. Is + required by :func:`result_path`. See :func:`result_path` Examples + for how to access the ``request`` fixture. + cubes : + Either a Cube or a sequence of Cubes. + reference_filename : optional, default=None + The relative path (relative to the test results directory). + If omitted, the result is generated from the calling + method's name, class, and module using + :meth:`iris.tests.IrisTest.result_path`. + checksum : bool, optional + When True, causes the CML to include a checksum for each + Cube's data. Defaults to True. + + """ + _check_for_request_fixture(request, "assert_CML") + + if isinstance(cubes, iris.cube.Cube): + cubes = [cubes] + if reference_filename is None: + reference_filename = result_path(request, None, "cml") + + if isinstance(cubes, (list, tuple)): + xml = iris.cube.CubeList(cubes).xml( + checksum=checksum, order=False, byteorder=False + ) + else: + xml = cubes.xml(checksum=checksum, order=False, byteorder=False) + reference_path = get_result_path(reference_filename) + _check_same(xml, reference_path) + + +def assert_text_file(source_filename, reference_filename, desc="text file"): + """Check if two text files are the same, printing any diffs.""" + with open(source_filename) as source_file: + source_text = source_file.readlines() + with open(reference_filename) as reference_file: + reference_text = reference_file.readlines() + + diff = "".join( + difflib.unified_diff( + reference_text, + source_text, + "Reference", + "Test result", + "", + "", + 0, + ) + ) + fail_string = ( + f"{desc} does not match: reference file " f"{reference_filename} \n {diff}" + ) + assert reference_text == source_text, fail_string + + +def assert_data_almost_equal(data, reference_filename, **kwargs): + reference_path = get_result_path(reference_filename) + if _check_reference_file(reference_path): + kwargs.setdefault("err_msg", "Reference file %s" % reference_path) + with open(reference_path, "r") as reference_file: + stats = json.load(reference_file) + assert stats.get("shape", []) == list(data.shape) + assert stats.get("masked", False) == ma.is_masked(data) + nstats = np.array( + ( + stats.get("mean", 0.0), + stats.get("std", 0.0), + stats.get("max", 0.0), + stats.get("min", 0.0), + ), + dtype=np.float64, + ) + if math.isnan(stats.get("mean", 0.0)): + assert math.isnan(data.mean()) + else: + data_stats = np.array( + (data.mean(), data.std(), data.max(), data.min()), + dtype=np.float64, + ) + assert_array_all_close(nstats, data_stats, **kwargs) + else: + _ensure_folder(reference_path) + stats = collections.OrderedDict( + [ + ("std", np.float64(data.std())), + ("min", np.float64(data.min())), + ("max", np.float64(data.max())), + ("shape", data.shape), + ("masked", ma.is_masked(data)), + ("mean", np.float64(data.mean())), + ] + ) + with open(reference_path, "w") as reference_file: + reference_file.write(json.dumps(stats)) + + +def assert_files_equal(test_filename, reference_filename): + reference_path = get_result_path(reference_filename) + if _check_reference_file(reference_path): + fmt = "test file {!r} does not match reference {!r}." + assert filecmp.cmp(test_filename, reference_path) and fmt.format( + test_filename, reference_path + ) + else: + _ensure_folder(reference_path) + shutil.copy(test_filename, reference_path) + + +def assert_string(request: pytest.FixtureRequest, string, reference_filename=None): + """Test that `string` matches the contents of the reference file. + + If the environment variable IRIS_TEST_CREATE_MISSING is + non-empty, the reference file is created if it doesn't exist. + + Parameters + ---------- + request: pytest.FixtureRequest + A pytest ``request`` fixture passed down from the calling test. Is + required by :func:`result_path`. See :func:`result_path` Examples + for how to access the ``request`` fixture. + string : str + The string to check. + reference_filename : optional, default=None + The relative path (relative to the test results directory). + If omitted, the result is generated from the calling + method's name, class, and module using + :meth:`iris.tests.IrisTest.result_path`. + + """ + _check_for_request_fixture(request, "assert_string") + + if reference_filename is None: + reference_path = result_path(request, None, "txt") + else: + reference_path = get_result_path(reference_filename) + _check_same(string, reference_path, type_comparison_name="Strings") + + +def assert_repr(request: pytest.FixtureRequest, obj, reference_filename): + assert_string(request, repr(obj), reference_filename) + + +def _check_same(item, reference_path, type_comparison_name="CML"): + if _check_reference_file(reference_path): + with open(reference_path, "rb") as reference_fh: + reference = "".join(part.decode("utf-8") for part in reference_fh) + _assert_str_same(reference, item, reference_path, type_comparison_name) + else: + _ensure_folder(reference_path) + with open(reference_path, "wb") as reference_fh: + reference_fh.writelines(part.encode("utf-8") for part in item) + + +def assert_XML_element(obj, reference_filename): + """Calls the xml_element method given obj and asserts the result is the same as the test file.""" + doc = xml.dom.minidom.Document() + doc.appendChild(obj.xml_element(doc)) + # sort the attributes on xml elements before testing against known good state. + # this is to be compatible with stored test output where xml attrs are stored in alphabetical order, + # (which was default behaviour in python <3.8, but changed to insert order in >3.8) + doc = iris.cube.Cube._sort_xml_attrs(doc) + pretty_xml = doc.toprettyxml(indent=" ") + reference_path = get_result_path(reference_filename) + _check_same(pretty_xml, reference_path, type_comparison_name="XML") + + +def assert_array_equal(a, b, err_msg=""): + np.testing.assert_array_equal(a, b, err_msg=err_msg) + + +@contextlib.contextmanager +def _record_warning_matches(expected_regexp=""): + # Record warnings raised matching a given expression. + matches = [] + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + yield matches + messages = [str(warning.message) for warning in w] + expr = re.compile(expected_regexp) + matches.extend(message for message in messages if expr.search(message)) + + +@contextlib.contextmanager +def assert_logs(caplog, logger=None, level=None, msg_regex=None): + """If msg_regex is used, checks that the result is a single message of the specified + level, and that it matches this regex. + + Checks that there is at least one message logged at the given parameters, + but then *also* exercises the message formatters of all the logger's handlers, + just to check that there are no formatting errors. + + """ + with caplog.at_level(level, logger.name): + assert len(caplog.records) != 0 + # Check for any formatting errors by running all the formatters. + for record in caplog.records: + for handler in caplog.logger.handlers: + handler.format(record) + + # Check message, if requested. + if msg_regex: + assert len(caplog.records) == 1 + rec = caplog.records[0] + assert level == rec.levelname + assert re.match(msg_regex, rec.msg) + + +@contextlib.contextmanager +def assert_no_warnings_regexp(expected_regexp=""): + # Check that no warning matching the given expression is raised. + with _record_warning_matches(expected_regexp) as matches: + yield + + msg = "Unexpected warning(s) raised, matching '{}' : {!r}." + msg = msg.format(expected_regexp, matches) + assert not matches, msg + + +def assert_array_almost_equal(a, b, decimal=6): + np.testing.assert_array_almost_equal(a, b, decimal=decimal) + + +def assert_array_all_close(a, b, rtol=1.0e-7, atol=1.0e-8, **kwargs): + """Check arrays are equal, within given relative + absolute tolerances. + + Parameters + ---------- + a, b : array-like + Two arrays to compare. + rtol, atol : float, optional + Relative and absolute tolerances to apply. + + Other Parameters + ---------------- + Any additional kwargs are passed to numpy.testing.assert_allclose. + + Performs pointwise toleranced comparison, and raises an assertion if + the two are not equal 'near enough'. + For full details see underlying routine numpy.allclose. + + """ + # Handle the 'err_msg' kwarg, which is the only API difference + # between np.allclose and np.testing_assert_allclose. + msg = kwargs.pop("err_msg", None) + ok = np.allclose(a, b, rtol=rtol, atol=atol, **kwargs) + if not ok: + # Calculate errors above a pointwise tolerance : The method is + # taken from "numpy.core.numeric.isclose". + a, b = np.broadcast_arrays(a, b) + errors = np.abs(a - b) - atol + rtol * np.abs(b) + worst_inds = np.unravel_index(np.argmax(errors.flat), errors.shape) + + if msg is None: + # Build a more useful message than np.testing.assert_allclose. + msg = ( + '\nARRAY CHECK FAILED "assert_array_all_close" :' + "\n with shapes={} {}, atol={}, rtol={}" + "\n worst at element {} : a={} b={}" + "\n absolute error ~{:.3g}, equivalent to rtol ~{:.3e}" + ) + aval, bval = a[worst_inds], b[worst_inds] + absdiff = np.abs(aval - bval) + equiv_rtol = absdiff / bval + msg = msg.format( + a.shape, + b.shape, + atol, + rtol, + worst_inds, + aval, + bval, + absdiff, + equiv_rtol, + ) + + raise AssertionError(msg) + + +def file_checksum(file_path): + """Generate checksum from file.""" + with open(file_path, "rb") as in_file: + return zlib.crc32(in_file.read()) + + +def _check_reference_file(reference_path): + reference_exists = os.path.isfile(reference_path) + if not (reference_exists or os.environ.get("IRIS_TEST_CREATE_MISSING")): + msg = "Missing test result: {}".format(reference_path) + raise AssertionError(msg) + return reference_exists + + +def _ensure_folder(path): + dir_path = os.path.dirname(path) + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + +# todo: relied on unitest functionality, need to find a pytest alternative +def patch(*args, **kwargs): + """Install a mock.patch, to be removed after the current test. + + The patch is created with mock.patch(*args, **kwargs). + + Returns + ------- + The substitute object returned by patch.start(). + + Examples + -------- + :: + + mock_call = self.patch('module.Class.call', return_value=1) + module_Class_instance.call(3, 4) + self.assertEqual(mock_call.call_args_list, [mock.call(3, 4)]) + + """ + raise NotImplementedError() + + +def assert_array_shape_stats(result, shape, mean, std_dev, rtol=1e-6): + """Assert that the result, a cube, has the provided shape and that the + mean and standard deviation of the data array are also as provided. + Thus build confidence that a cube processing operation, such as a + cube.regrid, has maintained its behaviour. + + """ + assert result.shape == shape + assert_array_all_close(result.data.mean(), mean, rtol=rtol) + assert_array_all_close(result.data.std(), std_dev, rtol=rtol) + + +def assert_dict_equal(lhs, rhs): + """Dictionary Comparison. + + This allows us to cope with dictionary comparison where the value of a key + may be a numpy array. + """ + emsg = f"Provided LHS argument is not a 'Mapping', got {type(lhs)}." + assert isinstance(lhs, Mapping), emsg + + emsg = f"Provided RHS argument is not a 'Mapping', got {type(rhs)}." + assert isinstance(rhs, Mapping), emsg + + emsg = f"{lhs!r} != {rhs!r}." + assert set(lhs.keys()) == set(rhs.keys()), emsg + + for key in lhs.keys(): + lvalue, rvalue = lhs[key], rhs[key] + + if ma.isMaskedArray(lvalue) or ma.isMaskedArray(rvalue): + if not ma.isMaskedArray(lvalue): + emsg = ( + f"Dictionary key {key!r} values are not equal, " + f"the LHS value has type {type(lvalue)} and " + f"the RHS value has type {ma.core.MaskedArray}." + ) + raise AssertionError(emsg) + + if not ma.isMaskedArray(rvalue): + emsg = ( + f"Dictionary key {key!r} values are not equal, " + f"the LHS value has type {ma.core.MaskedArray} and " + f"the RHS value has type {type(lvalue)}." + ) + raise AssertionError(emsg) + + assert_masked_array_equal(lvalue, rvalue) + elif isinstance(lvalue, np.ndarray) or isinstance(rvalue, np.ndarray): + if not isinstance(lvalue, np.ndarray): + emsg = ( + f"Dictionary key {key!r} values are not equal, " + f"the LHS value has type {type(lvalue)} and " + f"the RHS value has type {np.ndarray}." + ) + raise AssertionError(emsg) + + if not isinstance(rvalue, np.ndarray): + emsg = ( + f"Dictionary key {key!r} values are not equal, " + f"the LHS value has type {np.ndarray} and " + f"the RHS value has type {type(rvalue)}." + ) + raise AssertionError(emsg) + + assert_array_equal(lvalue, rvalue) + else: + if lvalue != rvalue: + emsg = ( + f"Dictionary key {key!r} values are not equal, " + f"{lvalue!r} != {rvalue!r}." + ) + raise AssertionError(emsg) + + +def assert_equal_and_kind(value, expected): + # Check a value, and also its type 'kind' = float/integer/string. + assert value == expected + assert np.array(value).dtype.kind == np.array(expected).dtype.kind + + +@contextlib.contextmanager +def pp_cube_save_test( + reference_txt_path, + reference_cubes=None, + reference_pp_path=None, + **kwargs, +): + """A context manager for testing the saving of Cubes to PP files. + + Args: + + * reference_txt_path: + The path of the file containing the textual PP reference data. + + Kwargs: + + * reference_cubes: + The cube(s) from which the textual PP reference can be re-built if necessary. + * reference_pp_path: + The location of a PP file from which the textual PP reference can be re-built if necessary. + NB. The "reference_cubes" argument takes precedence over this argument. + + The return value from the context manager is the name of a temporary file + into which the PP data to be tested should be saved. + + Example:: + with self.pp_cube_save_test(reference_txt_path, reference_cubes=cubes) as temp_pp_path: + iris.save(cubes, temp_pp_path) + + """ + + def _create_reference_txt(txt_path, pp_path): + # Load the reference data + pp_fields = list(iris.fileformats.pp.load(pp_path)) + for pp_field in pp_fields: + pp_field.data + + # Clear any header words we don't use + unused = ("lbexp", "lbegin", "lbnrec", "lbproj", "lbtyp") + for pp_field in pp_fields: + for word_name in unused: + setattr(pp_field, word_name, 0) + + # Save the textual representation of the PP fields + with open(txt_path, "w") as txt_file: + txt_file.writelines(str(pp_fields)) + + # Watch out for a missing reference text file + if not os.path.isfile(reference_txt_path): + if reference_cubes: + temp_pp_path = iris.util.create_temp_filename(".pp") + try: + iris.save(reference_cubes, temp_pp_path, **kwargs) + _create_reference_txt(reference_txt_path, temp_pp_path) + finally: + os.remove(temp_pp_path) + elif reference_pp_path: + _create_reference_txt(reference_txt_path, reference_pp_path) + else: + raise ValueError("Missing all of reference txt file, cubes, and PP path.") + + temp_pp_path = iris.util.create_temp_filename(".pp") + try: + # This value is returned to the target of the "with" statement's "as" clause. + yield temp_pp_path + + # Load deferred data for all of the fields (but don't do anything with it) + pp_fields = list(iris.fileformats.pp.load(temp_pp_path)) + for pp_field in pp_fields: + pp_field.data + with open(reference_txt_path, "r") as reference_fh: + reference = "".join(reference_fh) + _assert_str_same( + reference + "\n", + str(pp_fields) + "\n", + reference_txt_path, + type_comparison_name="PP files", + ) + finally: + os.remove(temp_pp_path) + + +def skip_data(fn): + """Decorator to choose whether to run tests, based on the availability of + external data. + + Example usage: + @skip_data + class MyDataTests(tests.IrisTest): + ... + + """ + no_data = ( + not iris.config.TEST_DATA_DIR + or not os.path.isdir(iris.config.TEST_DATA_DIR) + or os.environ.get("IRIS_TEST_NO_DATA") + ) + + skip = pytest.mark.skipif( + condition=no_data, reason="Test(s) require external data." + ) + + return skip(fn) + + +def skip_gdal(fn): + """Decorator to choose whether to run tests, based on the availability of the + GDAL library. + + Example usage: + @skip_gdal + class MyGeoTiffTests(test.IrisTest): + ... + + """ + skip = pytest.mark.skipif( + condition=not GDAL_AVAILABLE, reason="Test requires 'gdal'." + ) + return skip(fn) + + +skip_plot = graphics.skip_plot + +skip_sample_data = pytest.mark.skipif( + not SAMPLE_DATA_AVAILABLE, + reason=('Test(s) require "iris-sample-data", ' "which is not available."), +) + + +skip_nc_time_axis = pytest.mark.skipif( + not NC_TIME_AXIS_AVAILABLE, + reason='Test(s) require "nc_time_axis", which is not available.', +) + + +skip_inet = pytest.mark.skipif( + not INET_AVAILABLE, + reason=('Test(s) require an "internet connection", ' "which is not available."), +) + + +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. + + """ + + @functools.wraps(func) + def wrapped(*args, **kwargs): + with pytest.mock.patch("warnings.warn") as warn: + result = func(*args, **kwargs) + assert 0 == warn.call_count, "Got unexpected warnings.\n{}".format( + warn.call_args_list + ) + return result + + return wrapped + + +def env_bin_path(exe_name: Optional[str] = None): + """Return a Path object for (an executable in) the environment bin directory. + + Parameters + ---------- + exe_name : str + If set, the name of an executable to append to the path. + + Returns + ------- + exe_path : Path + A path to the bin directory, or an executable file within it. + + Notes + ----- + For use in tests which spawn commands which should call executables within + the Python environment, since many IDEs (Eclipse, PyCharm) don't + automatically include this location in $PATH (as opposed to $PYTHONPATH). + """ + exe_path = Path(os.__file__) + exe_path = (exe_path / "../../../bin").resolve() + if exe_name is not None: + exe_path = exe_path / exe_name + return exe_path + + +class GraphicsTest: + """All inheriting classes automatically have access to ``self.check_graphic()``.""" + + @pytest.fixture(autouse=True) + def _get_check_graphics(self, check_graphic_caller): + self.check_graphic = check_graphic_caller diff --git a/lib/iris/tests/conftest.py b/lib/iris/tests/conftest.py new file mode 100644 index 0000000000..2a3341d8c0 --- /dev/null +++ b/lib/iris/tests/conftest.py @@ -0,0 +1,55 @@ +# 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. +"""Top-level fixture infra-structure. + +Before adding to this: consider if :mod:`iris.tests.unit.conftest` or +:mod:`iris.tests.integration.conftest` might be more appropriate. +""" + +from collections import defaultdict +from typing import Callable + +import pytest + +import iris.tests.graphics + + +@pytest.fixture(scope="session", autouse=True) +def test_call_counter(): + """Provide a session-persistent tracker of the number of calls per test name. + + Used by :func:`_unique_id` to ensure uniqueness if called multiple times + per test. + """ + counter = defaultdict(int) + return counter + + +@pytest.fixture +def _unique_id(request: pytest.FixtureRequest, test_call_counter) -> Callable: + """Provide a function returning a unique ID of calling test and call number. + + Example: ``iris.tests.unit.test_cube.TestCube.test_data.my_param.0`` + + Used by :func:`iris.tests.graphics.check_graphic_caller` to ensure unique + image names. + """ + id_sequence = [request.module.__name__, request.node.originalname] + if request.cls is not None: + id_sequence.insert(-1, request.cls.__name__) + if hasattr(request.node, "callspec"): + id_sequence.append(request.node.callspec.id) + test_id = ".".join(id_sequence) + + def generate_id(): + assertion_id = test_call_counter[test_id] + test_call_counter[test_id] += 1 + return f"{test_id}.{assertion_id}" + + return generate_id + + +# Share this existing fixture from the expected location. +check_graphic_caller = iris.tests.graphics._check_graphic_caller diff --git a/lib/iris/tests/graphics/README.md b/lib/iris/tests/graphics/README.md index 069fc01f70..b345843109 100644 --- a/lib/iris/tests/graphics/README.md +++ b/lib/iris/tests/graphics/README.md @@ -24,8 +24,9 @@ perceived as it may be a simple pixel shift. ## Testing Strategy -The `iris.tests.IrisTest.check_graphic` test routine calls out to -`iris.tests.graphics.check_graphic` which tests against the **acceptable** +The `iris.tests.graphics.check_graphic` function - accessed via the +`check_graphic_caller` fixture (PyTest) or `iris.tests.IrisTest.check_graphic` +(unittest) - tests against the **acceptable** result. It does this using an image **hash** comparison technique which allows us to be robust against minor variations based on underlying library updates. @@ -48,4 +49,4 @@ This consists of: * The utility script `iris/tests/idiff.py` automates checking, enabling the developer to easily compare the proposed new **acceptable** result image - against the existing accepted baseline image, for each failing test. \ No newline at end of file + against the existing accepted baseline image, for each failing test. diff --git a/lib/iris/tests/graphics/__init__.py b/lib/iris/tests/graphics/__init__.py index 1fe199c8b7..7fb2074ca0 100644 --- a/lib/iris/tests/graphics/__init__.py +++ b/lib/iris/tests/graphics/__init__.py @@ -18,10 +18,10 @@ from pathlib import Path import sys import threading -from typing import Callable, Dict, Union -import unittest +from typing import Callable, Dict, Iterator, Union import filelock +import pytest # Test for availability of matplotlib. # (And remove matplotlib as an iris.tests dependency.) @@ -50,7 +50,7 @@ _DISPLAY_FIGURES = True # Threading non re-entrant blocking lock to ensure thread-safe plotting in the -# GraphicsTestMixin. +# GraphicsTestMixin and check_graphics_caller. _lock = threading.Lock() #: Default perceptual hash size. @@ -241,6 +241,7 @@ def _create_missing(phash: str) -> None: class GraphicsTestMixin: + # TODO: deprecate this in favour of check_graphic_caller. def setUp(self) -> None: # Acquire threading non re-entrant blocking lock to ensure # thread-safe plotting. @@ -263,15 +264,57 @@ def skip_plot(fn: Callable) -> Callable: """Decorator to choose whether to run tests, based on the availability of the matplotlib library. - Example usage: - @skip_plot - class MyPlotTests(test.GraphicsTest): - ... + Examples + -------- + >>> @skip_plot + >>> class TestMyPlots: + ... def test_my_plot(self, check_graphic_caller): + ... pass + ... + >>> @skip_plot + >>> def test_my_plot(check_graphic_caller): + ... pass """ - skip = unittest.skipIf( + skip = pytest.mark.skipIf( condition=not MPL_AVAILABLE, reason="Graphics tests require the matplotlib library.", ) return skip(fn) + + +@pytest.fixture +def _check_graphic_caller(_unique_id) -> Iterator[Callable]: + """Provide a function calling :func:`check_graphic` with safe configuration. + + Ensures a safe Matplotlib setup (and tears down afterwards), and generates + a unique test id for each call. + + Examples + -------- + >>> def test_my_plot(check_graphic_caller): + ... # ... do some plotting ... + ... check_graphic_caller() + """ + from iris.tests import _RESULT_PATH + + # Acquire threading non re-entrant blocking lock to ensure + # thread-safe plotting. + _lock.acquire() + # Make sure we have no unclosed plots from previous tests before + # generating this one. + if MPL_AVAILABLE: + plt.close("all") + + def call_check_graphic(): + check_graphic(_unique_id(), _RESULT_PATH) + + yield call_check_graphic + + # If a plotting test bombs out it can leave the current figure + # in an odd state, so we make sure it's been disposed of. + if MPL_AVAILABLE: + plt.close("all") + # Release the non re-entrant blocking lock. + _lock.release() diff --git a/lib/iris/tests/graphics/idiff.py b/lib/iris/tests/graphics/idiff.py index 64d690e55d..cbd9d3b891 100755 --- a/lib/iris/tests/graphics/idiff.py +++ b/lib/iris/tests/graphics/idiff.py @@ -28,6 +28,7 @@ from iris.warnings import IrisIgnoringWarning # noqa import iris.tests # noqa +from iris.tests import _shared_utils import iris.tests.graphics as graphics # noqa # Allows restoration of test id from result image name @@ -118,7 +119,7 @@ def step_over_diffs(result_dir, display=True): for fname in result_dir.glob(f"*{_POSTFIX_DIFF}"): fname.unlink() - reference_image_dir = Path(iris.tests.get_data_path("images")) + reference_image_dir = Path(_shared_utils.get_data_path("images")) repo = graphics.read_repo_json() # Filter out all non-test result image files. diff --git a/lib/iris/tests/graphics/recreate_imagerepo.py b/lib/iris/tests/graphics/recreate_imagerepo.py index 5261f0cc29..ca2f65279f 100755 --- a/lib/iris/tests/graphics/recreate_imagerepo.py +++ b/lib/iris/tests/graphics/recreate_imagerepo.py @@ -10,7 +10,7 @@ from imagehash import hex_to_hash -import iris.tests +from iris.tests import _shared_utils import iris.tests.graphics as graphics @@ -47,7 +47,7 @@ def update_json(baseline_image_dir: Path, dry_run: bool = False): if __name__ == "__main__": - default_baseline_image_dir = Path(iris.tests.IrisTest.get_data_path("images")) + default_baseline_image_dir = Path(_shared_utils.get_data_path("images")) description = ( "Update imagerepo.json based on contents of the baseline image directory" ) diff --git a/lib/iris/tests/integration/plot/test_animate.py b/lib/iris/tests/integration/plot/test_animate.py index 4afee0c463..59f269e9f7 100644 --- a/lib/iris/tests/integration/plot/test_animate.py +++ b/lib/iris/tests/integration/plot/test_animate.py @@ -4,24 +4,22 @@ # See LICENSE in the root of the repository for full licensing details. """Integration tests for :func:`iris.plot.animate`.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import numpy as np +import pytest import iris from iris.coord_systems import GeogCS +from iris.tests import _shared_utils # Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt -@tests.skip_plot -class IntegrationTest(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_plot +class IntegrationTest(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): cube = iris.cube.Cube(np.arange(36, dtype=np.int32).reshape((3, 3, 4))) cs = GeogCS(6371229) @@ -68,7 +66,3 @@ def test_cube_animation(self): for anim, d in zip(ani, data): anim._draw_next_frame(d, blit=False) self.check_graphic() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/plot/test_colorbar.py b/lib/iris/tests/integration/plot/test_colorbar.py index 4a3fd27a80..e02e51db78 100644 --- a/lib/iris/tests/integration/plot/test_colorbar.py +++ b/lib/iris/tests/integration/plot/test_colorbar.py @@ -7,26 +7,24 @@ """ -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import numpy as np +import pytest from iris.coords import AuxCoord +from iris.tests import _shared_utils import iris.tests.stock # Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import matplotlib.pyplot as plt from iris.plot import contour, contourf, pcolor, pcolormesh, points, scatter -@tests.skip_plot -class TestColorBarCreation(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_plot +class TestColorBarCreation(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): self.draw_functions = (contour, contourf, pcolormesh, pcolor) self.cube = iris.tests.stock.lat_lon_cube() self.cube.coord("longitude").guess_bounds() @@ -46,49 +44,37 @@ def test_common_draw_functions(self): for draw_function in self.draw_functions: mappable = draw_function(self.cube) cbar = plt.colorbar() - self.assertIs( - cbar.mappable, - mappable, - msg="Problem with draw function iris.plot.{}".format( - draw_function.__name__ - ), - ) + assert ( + cbar.mappable is mappable + ), "Problem with draw function iris.plot.{}".format(draw_function.__name__) def test_common_draw_functions_specified_mappable(self): for draw_function in self.draw_functions: mappable_initial = draw_function(self.cube, cmap="cool") _ = draw_function(self.cube) cbar = plt.colorbar(mappable_initial) - self.assertIs( - cbar.mappable, - mappable_initial, - msg="Problem with draw function iris.plot.{}".format( - draw_function.__name__ - ), - ) + assert ( + cbar.mappable is mappable_initial + ), "Problem with draw function iris.plot.{}".format(draw_function.__name__) def test_points_with_c_kwarg(self): mappable = points(self.cube, c=self.cube.data) cbar = plt.colorbar() - self.assertIs(cbar.mappable, mappable) + assert cbar.mappable is mappable def test_points_with_c_kwarg_specified_mappable(self): mappable_initial = points(self.cube, c=self.cube.data, cmap="cool") _ = points(self.cube, c=self.cube.data) cbar = plt.colorbar(mappable_initial) - self.assertIs(cbar.mappable, mappable_initial) + assert cbar.mappable is mappable_initial def test_scatter_with_c_kwarg(self): mappable = scatter(self.traj_lon, self.traj_lat, c=self.traj_lon.points) cbar = plt.colorbar() - self.assertIs(cbar.mappable, mappable) + assert cbar.mappable is mappable def test_scatter_with_c_kwarg_specified_mappable(self): mappable_initial = scatter(self.traj_lon, self.traj_lat, c=self.traj_lon.points) _ = scatter(self.traj_lon, self.traj_lat, c=self.traj_lon.points, cmap="cool") cbar = plt.colorbar(mappable_initial) - self.assertIs(cbar.mappable, mappable_initial) - - -if __name__ == "__main__": - tests.main() + assert cbar.mappable is mappable_initial diff --git a/lib/iris/tests/integration/plot/test_netcdftime.py b/lib/iris/tests/integration/plot/test_netcdftime.py index 750de9fdf3..408c04cb3b 100644 --- a/lib/iris/tests/integration/plot/test_netcdftime.py +++ b/lib/iris/tests/integration/plot/test_netcdftime.py @@ -4,24 +4,21 @@ # See LICENSE in the root of the repository for full licensing details. """Test plot of time coord with non-standard calendar.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - from cf_units import Unit import cftime import numpy as np from iris.coords import AuxCoord +from iris.tests import _shared_utils # Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt -@tests.skip_nc_time_axis -@tests.skip_plot -class Test(tests.GraphicsTest): +@_shared_utils.skip_nc_time_axis +@_shared_utils.skip_plot +class Test(_shared_utils.GraphicsTest): def test_360_day_calendar(self): n = 360 calendar = "360_day" @@ -44,8 +41,4 @@ def test_360_day_calendar(self): expected_ydata = times (line1,) = iplt.plot(time_coord) result_ydata = line1.get_ydata() - self.assertArrayEqual(expected_ydata, result_ydata) - - -if __name__ == "__main__": - tests.main() + _shared_utils.assert_array_equal(expected_ydata, result_ydata) diff --git a/lib/iris/tests/integration/plot/test_nzdateline.py b/lib/iris/tests/integration/plot/test_nzdateline.py index cb119f5b27..6e2241863e 100644 --- a/lib/iris/tests/integration/plot/test_nzdateline.py +++ b/lib/iris/tests/integration/plot/test_nzdateline.py @@ -4,33 +4,25 @@ # See LICENSE in the root of the repository for full licensing details. """Test set up of limited area map extents which bridge the date line.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import iris +from iris.tests import _shared_utils # Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import matplotlib.pyplot as plt from iris.plot import pcolormesh -@tests.skip_plot -@tests.skip_data -class TestExtent(tests.IrisTest): +@_shared_utils.skip_plot +@_shared_utils.skip_data +class TestExtent: def test_dateline(self): - dpath = tests.get_data_path(["PP", "nzgust.pp"]) + dpath = _shared_utils.get_data_path(["PP", "nzgust.pp"]) cube = iris.load_cube(dpath) pcolormesh(cube) # Ensure that the limited area expected for NZ is set. # This is set in longitudes with the datum set to the # International Date Line. - self.assertTrue( - -10 < plt.gca().get_xlim()[0] < -5 and 5 < plt.gca().get_xlim()[1] < 10 - ) - - -if __name__ == "__main__": - tests.main() + assert -10 < plt.gca().get_xlim()[0] < -5 + assert 5 < plt.gca().get_xlim()[1] < 10 diff --git a/lib/iris/tests/integration/plot/test_plot_2d_coords.py b/lib/iris/tests/integration/plot/test_plot_2d_coords.py index 43cd051f46..f4e23fad7b 100644 --- a/lib/iris/tests/integration/plot/test_plot_2d_coords.py +++ b/lib/iris/tests/integration/plot/test_plot_2d_coords.py @@ -4,10 +4,6 @@ # See LICENSE in the root of the repository for full licensing details. """Test plots with two dimensional coordinates.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import cartopy.crs as ccrs import matplotlib.pyplot as plt import numpy as np @@ -16,22 +12,23 @@ from iris.analysis.cartography import unrotate_pole from iris.coords import AuxCoord from iris.cube import Cube +from iris.tests import _shared_utils # Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.quickplot as qplt -@tests.skip_data +@_shared_utils.skip_data def simple_cube_w_2d_coords(): - path = tests.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) + path = _shared_utils.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) cube = iris.load_cube(path) return cube -@tests.skip_plot -@tests.skip_data -class Test(tests.GraphicsTest): +@_shared_utils.skip_plot +@_shared_utils.skip_data +class Test(_shared_utils.GraphicsTest): def test_2d_coord_bounds_platecarree(self): # To avoid a problem with Cartopy smearing the data where the # longitude wraps, we set the central_longitude. @@ -56,8 +53,8 @@ def test_2d_coord_bounds_northpolarstereo(self): self.check_graphic() -@tests.skip_plot -class Test2dContour(tests.GraphicsTest): +@_shared_utils.skip_plot +class Test2dContour(_shared_utils.GraphicsTest): def test_2d_coords_contour(self): ny, nx = 4, 6 x1 = np.linspace(-20, 70, nx) @@ -77,7 +74,3 @@ def test_2d_coords_contour(self): ax.gridlines(draw_labels=True) ax.set_extent((0, 180, 0, 90)) self.check_graphic() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/plot/test_vector_plots.py b/lib/iris/tests/integration/plot/test_vector_plots.py index 5419dc182f..0f8ac11a2d 100644 --- a/lib/iris/tests/integration/plot/test_vector_plots.py +++ b/lib/iris/tests/integration/plot/test_vector_plots.py @@ -4,26 +4,24 @@ # See LICENSE in the root of the repository for full licensing details. """Test some key usages of :func:`iris.plot.quiver`.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import cartopy.crs as ccrs import numpy as np +import pytest from iris.coord_systems import Mercator from iris.coords import AuxCoord, DimCoord from iris.cube import Cube +from iris.tests import _shared_utils from iris.tests.stock import sample_2d_latlons # Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import matplotlib.pyplot as plt from iris.plot import barbs, quiver -@tests.skip_plot +@_shared_utils.skip_plot class MixinVectorPlotCases: """Test examples mixin, used by separate barb, quiver + streamplot classes. @@ -147,7 +145,7 @@ def test_fail_unsupported_coord_system(self): r"Can only plot .* lat-lon projection, .* " r"This .* translates as Cartopy \+proj=merc .*" ) - with self.assertRaisesRegex(ValueError, re_msg): + with pytest.raises(ValueError, match=re_msg): self.plot("2d_rotated", u_cube, v_cube, coords=("longitude", "latitude")) def test_circular_longitude(self): @@ -178,10 +176,7 @@ def test_circular_longitude(self): self.plot("circular", u_cube, v_cube, coords=("longitude", "latitude")) -class TestBarbs(MixinVectorPlotCases, tests.GraphicsTest): - def setUp(self): - super().setUp() - +class TestBarbs(MixinVectorPlotCases, _shared_utils.GraphicsTest): @staticmethod def _nonlatlon_xyuv(): # Increase the range of wind speeds used in the barbs test to test more @@ -206,13 +201,6 @@ def plot_function_to_test(self): return barbs -class TestQuiver(MixinVectorPlotCases, tests.GraphicsTest): - def setUp(self): - super().setUp() - +class TestQuiver(MixinVectorPlotCases, _shared_utils.GraphicsTest): def plot_function_to_test(self): return quiver - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/results/unit/util/mask_cube/TestCubeMask/mask_cube_2d_create_new_dim.cml b/lib/iris/tests/results/unit/util/mask_cube/CubeMask/mask_cube_2d_create_new_dim.cml similarity index 100% rename from lib/iris/tests/results/unit/util/mask_cube/TestCubeMask/mask_cube_2d_create_new_dim.cml rename to lib/iris/tests/results/unit/util/mask_cube/CubeMask/mask_cube_2d_create_new_dim.cml diff --git a/lib/iris/tests/test_aggregate_by.py b/lib/iris/tests/test_aggregate_by.py index 60a9018c09..b0a90ccdaf 100644 --- a/lib/iris/tests/test_aggregate_by.py +++ b/lib/iris/tests/test_aggregate_by.py @@ -3,23 +3,22 @@ # 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. -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import unittest - import numpy as np import numpy.ma as ma +import pytest import iris import iris.analysis import iris.coord_systems import iris.coords +from iris.tests import _shared_utils -class TestAggregateBy(tests.IrisTest): - def setUp(self): +class TestAggregateBy: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + # # common # @@ -366,47 +365,67 @@ def setUp(self): def test_single(self): # mean group-by with single coordinate name. aggregateby_cube = self.cube_single.aggregated_by("height", iris.analysis.MEAN) - self.assertCML(aggregateby_cube, ("analysis", "aggregated_by", "single.cml")) + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "single.cml") + ) # mean group-by with single coordinate. aggregateby_cube = self.cube_single.aggregated_by( self.coord_z_single, iris.analysis.MEAN ) - self.assertCML(aggregateby_cube, ("analysis", "aggregated_by", "single.cml")) + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "single.cml") + ) - np.testing.assert_almost_equal(aggregateby_cube.data, self.single_expected) + _shared_utils.assert_array_almost_equal( + aggregateby_cube.data, self.single_expected + ) # rms group-by with single coordinate name. aggregateby_cube = self.cube_single.aggregated_by("height", iris.analysis.RMS) - self.assertCML( - aggregateby_cube, ("analysis", "aggregated_by", "single_rms.cml") + _shared_utils.assert_CML( + self.request, + aggregateby_cube, + ("analysis", "aggregated_by", "single_rms.cml"), ) # rms group-by with single coordinate. aggregateby_cube = self.cube_single.aggregated_by( self.coord_z_single, iris.analysis.RMS ) - self.assertCML( - aggregateby_cube, ("analysis", "aggregated_by", "single_rms.cml") + _shared_utils.assert_CML( + self.request, + aggregateby_cube, + ("analysis", "aggregated_by", "single_rms.cml"), ) - np.testing.assert_almost_equal(aggregateby_cube.data, self.single_rms_expected) + _shared_utils.assert_array_almost_equal( + aggregateby_cube.data, self.single_rms_expected + ) def test_str_aggregation_single_weights_none(self): # mean group-by with single coordinate name. aggregateby_cube = self.cube_single.aggregated_by( "height", iris.analysis.MEAN, weights=None ) - self.assertCML(aggregateby_cube, ("analysis", "aggregated_by", "single.cml")) - np.testing.assert_almost_equal(aggregateby_cube.data, self.single_expected) + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "single.cml") + ) + _shared_utils.assert_array_almost_equal( + aggregateby_cube.data, self.single_expected + ) def test_coord_aggregation_single_weights_none(self): # mean group-by with single coordinate. aggregateby_cube = self.cube_single.aggregated_by( self.coord_z_single, iris.analysis.MEAN, weights=None ) - self.assertCML(aggregateby_cube, ("analysis", "aggregated_by", "single.cml")) - np.testing.assert_almost_equal(aggregateby_cube.data, self.single_expected) + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "single.cml") + ) + _shared_utils.assert_array_almost_equal( + aggregateby_cube.data, self.single_expected + ) def test_weighted_single(self): # weighted mean group-by with single coordinate name. @@ -416,7 +435,8 @@ def test_weighted_single(self): weights=self.weights_single, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_single.cml"), ) @@ -427,11 +447,12 @@ def test_weighted_single(self): iris.analysis.MEAN, weights=self.weights_single, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_single.cml"), ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, self.weighted_single_expected, ) @@ -443,7 +464,8 @@ def test_single_shared(self): # group-by with single coordinate name on shared axis. aggregateby_cube = self.cube_single.aggregated_by("height", iris.analysis.MEAN) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "single_shared.cml"), ) @@ -452,12 +474,15 @@ def test_single_shared(self): aggregateby_cube = self.cube_single.aggregated_by( self.coord_z_single, iris.analysis.MEAN ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "single_shared.cml"), ) - np.testing.assert_almost_equal(aggregateby_cube.data, self.single_expected) + _shared_utils.assert_array_almost_equal( + aggregateby_cube.data, self.single_expected + ) def test_weighted_single_shared(self): z2_points = np.arange(36, dtype=np.int32) @@ -470,7 +495,8 @@ def test_weighted_single_shared(self): iris.analysis.MEAN, weights=self.weights_single, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_single_shared.cml"), ) @@ -481,11 +507,12 @@ def test_weighted_single_shared(self): iris.analysis.MEAN, weights=self.weights_single, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_single_shared.cml"), ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, self.weighted_single_expected ) @@ -498,7 +525,8 @@ def test_single_shared_circular(self): # group-by with single coordinate name on shared axis. aggregateby_cube = self.cube_single.aggregated_by("height", iris.analysis.MEAN) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "single_shared_circular.cml"), ) @@ -506,11 +534,14 @@ def test_single_shared_circular(self): # group-by with single coordinate on shared axis. coord = self.cube_single.coords("height") aggregateby_cube = self.cube_single.aggregated_by(coord, iris.analysis.MEAN) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "single_shared_circular.cml"), ) - np.testing.assert_almost_equal(aggregateby_cube.data, self.single_expected) + _shared_utils.assert_array_almost_equal( + aggregateby_cube.data, self.single_expected + ) def test_weighted_single_shared_circular(self): points = np.arange(36) * 10.0 @@ -525,7 +556,8 @@ def test_weighted_single_shared_circular(self): iris.analysis.MEAN, weights=self.weights_single, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ( "analysis", @@ -541,7 +573,8 @@ def test_weighted_single_shared_circular(self): iris.analysis.MEAN, weights=self.weights_single, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ( "analysis", @@ -549,7 +582,7 @@ def test_weighted_single_shared_circular(self): "weighted_single_shared_circular.cml", ), ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, self.weighted_single_expected, ) @@ -559,27 +592,37 @@ def test_multi(self): aggregateby_cube = self.cube_multi.aggregated_by( ["height", "level"], iris.analysis.MEAN ) - self.assertCML(aggregateby_cube, ("analysis", "aggregated_by", "multi.cml")) + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "multi.cml") + ) # group-by with multiple coordinate names (different order). aggregateby_cube = self.cube_multi.aggregated_by( ["level", "height"], iris.analysis.MEAN ) - self.assertCML(aggregateby_cube, ("analysis", "aggregated_by", "multi.cml")) + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "multi.cml") + ) # group-by with multiple coordinates. aggregateby_cube = self.cube_multi.aggregated_by( [self.coord_z1_multi, self.coord_z2_multi], iris.analysis.MEAN ) - self.assertCML(aggregateby_cube, ("analysis", "aggregated_by", "multi.cml")) + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "multi.cml") + ) # group-by with multiple coordinates (different order). aggregateby_cube = self.cube_multi.aggregated_by( [self.coord_z2_multi, self.coord_z1_multi], iris.analysis.MEAN ) - self.assertCML(aggregateby_cube, ("analysis", "aggregated_by", "multi.cml")) + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "multi.cml") + ) - np.testing.assert_almost_equal(aggregateby_cube.data, self.multi_expected) + _shared_utils.assert_array_almost_equal( + aggregateby_cube.data, self.multi_expected + ) def test_weighted_multi(self): # weighted group-by with multiple coordinate names. @@ -588,7 +631,8 @@ def test_weighted_multi(self): iris.analysis.MEAN, weights=self.weights_multi, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_multi.cml"), ) @@ -599,7 +643,8 @@ def test_weighted_multi(self): iris.analysis.MEAN, weights=self.weights_multi, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_multi.cml"), ) @@ -610,7 +655,8 @@ def test_weighted_multi(self): iris.analysis.MEAN, weights=self.weights_multi, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_multi.cml"), ) @@ -621,11 +667,12 @@ def test_weighted_multi(self): iris.analysis.MEAN, weights=self.weights_multi, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_multi.cml"), ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, self.weighted_multi_expected, ) @@ -643,8 +690,10 @@ def test_multi_shared(self): aggregateby_cube = self.cube_multi.aggregated_by( ["height", "level"], iris.analysis.MEAN ) - self.assertCML( - aggregateby_cube, ("analysis", "aggregated_by", "multi_shared.cml") + _shared_utils.assert_CML( + self.request, + aggregateby_cube, + ("analysis", "aggregated_by", "multi_shared.cml"), ) # group-by with multiple coordinate names on shared axis (different @@ -652,27 +701,35 @@ def test_multi_shared(self): aggregateby_cube = self.cube_multi.aggregated_by( ["level", "height"], iris.analysis.MEAN ) - self.assertCML( - aggregateby_cube, ("analysis", "aggregated_by", "multi_shared.cml") + _shared_utils.assert_CML( + self.request, + aggregateby_cube, + ("analysis", "aggregated_by", "multi_shared.cml"), ) # group-by with multiple coordinates on shared axis. aggregateby_cube = self.cube_multi.aggregated_by( [self.coord_z1_multi, self.coord_z2_multi], iris.analysis.MEAN ) - self.assertCML( - aggregateby_cube, ("analysis", "aggregated_by", "multi_shared.cml") + _shared_utils.assert_CML( + self.request, + aggregateby_cube, + ("analysis", "aggregated_by", "multi_shared.cml"), ) # group-by with multiple coordinates on shared axis (different order). aggregateby_cube = self.cube_multi.aggregated_by( [self.coord_z2_multi, self.coord_z1_multi], iris.analysis.MEAN ) - self.assertCML( - aggregateby_cube, ("analysis", "aggregated_by", "multi_shared.cml") + _shared_utils.assert_CML( + self.request, + aggregateby_cube, + ("analysis", "aggregated_by", "multi_shared.cml"), ) - np.testing.assert_almost_equal(aggregateby_cube.data, self.multi_expected) + _shared_utils.assert_array_almost_equal( + aggregateby_cube.data, self.multi_expected + ) def test_weighted_multi_shared(self): z3_points = np.arange(20, dtype=np.int32) @@ -689,7 +746,8 @@ def test_weighted_multi_shared(self): iris.analysis.MEAN, weights=self.weights_multi, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_multi_shared.cml"), ) @@ -701,7 +759,8 @@ def test_weighted_multi_shared(self): iris.analysis.MEAN, weights=self.weights_multi, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_multi_shared.cml"), ) @@ -712,7 +771,8 @@ def test_weighted_multi_shared(self): iris.analysis.MEAN, weights=self.weights_multi, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_multi_shared.cml"), ) @@ -724,11 +784,12 @@ def test_weighted_multi_shared(self): iris.analysis.MEAN, weights=self.weights_multi, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_multi_shared.cml"), ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, self.weighted_multi_expected, ) @@ -738,18 +799,19 @@ def test_easy(self): # Easy mean aggregate test by each coordinate. # aggregateby_cube = self.cube_easy.aggregated_by("longitude", iris.analysis.MEAN) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array([[8.0, 15.0], [10.0, 17.0], [15.0, 8.0]], dtype=np.float32), ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "easy.cml"), ) aggregateby_cube = self.cube_easy.aggregated_by("latitude", iris.analysis.MEAN) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array( [[7.0, 11.0, 13.0, 19.0], [18.0, 12.0, 10.0, 6.0]], @@ -761,13 +823,13 @@ def test_easy(self): # Easy max aggregate test by each coordinate. # aggregateby_cube = self.cube_easy.aggregated_by("longitude", iris.analysis.MAX) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array([[10.0, 18.0], [12.0, 20.0], [18.0, 10.0]], dtype=np.float32), ) aggregateby_cube = self.cube_easy.aggregated_by("latitude", iris.analysis.MAX) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array( [[8.0, 12.0, 14.0, 20.0], [18.0, 12.0, 10.0, 6.0]], @@ -779,13 +841,13 @@ def test_easy(self): # Easy sum aggregate test by each coordinate. # aggregateby_cube = self.cube_easy.aggregated_by("longitude", iris.analysis.SUM) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array([[16.0, 30.0], [20.0, 34.0], [30.0, 16.0]], dtype=np.float32), ) aggregateby_cube = self.cube_easy.aggregated_by("latitude", iris.analysis.SUM) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array( [[14.0, 22.0, 26.0, 38.0], [18.0, 12.0, 10.0, 6.0]], @@ -799,7 +861,7 @@ def test_easy(self): aggregateby_cube = self.cube_easy.aggregated_by( "longitude", iris.analysis.PERCENTILE, percent=25 ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array([[7.0, 13.5], [9.0, 15.5], [13.5, 7.0]], dtype=np.float32), ) @@ -807,7 +869,7 @@ def test_easy(self): aggregateby_cube = self.cube_easy.aggregated_by( "latitude", iris.analysis.PERCENTILE, percent=25 ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array( [[6.5, 10.5, 12.5, 18.5], [18.0, 12.0, 10.0, 6.0]], @@ -824,7 +886,7 @@ def test_easy(self): list(np.sqrt([104.0, 298.0])), list(np.sqrt([234.0, 68.0])), ] - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array(row, dtype=np.float32) ) @@ -833,7 +895,7 @@ def test_easy(self): list(np.sqrt([50.0, 122.0, 170.0, 362.0])), [18.0, 12.0, 10.0, 6.0], ] - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array(row, dtype=np.float32) ) @@ -852,11 +914,12 @@ def test_weighted_easy(self): "longitude", iris.analysis.MEAN, weights=lon_weights ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array([[3.0, 8.0], [0.2, 4.0]], dtype=np.float32), ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_easy.cml"), ) @@ -866,7 +929,7 @@ def test_weighted_easy(self): iris.analysis.MEAN, weights=lat_weights, ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array( [[3.0, 5.0, 7.0, 9.0], [0.0, 2.0, 4.0, 6.0]], @@ -880,7 +943,7 @@ def test_weighted_easy(self): aggregateby_cube = self.cube_easy_weighted.aggregated_by( "longitude", iris.analysis.SUM, weights=lon_weights ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array([[3.0, 16.0], [2.0, 8.0]], dtype=np.float32), ) @@ -890,7 +953,7 @@ def test_weighted_easy(self): iris.analysis.SUM, weights=lat_weights, ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array( [[6.0, 10.0, 14.0, 18.0], [0.0, 4.0, 8.0, 12.0]], @@ -909,7 +972,7 @@ def test_weighted_easy(self): percent=50, weights=lon_weights, ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array([[3.0, 8.0], [0.2, 4.0]], dtype=np.float32), ) @@ -920,7 +983,7 @@ def test_weighted_easy(self): aggregateby_cube = self.cube_easy_weighted.aggregated_by( "longitude", iris.analysis.RMS, weights=lon_weights ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array([[3.0, np.sqrt(65.0)], [np.sqrt(0.4), 4.0]], dtype=np.float32), ) @@ -928,7 +991,7 @@ def test_weighted_easy(self): aggregateby_cube = self.cube_easy_weighted.aggregated_by( "latitude", iris.analysis.RMS, weights=lat_weights ) - np.testing.assert_almost_equal( + _shared_utils.assert_array_almost_equal( aggregateby_cube.data, np.array( [[3.0, 5.0, 7.0, 9.0], [0.0, 2.0, 4.0, 6.0]], @@ -986,11 +1049,14 @@ def test_single_missing(self): "height", iris.analysis.MEAN ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "single_missing.cml"), ) - self.assertMaskedArrayAlmostEqual(aggregateby_cube.data, single_expected) + _shared_utils.assert_masked_array_almost_equal( + aggregateby_cube.data, single_expected + ) def test_weighted_single_missing(self): # weighted aggregation correctly handles masked data @@ -1044,11 +1110,12 @@ def test_weighted_single_missing(self): weights=self.weights_single, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_single_missing.cml"), ) - self.assertMaskedArrayAlmostEqual( + _shared_utils.assert_masked_array_almost_equal( aggregateby_cube.data, weighted_single_expected, ) @@ -1108,11 +1175,14 @@ def test_multi_missing(self): ["height", "level"], iris.analysis.MEAN ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "multi_missing.cml"), ) - self.assertMaskedArrayAlmostEqual(aggregateby_cube.data, multi_expected) + _shared_utils.assert_masked_array_almost_equal( + aggregateby_cube.data, multi_expected + ) def test_weighted_multi_missing(self): # weighted aggregation correctly handles masked data @@ -1170,11 +1240,12 @@ def test_weighted_multi_missing(self): iris.analysis.MEAN, weights=self.weights_multi, ) - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_multi_missing.cml"), ) - self.assertMaskedArrayAlmostEqual( + _shared_utils.assert_masked_array_almost_equal( aggregateby_cube.data, weighted_multi_expected, ) @@ -1186,10 +1257,11 @@ def test_returned_true_single(self): returned=True, weights=self.weights_single, ) - self.assertTrue(isinstance(aggregateby_output, tuple)) + assert isinstance(aggregateby_output, tuple) aggregateby_cube = aggregateby_output[0] - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_single.cml"), ) @@ -1207,7 +1279,7 @@ def test_returned_true_single(self): [[8.0, 8.0, 8.0], [8.0, 8.0, 8.0], [8.0, 8.0, 8.0]], ] ) - np.testing.assert_almost_equal(aggregateby_weights, expected_weights) + _shared_utils.assert_array_almost_equal(aggregateby_weights, expected_weights) def test_returned_true_multi(self): aggregateby_output = self.cube_multi.aggregated_by( @@ -1216,10 +1288,11 @@ def test_returned_true_multi(self): returned=True, weights=self.weights_multi, ) - self.assertTrue(isinstance(aggregateby_output, tuple)) + assert isinstance(aggregateby_output, tuple) aggregateby_cube = aggregateby_output[0] - self.assertCML( + _shared_utils.assert_CML( + self.request, aggregateby_cube, ("analysis", "aggregated_by", "weighted_multi.cml"), ) @@ -1238,10 +1311,10 @@ def test_returned_true_multi(self): [[2.0, 2.0, 2.0], [2.0, 2.0, 2.0], [2.0, 2.0, 2.0]], ] ) - np.testing.assert_almost_equal(aggregateby_weights, expected_weights) + _shared_utils.assert_array_almost_equal(aggregateby_weights, expected_weights) def test_returned_fails_with_non_weighted_aggregator(self): - self.assertRaises( + pytest.raises( TypeError, self.cube_single.aggregated_by, "height", @@ -1250,7 +1323,7 @@ def test_returned_fails_with_non_weighted_aggregator(self): ) def test_weights_fail_with_non_weighted_aggregator(self): - self.assertRaises( + pytest.raises( TypeError, self.cube_single.aggregated_by, "height", @@ -1267,9 +1340,9 @@ def test_weights_fail_with_non_weighted_aggregator(self): class TestAggregateByWeightedByCube(TestAggregateBy): - def setUp(self): - super().setUp() - + @pytest.fixture(autouse=True) + def _setup_subclass(self, _setup): + # Requests _setup to ensure this fixture runs AFTER _setup. self.weights_single = self.cube_single[:, 0, 0].copy(self.weights_single) self.weights_single.units = "m2" self.weights_multi = self.cube_multi[:, 0, 0].copy(self.weights_multi) @@ -1281,7 +1354,7 @@ def test_str_aggregation_weighted_sum_single(self): iris.analysis.SUM, weights=self.weights_single, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" def test_coord_aggregation_weighted_sum_single(self): aggregateby_cube = self.cube_single.aggregated_by( @@ -1289,7 +1362,7 @@ def test_coord_aggregation_weighted_sum_single(self): iris.analysis.SUM, weights=self.weights_single, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" def test_str_aggregation_weighted_sum_multi(self): aggregateby_cube = self.cube_multi.aggregated_by( @@ -1297,7 +1370,7 @@ def test_str_aggregation_weighted_sum_multi(self): iris.analysis.SUM, weights=self.weights_multi, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" def test_str_aggregation_rev_order_weighted_sum_multi(self): aggregateby_cube = self.cube_multi.aggregated_by( @@ -1305,7 +1378,7 @@ def test_str_aggregation_rev_order_weighted_sum_multi(self): iris.analysis.SUM, weights=self.weights_multi, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" def test_coord_aggregation_weighted_sum_multi(self): aggregateby_cube = self.cube_multi.aggregated_by( @@ -1313,7 +1386,7 @@ def test_coord_aggregation_weighted_sum_multi(self): iris.analysis.SUM, weights=self.weights_multi, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" def test_coord_aggregation_rev_order_weighted_sum_multi(self): aggregateby_cube = self.cube_multi.aggregated_by( @@ -1321,11 +1394,12 @@ def test_coord_aggregation_rev_order_weighted_sum_multi(self): iris.analysis.SUM, weights=self.weights_multi, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" -class TestAggregateByWeightedByObj(tests.IrisTest): - def setUp(self): +class TestAggregateByWeightedByObj: + @pytest.fixture(autouse=True) + def _setup(self): self.dim_coord = iris.coords.DimCoord( [0, 1, 2], standard_name="latitude", units="degrees" ) @@ -1352,58 +1426,54 @@ def test_weighting_with_str_dim_coord(self): res_cube = self.cube.aggregated_by( "auxcoord", iris.analysis.SUM, weights="latitude" ) - np.testing.assert_array_equal(res_cube.data, [0, 8]) - self.assertEqual(res_cube.units, "K degrees") + _shared_utils.assert_array_equal(res_cube.data, [0, 8]) + assert res_cube.units == "K degrees" def test_weighting_with_str_aux_coord(self): res_cube = self.cube.aggregated_by( "auxcoord", iris.analysis.SUM, weights="auxcoord" ) - np.testing.assert_array_equal(res_cube.data, [0, 5]) - self.assertEqual(res_cube.units, "K kg") + _shared_utils.assert_array_equal(res_cube.data, [0, 5]) + assert res_cube.units == "K kg" def test_weighting_with_str_cell_measure(self): res_cube = self.cube.aggregated_by( "auxcoord", iris.analysis.SUM, weights="cell_area" ) - np.testing.assert_array_equal(res_cube.data, [0, 0]) - self.assertEqual(res_cube.units, "K m2") + _shared_utils.assert_array_equal(res_cube.data, [0, 0]) + assert res_cube.units == "K m2" def test_weighting_with_str_ancillary_variable(self): res_cube = self.cube.aggregated_by( "auxcoord", iris.analysis.SUM, weights="ancvar" ) - np.testing.assert_array_equal(res_cube.data, [1, 5]) - self.assertEqual(res_cube.units, "K kg") + _shared_utils.assert_array_equal(res_cube.data, [1, 5]) + assert res_cube.units == "K kg" def test_weighting_with_dim_coord(self): res_cube = self.cube.aggregated_by( "auxcoord", iris.analysis.SUM, weights=self.dim_coord ) - np.testing.assert_array_equal(res_cube.data, [0, 8]) - self.assertEqual(res_cube.units, "K degrees") + _shared_utils.assert_array_equal(res_cube.data, [0, 8]) + assert res_cube.units == "K degrees" def test_weighting_with_aux_coord(self): res_cube = self.cube.aggregated_by( "auxcoord", iris.analysis.SUM, weights=self.aux_coord ) - np.testing.assert_array_equal(res_cube.data, [0, 5]) - self.assertEqual(res_cube.units, "K kg") + _shared_utils.assert_array_equal(res_cube.data, [0, 5]) + assert res_cube.units == "K kg" def test_weighting_with_cell_measure(self): res_cube = self.cube.aggregated_by( "auxcoord", iris.analysis.SUM, weights=self.cell_measure ) - np.testing.assert_array_equal(res_cube.data, [0, 0]) - self.assertEqual(res_cube.units, "K m2") + _shared_utils.assert_array_equal(res_cube.data, [0, 0]) + assert res_cube.units == "K m2" def test_weighting_with_ancillary_variable(self): res_cube = self.cube.aggregated_by( "auxcoord", iris.analysis.SUM, weights=self.ancillary_variable ) - np.testing.assert_array_equal(res_cube.data, [1, 5]) - self.assertEqual(res_cube.units, "K kg") - - -if __name__ == "__main__": - unittest.main() + _shared_utils.assert_array_equal(res_cube.data, [1, 5]) + assert res_cube.units == "K kg" diff --git a/lib/iris/tests/test_analysis.py b/lib/iris/tests/test_analysis.py index a8446034be..9e0bf76d34 100644 --- a/lib/iris/tests/test_analysis.py +++ b/lib/iris/tests/test_analysis.py @@ -3,10 +3,6 @@ # 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. - -# import iris tests first so that some things can be initialised before importing anything else -import iris.tests as tests # isort:skip - import cf_units import dask.array as da import numpy as np @@ -20,12 +16,17 @@ import iris.coord_systems import iris.coords import iris.cube +from iris.tests import _shared_utils, stock import iris.tests.stock import iris.util -class TestAnalysisCubeCoordComparison(tests.IrisTest): - def assertComparisonDict(self, comparison_dict, reference_filename): +class TestAnalysisCubeCoordComparison: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + + def assert_comparison_dict(self, comparison_dict, reference_filename): string = "" for key in sorted(comparison_dict): coord_groups = comparison_dict[key] @@ -36,7 +37,7 @@ def assertComparisonDict(self, comparison_dict, reference_filename): ] string += str(sorted(names)) string += "\n" - self.assertString(string, reference_filename) + _shared_utils.assert_string(self.request, string, reference_filename) def test_coord_comparison(self): cube1 = iris.cube.Cube(np.zeros((41, 41))) @@ -109,67 +110,71 @@ def test_coord_comparison(self): coord_comparison = iris.analysis._dimensional_metadata_comparison - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube1, cube1), ("analysis", "coord_comparison", "cube1_cube1.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube1, cube2), ("analysis", "coord_comparison", "cube1_cube2.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube1, cube3), ("analysis", "coord_comparison", "cube1_cube3.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube1, cube4), ("analysis", "coord_comparison", "cube1_cube4.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube1, cube5), ("analysis", "coord_comparison", "cube1_cube5.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube2, cube3), ("analysis", "coord_comparison", "cube2_cube3.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube2, cube4), ("analysis", "coord_comparison", "cube2_cube4.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube2, cube5), ("analysis", "coord_comparison", "cube2_cube5.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube3, cube4), ("analysis", "coord_comparison", "cube3_cube4.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube3, cube5), ("analysis", "coord_comparison", "cube3_cube5.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube4, cube5), ("analysis", "coord_comparison", "cube4_cube5.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube1, cube1, cube1), ("analysis", "coord_comparison", "cube1_cube1_cube1.txt"), ) - self.assertComparisonDict( + self.assert_comparison_dict( coord_comparison(cube1, cube2, cube1), ("analysis", "coord_comparison", "cube1_cube2_cube1.txt"), ) # get a coord comparison result and check that we are getting back what was expected coord_group = coord_comparison(cube1, cube2)["grouped_coords"][0] - self.assertIsInstance(coord_group, iris.analysis._CoordGroup) - self.assertIsInstance(list(coord_group)[0], iris.coords.Coord) + assert isinstance(coord_group, iris.analysis._CoordGroup) + assert isinstance(list(coord_group)[0], iris.coords.Coord) -class TestAnalysisWeights(tests.IrisTest): +class TestAnalysisWeights: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + def test_weighted_mean_little(self): data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32) weights = np.array([[9, 8, 7], [6, 5, 4], [3, 2, 1]], dtype=np.float32) @@ -196,7 +201,9 @@ def test_weighted_mean_little(self): ), 1, ) - self.assertCML(cube, ("analysis", "weighted_mean_source.cml")) + _shared_utils.assert_CML( + self.request, cube, ("analysis", "weighted_mean_source.cml") + ) a = cube.collapsed("lat", iris.analysis.MEAN, weights=weights) # np.ma.average doesn't apply type promotion rules in some versions, @@ -206,15 +213,19 @@ def test_weighted_mean_little(self): if a.dtype > np.float32: cast_data = a.data.astype(np.float32) a.data = cast_data - self.assertCMLApproxData(a, ("analysis", "weighted_mean_lat.cml")) + _shared_utils.assert_CML_approx_data( + self.request, a, ("analysis", "weighted_mean_lat.cml") + ) b = cube.collapsed(lon_coord, iris.analysis.MEAN, weights=weights) if b.dtype > np.float32: cast_data = b.data.astype(np.float32) b.data = cast_data b.data = np.asarray(b.data) - self.assertCMLApproxData(b, ("analysis", "weighted_mean_lon.cml")) - self.assertEqual(b.coord("dummy").shape, (1,)) + _shared_utils.assert_CML_approx_data( + self.request, b, ("analysis", "weighted_mean_lon.cml") + ) + assert b.coord("dummy").shape == (1,) # test collapsing multiple coordinates (and the fact that one of the coordinates isn't the same coordinate instance as on the cube) c = cube.collapsed( @@ -223,22 +234,26 @@ def test_weighted_mean_little(self): if c.dtype > np.float32: cast_data = c.data.astype(np.float32) c.data = cast_data - self.assertCMLApproxData(c, ("analysis", "weighted_mean_latlon.cml")) - self.assertEqual(c.coord("dummy").shape, (1,)) + _shared_utils.assert_CML_approx_data( + self.request, c, ("analysis", "weighted_mean_latlon.cml") + ) + assert c.coord("dummy").shape == (1,) # Check new coord bounds - made from points - self.assertArrayEqual(c.coord("lat").bounds, [[1, 3]]) + _shared_utils.assert_array_equal(c.coord("lat").bounds, [[1, 3]]) # Check new coord bounds - made from bounds cube.coord("lat").bounds = [[0.5, 1.5], [1.5, 2.5], [2.5, 3.5]] c = cube.collapsed(["lat", "lon"], iris.analysis.MEAN, weights=weights) - self.assertArrayEqual(c.coord("lat").bounds, [[0.5, 3.5]]) + _shared_utils.assert_array_equal(c.coord("lat").bounds, [[0.5, 3.5]]) cube.coord("lat").bounds = None # Check there was no residual change - self.assertCML(cube, ("analysis", "weighted_mean_source.cml")) + _shared_utils.assert_CML( + self.request, cube, ("analysis", "weighted_mean_source.cml") + ) - @tests.skip_data + @_shared_utils.skip_data def test_weighted_mean(self): # compare with pp_area_avg - which collapses both lat and lon # @@ -247,8 +262,10 @@ def test_weighted_mean(self): # print, pp_area_avg(pp, /box) #287.927 # ;gives an answer of 287.927 # - e = iris.tests.stock.simple_pp() - self.assertCML(e, ("analysis", "weighted_mean_original.cml")) + e = stock.simple_pp() + _shared_utils.assert_CML( + self.request, e, ("analysis", "weighted_mean_original.cml") + ) e.coord("latitude").guess_bounds() e.coord("longitude").guess_bounds() area_weights = iris.analysis.cartography.area_weights(e) @@ -259,9 +276,9 @@ def test_weighted_mean(self): ) g = f.collapsed("longitude", iris.analysis.MEAN, weights=collapsed_area_weights) # check it's a 0d, scalar cube - self.assertEqual(g.shape, ()) + assert g.shape == () # check the value - pp_area_avg's result of 287.927 differs by factor of 1.00002959 - np.testing.assert_approx_equal(g.data, 287.935, significant=5) + _shared_utils.assert_array_almost_equal(g.data, 287.935, decimal=2) # check we get summed weights even if we don't give any h, summed_weights = e.collapsed("latitude", iris.analysis.MEAN, returned=True) @@ -270,10 +287,12 @@ def test_weighted_mean(self): # Check there was no residual change e.coord("latitude").bounds = None e.coord("longitude").bounds = None - self.assertCML(e, ("analysis", "weighted_mean_original.cml")) + _shared_utils.assert_CML( + self.request, e, ("analysis", "weighted_mean_original.cml") + ) # Test collapsing of missing coord - self.assertRaises( + pytest.raises( iris.exceptions.CoordinateNotFoundError, e.collapsed, "platitude", @@ -281,7 +300,7 @@ def test_weighted_mean(self): ) # Test collapsing of non data coord - self.assertRaises( + pytest.raises( iris.exceptions.CoordinateCollapseError, e.collapsed, "pressure", @@ -289,13 +308,15 @@ def test_weighted_mean(self): ) -@tests.skip_data -class TestAnalysisBasic(tests.IrisTest): - def setUp(self): - file = tests.get_data_path(("PP", "aPProt1", "rotatedMHtimecube.pp")) +@_shared_utils.skip_data +class TestAnalysisBasic: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + file = _shared_utils.get_data_path(("PP", "aPProt1", "rotatedMHtimecube.pp")) cubes = iris.load(file) self.cube = cubes[0] - self.assertCML(self.cube, ("analysis", "original.cml")) + _shared_utils.assert_CML(self.request, self.cube, ("analysis", "original.cml")) def _common( self, @@ -307,15 +328,16 @@ def _common( ): self.cube.data = self.cube.data.astype(np.float64) - self.assertCML(self.cube, ("analysis", original_name)) + _shared_utils.assert_CML(self.request, self.cube, ("analysis", original_name)) a = self.cube.collapsed("grid_latitude", aggregate) - self.assertCMLApproxData( - a, ("analysis", "%s_latitude.cml" % name), *args, **kwargs + _shared_utils.assert_CML_approx_data( + self.request, a, ("analysis", "%s_latitude.cml" % name), *args, **kwargs ) b = a.collapsed("grid_longitude", aggregate) - self.assertCMLApproxData( + _shared_utils.assert_CML_approx_data( + self.request, b, ("analysis", "%s_latitude_longitude.cml" % name), *args, @@ -323,7 +345,8 @@ def _common( ) c = self.cube.collapsed(["grid_latitude", "grid_longitude"], aggregate) - self.assertCMLApproxData( + _shared_utils.assert_CML_approx_data( + self.request, c, ("analysis", "%s_latitude_longitude_1call.cml" % name), *args, @@ -331,7 +354,7 @@ def _common( ) # Check there was no residual change - self.assertCML(self.cube, ("analysis", original_name)) + _shared_utils.assert_CML(self.request, self.cube, ("analysis", original_name)) def test_mean(self): self._common("mean", iris.analysis.MEAN, rtol=1e-05) @@ -369,12 +392,13 @@ def test_rms(self): self._common("rms", iris.analysis.RMS) def test_duplicate_coords(self): - self.assertRaises(ValueError, tests.stock.track_1d, duplicate_x=True) + pytest.raises(ValueError, stock.track_1d, duplicate_x=True) -class TestMissingData(tests.IrisTest): - def setUp(self): - self.cube_with_nan = tests.stock.simple_2d() +class TestMissingData: + @pytest.fixture(autouse=True) + def _setup(self): + self.cube_with_nan = stock.simple_2d() data = self.cube_with_nan.data.astype(np.float32) self.cube_with_nan.data = data.copy() @@ -382,36 +406,37 @@ def setUp(self): self.cube_with_nan.data[2, 2] = np.nan self.cube_with_nan.data[2, 3] = np.nan - self.cube_with_mask = tests.stock.simple_2d() + self.cube_with_mask = stock.simple_2d() self.cube_with_mask.data = ma.array( self.cube_with_nan.data, mask=np.isnan(self.cube_with_nan.data) ) def test_max(self): cube = self.cube_with_nan.collapsed("foo", iris.analysis.MAX) - np.testing.assert_array_equal(cube.data, np.array([3, np.nan, np.nan])) + _shared_utils.assert_array_equal(cube.data, np.array([3, np.nan, np.nan])) cube = self.cube_with_mask.collapsed("foo", iris.analysis.MAX) - np.testing.assert_array_equal(cube.data, np.array([3, 7, 9])) + _shared_utils.assert_array_equal(cube.data, np.array([3, 7, 9])) def test_min(self): cube = self.cube_with_nan.collapsed("foo", iris.analysis.MIN) - np.testing.assert_array_equal(cube.data, np.array([0, np.nan, np.nan])) + _shared_utils.assert_array_equal(cube.data, np.array([0, np.nan, np.nan])) cube = self.cube_with_mask.collapsed("foo", iris.analysis.MIN) - np.testing.assert_array_equal(cube.data, np.array([0, 5, 8])) + _shared_utils.assert_array_equal(cube.data, np.array([0, 5, 8])) def test_sum(self): cube = self.cube_with_nan.collapsed("foo", iris.analysis.SUM) - np.testing.assert_array_equal(cube.data, np.array([6, np.nan, np.nan])) + _shared_utils.assert_array_equal(cube.data, np.array([6, np.nan, np.nan])) cube = self.cube_with_mask.collapsed("foo", iris.analysis.SUM) - np.testing.assert_array_equal(cube.data, np.array([6, 18, 17])) + _shared_utils.assert_array_equal(cube.data, np.array([6, 18, 17])) -class TestAuxCoordCollapse(tests.IrisTest): - def setUp(self): - self.cube_with_aux_coord = tests.stock.simple_4d_with_hybrid_height() +class TestAuxCoordCollapse: + @pytest.fixture(autouse=True) + def _setup(self): + self.cube_with_aux_coord = stock.simple_4d_with_hybrid_height() # Guess bounds to get the weights self.cube_with_aux_coord.coord("grid_latitude").guess_bounds() @@ -419,12 +444,12 @@ def setUp(self): def test_max(self): cube = self.cube_with_aux_coord.collapsed("grid_latitude", iris.analysis.MAX) - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( cube.coord("surface_altitude").points, np.array([112, 113, 114, 115, 116, 117]), ) - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( cube.coord("surface_altitude").bounds, np.array( [ @@ -441,29 +466,30 @@ def test_max(self): # Check collapsing over the whole coord still works cube = self.cube_with_aux_coord.collapsed("altitude", iris.analysis.MAX) - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( cube.coord("surface_altitude").points, np.array([114]) ) - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( cube.coord("surface_altitude").bounds, np.array([[100, 129]]) ) cube = self.cube_with_aux_coord.collapsed("grid_longitude", iris.analysis.MAX) - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( cube.coord("surface_altitude").points, np.array([102, 108, 114, 120, 126]), ) - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( cube.coord("surface_altitude").bounds, np.array([[100, 105], [106, 111], [112, 117], [118, 123], [124, 129]]), ) -class TestAggregator_mdtol_keyword(tests.IrisTest): - def setUp(self): +class TestAggregator_mdtol_keyword: + @pytest.fixture(autouse=True) + def _setup(self): data = ma.array( [[1, 2], [4, 5]], dtype=np.float32, @@ -483,7 +509,7 @@ def setUp(self): def test_single_coord_no_mdtol(self): collapsed = self.cube.collapsed(self.cube.coord("lat"), iris.analysis.MEAN) t = ma.array([2.5, 5.0], mask=[False, True]) - self.assertMaskedArrayEqual(collapsed.data, t) + _shared_utils.assert_masked_array_almost_equal(collapsed.data, t) def test_single_coord_mdtol(self): self.cube.data.mask = np.array([[False, True], [False, False]]) @@ -491,7 +517,7 @@ def test_single_coord_mdtol(self): self.cube.coord("lat"), iris.analysis.MEAN, mdtol=0.5 ) t = ma.array([2.5, 5], mask=[False, False]) - self.assertMaskedArrayEqual(collapsed.data, t) + _shared_utils.assert_masked_array_almost_equal(collapsed.data, t) def test_single_coord_mdtol_alt(self): self.cube.data.mask = np.array([[False, True], [False, False]]) @@ -499,7 +525,7 @@ def test_single_coord_mdtol_alt(self): self.cube.coord("lat"), iris.analysis.MEAN, mdtol=0.4 ) t = ma.array([2.5, 5], mask=[False, True]) - self.assertMaskedArrayEqual(collapsed.data, t) + _shared_utils.assert_masked_array_almost_equal(collapsed.data, t) def test_multi_coord_no_mdtol(self): collapsed = self.cube.collapsed( @@ -507,7 +533,7 @@ def test_multi_coord_no_mdtol(self): iris.analysis.MEAN, ) t = np.array(2.5) - self.assertArrayEqual(collapsed.data, t) + _shared_utils.assert_array_equal(collapsed.data, t) def test_multi_coord_mdtol(self): collapsed = self.cube.collapsed( @@ -516,10 +542,14 @@ def test_multi_coord_mdtol(self): mdtol=0.4, ) t = ma.array(2.5, mask=True) - self.assertMaskedArrayEqual(collapsed.data, t) + _shared_utils.assert_masked_array_almost_equal(collapsed.data, t) -class TestAggregators(tests.IrisTest): +class TestAggregators: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + def _check_collapsed_percentile( self, cube, @@ -537,30 +567,32 @@ def _check_collapsed_percentile( percent=percents, **kwargs, ) - np.testing.assert_array_almost_equal(result.data, expected_result) - self.assertEqual(type(result.data), cube_data_type) + _shared_utils.assert_array_almost_equal(result.data, expected_result) + assert type(result.data) is cube_data_type if CML_filename is not None: - self.assertCML(result, ("analysis", CML_filename), checksum=False) + _shared_utils.assert_CML( + self.request, result, ("analysis", CML_filename), checksum=False + ) def _check_percentile(self, data, axis, percents, expected_result, **kwargs): result = iris.analysis._percentile(data, axis, percents, **kwargs) - np.testing.assert_array_almost_equal(result, expected_result) - self.assertEqual(type(result), type(expected_result)) + _shared_utils.assert_array_almost_equal(result, expected_result) + assert type(result) is type(expected_result) def test_percentile_1d_25_percent(self): - cube = tests.stock.simple_1d() + cube = stock.simple_1d() self._check_collapsed_percentile( cube, 25, "foo", 2.5, CML_filename="first_quartile_foo_1d.cml" ) def test_percentile_1d_75_percent(self): - cube = tests.stock.simple_1d() + cube = stock.simple_1d() self._check_collapsed_percentile( cube, 75, "foo", 7.5, CML_filename="third_quartile_foo_1d.cml" ) def test_fast_percentile_1d_25_percent(self): - cube = tests.stock.simple_1d() + cube = stock.simple_1d() self._check_collapsed_percentile( cube, 25, @@ -571,7 +603,7 @@ def test_fast_percentile_1d_25_percent(self): ) def test_fast_percentile_1d_75_percent(self): - cube = tests.stock.simple_1d() + cube = stock.simple_1d() self._check_collapsed_percentile( cube, 75, @@ -582,7 +614,7 @@ def test_fast_percentile_1d_75_percent(self): ) def test_fast_percentile_1d_75_percent_masked_type_no_mask(self): - cube = tests.stock.simple_1d() + cube = stock.simple_1d() cube.data = ma.MaskedArray(cube.data) self._check_collapsed_percentile( cube, @@ -594,7 +626,7 @@ def test_fast_percentile_1d_75_percent_masked_type_no_mask(self): ) def test_percentile_2d_single_coord(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() self._check_collapsed_percentile( cube, 25, @@ -604,7 +636,7 @@ def test_percentile_2d_single_coord(self): ) def test_percentile_2d_two_coords(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() self._check_collapsed_percentile( cube, 25, @@ -614,7 +646,7 @@ def test_percentile_2d_two_coords(self): ) def test_fast_percentile_2d_single_coord(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() self._check_collapsed_percentile( cube, 25, @@ -625,7 +657,7 @@ def test_fast_percentile_2d_single_coord(self): ) def test_fast_percentile_2d_two_coords(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() self._check_collapsed_percentile( cube, 25, @@ -636,7 +668,7 @@ def test_fast_percentile_2d_two_coords(self): ) def test_fast_percentile_2d_single_coord_masked_type_no_mask(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() cube.data = ma.MaskedArray(cube.data) self._check_collapsed_percentile( cube, @@ -648,7 +680,7 @@ def test_fast_percentile_2d_single_coord_masked_type_no_mask(self): ) def test_fast_percentile_2d_two_coords_masked_type_no_mask(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() cube.data = ma.MaskedArray(cube.data) self._check_collapsed_percentile( cube, @@ -744,7 +776,7 @@ def test_fast_percentile_3d_axis_two_masked_type_no_mask(self): ) def test_percentile_3d_masked(self): - cube = tests.stock.simple_3d_mask() + cube = stock.simple_3d_mask() expected_result = [ [12.0, 13.0, 14.0, 15.0], [16.0, 17.0, 18.0, 19.0], @@ -760,10 +792,10 @@ def test_percentile_3d_masked(self): ) def test_fast_percentile_3d_masked_type_masked(self): - cube = tests.stock.simple_3d_mask() + cube = stock.simple_3d_mask() msg = "Cannot use fast np.percentile method with masked array." - with self.assertRaisesRegex(TypeError, msg): + with pytest.raises(TypeError, match=msg): cube.collapsed( "wibble", iris.analysis.PERCENTILE, @@ -772,7 +804,7 @@ def test_fast_percentile_3d_masked_type_masked(self): ) def test_percentile_3d_notmasked(self): - cube = tests.stock.simple_3d() + cube = stock.simple_3d() expected_result = [ [9.0, 10.0, 11.0, 12.0], [13.0, 14.0, 15.0, 16.0], @@ -788,7 +820,7 @@ def test_percentile_3d_notmasked(self): ) def test_fast_percentile_3d_notmasked(self): - cube = tests.stock.simple_3d() + cube = stock.simple_3d() expected_result = [ [9.0, 10.0, 11.0, 12.0], [13.0, 14.0, 15.0, 16.0], @@ -805,42 +837,50 @@ def test_fast_percentile_3d_notmasked(self): ) def test_proportion(self): - cube = tests.stock.simple_1d() + cube = stock.simple_1d() assert np.any(cube.data >= 5) gt5 = cube.collapsed( "foo", iris.analysis.PROPORTION, function=lambda val: val >= 5 ) - np.testing.assert_array_almost_equal(gt5.data, np.array([6 / 11.0])) - self.assertCML(gt5, ("analysis", "proportion_foo_1d.cml"), checksum=False) + _shared_utils.assert_array_almost_equal(gt5.data, np.array([6 / 11.0])) + _shared_utils.assert_CML( + self.request, gt5, ("analysis", "proportion_foo_1d.cml"), checksum=False + ) def test_proportion_2d(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() gt6 = cube.collapsed( "foo", iris.analysis.PROPORTION, function=lambda val: val >= 6 ) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( gt6.data, np.array([0, 0.5, 1], dtype=np.float32) ) - self.assertCML(gt6, ("analysis", "proportion_foo_2d.cml"), checksum=False) + _shared_utils.assert_CML( + self.request, gt6, ("analysis", "proportion_foo_2d.cml"), checksum=False + ) gt6 = cube.collapsed( "bar", iris.analysis.PROPORTION, function=lambda val: val >= 6 ) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( gt6.data, np.array([1 / 3, 1 / 3, 2 / 3, 2 / 3], dtype=np.float32) ) - self.assertCML(gt6, ("analysis", "proportion_bar_2d.cml"), checksum=False) + _shared_utils.assert_CML( + self.request, gt6, ("analysis", "proportion_bar_2d.cml"), checksum=False + ) gt6 = cube.collapsed( ("foo", "bar"), iris.analysis.PROPORTION, function=lambda val: val >= 6, ) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( gt6.data, np.array([0.5], dtype=np.float32) ) - self.assertCML(gt6, ("analysis", "proportion_foo_bar_2d.cml"), checksum=False) + _shared_utils.assert_CML( + self.request, gt6, ("analysis", "proportion_foo_bar_2d.cml"), checksum=False + ) # mask the data cube.data = ma.array(cube.data, mask=cube.data % 2) @@ -848,7 +888,7 @@ def test_proportion_2d(self): gt6_masked = cube.collapsed( "bar", iris.analysis.PROPORTION, function=lambda val: val >= 6 ) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( gt6_masked.data, ma.array( [1 / 3, None, 1 / 2, None], @@ -856,58 +896,71 @@ def test_proportion_2d(self): dtype=np.float32, ), ) - self.assertCML( + _shared_utils.assert_CML( + self.request, gt6_masked, ("analysis", "proportion_foo_2d_masked.cml"), checksum=False, ) def test_count(self): - cube = tests.stock.simple_1d() + cube = stock.simple_1d() gt5 = cube.collapsed("foo", iris.analysis.COUNT, function=lambda val: val >= 5) - np.testing.assert_array_almost_equal(gt5.data, np.array([6])) + _shared_utils.assert_array_almost_equal(gt5.data, np.array([6])) gt5.data = gt5.data.astype("i8") - self.assertCML(gt5, ("analysis", "count_foo_1d.cml"), checksum=False) + _shared_utils.assert_CML( + self.request, gt5, ("analysis", "count_foo_1d.cml"), checksum=False + ) def test_count_2d(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() gt6 = cube.collapsed("foo", iris.analysis.COUNT, function=lambda val: val >= 6) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( gt6.data, np.array([0, 2, 4], dtype=np.float32) ) gt6.data = gt6.data.astype("i8") - self.assertCML(gt6, ("analysis", "count_foo_2d.cml"), checksum=False) + _shared_utils.assert_CML( + self.request, gt6, ("analysis", "count_foo_2d.cml"), checksum=False + ) gt6 = cube.collapsed("bar", iris.analysis.COUNT, function=lambda val: val >= 6) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( gt6.data, np.array([1, 1, 2, 2], dtype=np.float32) ) gt6.data = gt6.data.astype("i8") - self.assertCML(gt6, ("analysis", "count_bar_2d.cml"), checksum=False) + _shared_utils.assert_CML( + self.request, gt6, ("analysis", "count_bar_2d.cml"), checksum=False + ) gt6 = cube.collapsed( ("foo", "bar"), iris.analysis.COUNT, function=lambda val: val >= 6 ) - np.testing.assert_array_almost_equal(gt6.data, np.array([6], dtype=np.float32)) + _shared_utils.assert_array_almost_equal( + gt6.data, np.array([6], dtype=np.float32) + ) gt6.data = gt6.data.astype("i8") - self.assertCML(gt6, ("analysis", "count_foo_bar_2d.cml"), checksum=False) + _shared_utils.assert_CML( + self.request, gt6, ("analysis", "count_foo_bar_2d.cml"), checksum=False + ) def test_max_run_1d(self): - cube = tests.stock.simple_1d() + cube = stock.simple_1d() # [ 0 1 2 3 4 5 6 7 8 9 10] result = cube.collapsed( "foo", iris.analysis.MAX_RUN, function=lambda val: np.isin(val, [0, 1, 4, 5, 6, 8, 9]), ) - self.assertArrayEqual(result.data, np.array(3)) - self.assertEqual(result.units, 1) - self.assertTupleEqual(result.cell_methods, ()) - self.assertCML(result, ("analysis", "max_run_foo_1d.cml"), checksum=False) + _shared_utils.assert_array_equal(result.data, np.array(3)) + assert result.units == 1 + assert result.cell_methods == () + _shared_utils.assert_CML( + self.request, result, ("analysis", "max_run_foo_1d.cml"), checksum=False + ) def test_max_run_lazy(self): - cube = tests.stock.simple_1d() + cube = stock.simple_1d() # [ 0 1 2 3 4 5 6 7 8 9 10] # Make data lazy cube.data = da.from_array(cube.data) @@ -916,16 +969,18 @@ def test_max_run_lazy(self): iris.analysis.MAX_RUN, function=lambda val: np.isin(val, [0, 1, 4, 5, 6, 8, 9]), ) - self.assertTrue(result.has_lazy_data()) + assert result.has_lazy_data() # Realise data _ = result.data - self.assertArrayEqual(result.data, np.array(3)) - self.assertEqual(result.units, 1) - self.assertTupleEqual(result.cell_methods, ()) - self.assertCML(result, ("analysis", "max_run_foo_1d.cml"), checksum=False) + _shared_utils.assert_array_equal(result.data, np.array(3)) + assert result.units == 1 + assert result.cell_methods == () + _shared_utils.assert_CML( + self.request, result, ("analysis", "max_run_foo_1d.cml"), checksum=False + ) def test_max_run_2d(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() # [[ 0 1 2 3] # [ 4 5 6 7] # [ 8 9 10 11]] @@ -934,22 +989,30 @@ def test_max_run_2d(self): iris.analysis.MAX_RUN, function=lambda val: np.isin(val, [0, 3, 4, 5, 7, 9, 11]), ) - self.assertArrayEqual(foo_result.data, np.array([1, 2, 1], dtype=np.float32)) - self.assertEqual(foo_result.units, 1) - self.assertTupleEqual(foo_result.cell_methods, ()) - self.assertCML(foo_result, ("analysis", "max_run_foo_2d.cml"), checksum=False) + _shared_utils.assert_array_equal( + foo_result.data, np.array([1, 2, 1], dtype=np.float32) + ) + assert foo_result.units == 1 + assert foo_result.cell_methods == () + _shared_utils.assert_CML( + self.request, foo_result, ("analysis", "max_run_foo_2d.cml"), checksum=False + ) bar_result = cube.collapsed( "bar", iris.analysis.MAX_RUN, function=lambda val: np.isin(val, [0, 3, 4, 5, 7, 9, 11]), ) - self.assertArrayEqual(bar_result.data, np.array([2, 2, 0, 3], dtype=np.float32)) - self.assertEqual(bar_result.units, 1) - self.assertTupleEqual(bar_result.cell_methods, ()) - self.assertCML(bar_result, ("analysis", "max_run_bar_2d.cml"), checksum=False) + _shared_utils.assert_array_equal( + bar_result.data, np.array([2, 2, 0, 3], dtype=np.float32) + ) + assert bar_result.units == 1 + assert bar_result.cell_methods == () + _shared_utils.assert_CML( + self.request, bar_result, ("analysis", "max_run_bar_2d.cml"), checksum=False + ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = cube.collapsed( ("foo", "bar"), iris.analysis.MAX_RUN, @@ -957,7 +1020,7 @@ def test_max_run_2d(self): ) def test_max_run_masked(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() # [[ 0 1 2 3] # [ 4 5 6 7] # [ 8 9 10 11]] @@ -972,40 +1035,51 @@ def test_max_run_masked(self): iris.analysis.MAX_RUN, function=lambda val: np.isin(val, [0, 1, 4, 5, 6, 9, 10, 11]), ) - self.assertArrayEqual(result.data, np.array([1, 1, 2, 0], dtype=np.float32)) - self.assertEqual(result.units, 1) - self.assertTupleEqual(result.cell_methods, ()) - self.assertCML( - result, ("analysis", "max_run_bar_2d_masked.cml"), checksum=False + _shared_utils.assert_array_equal( + result.data, np.array([1, 1, 2, 0], dtype=np.float32) + ) + assert result.units == 1 + assert result.cell_methods == () + _shared_utils.assert_CML( + self.request, + result, + ("analysis", "max_run_bar_2d_masked.cml"), + checksum=False, ) def test_weighted_sum_consistency(self): # weighted sum with unit weights should be the same as a sum - cube = tests.stock.simple_1d() + cube = stock.simple_1d() normal_sum = cube.collapsed("foo", iris.analysis.SUM) weights = np.ones_like(cube.data) weighted_sum = cube.collapsed("foo", iris.analysis.SUM, weights=weights) - self.assertArrayAlmostEqual(normal_sum.data, weighted_sum.data) + _shared_utils.assert_array_almost_equal(normal_sum.data, weighted_sum.data) def test_weighted_sum_1d(self): # verify 1d weighted sum is correct - cube = tests.stock.simple_1d() + cube = stock.simple_1d() weights = np.array([0.05, 0.05, 0.1, 0.1, 0.2, 0.3, 0.2, 0.1, 0.1, 0.05, 0.05]) result = cube.collapsed("foo", iris.analysis.SUM, weights=weights) - self.assertAlmostEqual(result.data, 6.5) - self.assertCML(result, ("analysis", "sum_weighted_1d.cml"), checksum=False) + assert result.data == pytest.approx(6.5) + _shared_utils.assert_CML( + self.request, result, ("analysis", "sum_weighted_1d.cml"), checksum=False + ) def test_weighted_sum_2d(self): # verify 2d weighted sum is correct - cube = tests.stock.simple_2d() + cube = stock.simple_2d() weights = np.array([0.3, 0.4, 0.3]) weights = iris.util.broadcast_to_shape(weights, cube.shape, [0]) result = cube.collapsed("bar", iris.analysis.SUM, weights=weights) - self.assertArrayAlmostEqual(result.data, np.array([4.0, 5.0, 6.0, 7.0])) - self.assertCML(result, ("analysis", "sum_weighted_2d.cml"), checksum=False) + _shared_utils.assert_array_almost_equal( + result.data, np.array([4.0, 5.0, 6.0, 7.0]) + ) + _shared_utils.assert_CML( + self.request, result, ("analysis", "sum_weighted_2d.cml"), checksum=False + ) def test_weighted_rms(self): - cube = tests.stock.simple_2d() + cube = stock.simple_2d() # modify cube data so that the results are nice numbers cube.data = np.array( [[4, 7, 10, 8], [21, 30, 12, 24], [14, 16, 20, 8]], @@ -1017,12 +1091,14 @@ def test_weighted_rms(self): ) expected_result = np.array([8.0, 24.0, 16.0]) result = cube.collapsed("foo", iris.analysis.RMS, weights=weights) - self.assertArrayAlmostEqual(result.data, expected_result) - self.assertCML(result, ("analysis", "rms_weighted_2d.cml"), checksum=False) + _shared_utils.assert_array_almost_equal(result.data, expected_result) + _shared_utils.assert_CML( + self.request, result, ("analysis", "rms_weighted_2d.cml"), checksum=False + ) -@tests.skip_data -class TestRotatedPole(tests.IrisTest): +@_shared_utils.skip_data +class TestRotatedPole: def _check_both_conversions(self, cube, index): rlons, rlats = iris.analysis.cartography.get_xy_grids(cube) rcs = cube.coord_system("RotatedGeogCS") @@ -1032,21 +1108,21 @@ def _check_both_conversions(self, cube, index): rcs.grid_north_pole_longitude, rcs.grid_north_pole_latitude, ) - self.assertDataAlmostEqual( + _shared_utils.assert_data_almost_equal( x, ("analysis", "rotated_pole.{}.x.json".format(index)) ) - self.assertDataAlmostEqual( + _shared_utils.assert_data_almost_equal( y, ("analysis", "rotated_pole.{}.y.json".format(index)) ) - self.assertDataAlmostEqual( + _shared_utils.assert_data_almost_equal( rlons, ("analysis", "rotated_pole.{}.rlon.json".format(index)) ) - self.assertDataAlmostEqual( + _shared_utils.assert_data_almost_equal( rlats, ("analysis", "rotated_pole.{}.rlat.json".format(index)) ) def test_all(self): - path = tests.get_data_path(("PP", "ukVorog", "ukv_orog_refonly.pp")) + path = _shared_utils.get_data_path(("PP", "ukVorog", "ukv_orog_refonly.pp")) master_cube = iris.load_cube(path) # Check overall behaviour. @@ -1067,8 +1143,8 @@ def test_unrotate_nd(self): solx = np.array([[-16.42176094, -14.85892262], [-16.71055023, -14.58434624]]) soly = np.array([[46.00724251, 51.29188893], [46.98728486, 50.30706042]]) - self.assertArrayAlmostEqual(resx, solx) - self.assertArrayAlmostEqual(resy, soly) + _shared_utils.assert_array_almost_equal(resx, solx) + _shared_utils.assert_array_almost_equal(resy, soly) def test_unrotate_1d(self): rlons = np.array([350.0, 352.0, 354.0, 356.0]) @@ -1082,8 +1158,8 @@ def test_unrotate_1d(self): solx = np.array([-16.42176094, -14.85892262, -12.88946157, -10.35078336]) soly = np.array([46.00724251, 51.29188893, 56.55031485, 61.77015703]) - self.assertArrayAlmostEqual(resx, solx) - self.assertArrayAlmostEqual(resy, soly) + _shared_utils.assert_array_almost_equal(resx, solx) + _shared_utils.assert_array_almost_equal(resy, soly) def test_rotate_nd(self): rlons = np.array([[350.0, 351.0], [352.0, 353.0]]) @@ -1095,8 +1171,8 @@ def test_rotate_nd(self): solx = np.array([[148.69672569, 149.24727087], [149.79067025, 150.31754368]]) soly = np.array([[18.60905789, 23.67749384], [28.74419024, 33.8087963]]) - self.assertArrayAlmostEqual(resx, solx) - self.assertArrayAlmostEqual(resy, soly) + _shared_utils.assert_array_almost_equal(resx, solx) + _shared_utils.assert_array_almost_equal(resy, soly) def test_rotate_1d(self): rlons = np.array([350.0, 351.0, 352.0, 353.0]) @@ -1110,22 +1186,27 @@ def test_rotate_1d(self): solx = np.array([148.69672569, 149.24727087, 149.79067025, 150.31754368]) soly = np.array([18.60905789, 23.67749384, 28.74419024, 33.8087963]) - self.assertArrayAlmostEqual(resx, solx) - self.assertArrayAlmostEqual(resy, soly) + _shared_utils.assert_array_almost_equal(resx, solx) + _shared_utils.assert_array_almost_equal(resy, soly) -@tests.skip_data -class TestAreaWeights(tests.IrisTest): +@_shared_utils.skip_data +class TestAreaWeights: # Note: chunks is simply ignored for non-lazy data @pytest.mark.parametrize("chunks", [None, (2, 3)]) + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + def test_area_weights(self): - small_cube = iris.tests.stock.simple_pp() + small_cube = stock.simple_pp() # Get offset, subsampled region: small enough to test against literals small_cube = small_cube[10:, 35:] small_cube = small_cube[::8, ::8] small_cube = small_cube[:5, :4] # pre-check non-data properties - self.assertCML( + _shared_utils.assert_CML( + self.request, small_cube, ("analysis", "areaweights_original.cml"), checksum=False, @@ -1145,19 +1226,20 @@ def test_area_weights(self): ], dtype=np.float64, ) - self.assertArrayAllClose(area_weights, expected_results, rtol=1e-8) + _shared_utils.assert_array_all_close(area_weights, expected_results, rtol=1e-8) # Check there was no residual change small_cube.coord("latitude").bounds = None small_cube.coord("longitude").bounds = None - self.assertCML( + _shared_utils.assert_CML( + self.request, small_cube, ("analysis", "areaweights_original.cml"), checksum=False, ) -@tests.skip_data +@_shared_utils.skip_data class TestLazyAreaWeights: @pytest.mark.parametrize("normalize", [True, False]) @pytest.mark.parametrize("chunks", [None, (2, 3, 4), (2, 2, 2)]) @@ -1198,71 +1280,72 @@ def test_lazy_area_weights(self, chunks, normalize): np.testing.assert_allclose(area_weights.compute(), expected) -@tests.skip_data -class TestAreaWeightGeneration(tests.IrisTest): - def setUp(self): - self.cube = iris.tests.stock.realistic_4d() +@_shared_utils.skip_data +class TestAreaWeightGeneration: + @pytest.fixture(autouse=True) + def _setup(self): + self.cube = stock.realistic_4d() def test_area_weights_std(self): # weights for stock 4d data weights = iris.analysis.cartography.area_weights(self.cube) - self.assertEqual(weights.shape, self.cube.shape) + assert weights.shape == self.cube.shape def test_area_weights_order(self): # weights for data with dimensions in a different order order = [3, 2, 1, 0] # (lon, lat, level, time) self.cube.transpose(order) weights = iris.analysis.cartography.area_weights(self.cube) - self.assertEqual(weights.shape, self.cube.shape) + assert weights.shape == self.cube.shape def test_area_weights_non_adjacent(self): # weights for cube with non-adjacent latitude/longitude dimensions order = [0, 3, 1, 2] # (time, lon, level, lat) self.cube.transpose(order) weights = iris.analysis.cartography.area_weights(self.cube) - self.assertEqual(weights.shape, self.cube.shape) + assert weights.shape == self.cube.shape def test_area_weights_scalar_latitude(self): # weights for cube with a scalar latitude dimension cube = self.cube[:, :, 0, :] weights = iris.analysis.cartography.area_weights(cube) - self.assertEqual(weights.shape, cube.shape) + assert weights.shape == cube.shape def test_area_weights_scalar_longitude(self): # weights for cube with a scalar longitude dimension cube = self.cube[:, :, :, 0] weights = iris.analysis.cartography.area_weights(cube) - self.assertEqual(weights.shape, cube.shape) + assert weights.shape == cube.shape def test_area_weights_scalar(self): # weights for cube with scalar latitude and longitude dimensions cube = self.cube[:, :, 0, 0] weights = iris.analysis.cartography.area_weights(cube) - self.assertEqual(weights.shape, cube.shape) + assert weights.shape == cube.shape def test_area_weights_singleton_latitude(self): # singleton (1-point) latitude dimension cube = self.cube[:, :, 0:1, :] weights = iris.analysis.cartography.area_weights(cube) - self.assertEqual(weights.shape, cube.shape) + assert weights.shape == cube.shape def test_area_weights_singleton_longitude(self): # singleton (1-point) longitude dimension cube = self.cube[:, :, :, 0:1] weights = iris.analysis.cartography.area_weights(cube) - self.assertEqual(weights.shape, cube.shape) + assert weights.shape == cube.shape def test_area_weights_singletons(self): # singleton (1-point) latitude and longitude dimensions cube = self.cube[:, :, 0:1, 0:1] weights = iris.analysis.cartography.area_weights(cube) - self.assertEqual(weights.shape, cube.shape) + assert weights.shape == cube.shape def test_area_weights_normalized(self): # normalized area weights must sum to one over lat/lon dimensions. weights = iris.analysis.cartography.area_weights(self.cube, normalize=True) sumweights = weights.sum(axis=3).sum(axis=2) # sum over lon and lat - self.assertArrayAlmostEqual(sumweights, 1) + _shared_utils.assert_array_almost_equal(sumweights, 1) def test_area_weights_non_contiguous(self): # Slice the cube so that we have non-contiguous longitude @@ -1271,23 +1354,24 @@ def test_area_weights_non_contiguous(self): cube = self.cube[..., ind] weights = iris.analysis.cartography.area_weights(cube) expected = iris.analysis.cartography.area_weights(self.cube)[..., ind] - self.assertArrayEqual(weights, expected) + _shared_utils.assert_array_equal(weights, expected) def test_area_weights_no_lon_bounds(self): self.cube.coord("grid_longitude").bounds = None - with self.assertRaises(ValueError): + with pytest.raises(ValueError): iris.analysis.cartography.area_weights(self.cube) def test_area_weights_no_lat_bounds(self): self.cube.coord("grid_latitude").bounds = None - with self.assertRaises(ValueError): + with pytest.raises(ValueError): iris.analysis.cartography.area_weights(self.cube) -@tests.skip_data -class TestLatitudeWeightGeneration(tests.IrisTest): - def setUp(self): - path = iris.tests.get_data_path( +@_shared_utils.skip_data +class TestLatitudeWeightGeneration: + @pytest.fixture(autouse=True) + def _setup(self): + path = _shared_utils.get_data_path( ["NetCDF", "rotated", "xyt", "small_rotPole_precipitation.nc"] ) self.cube = iris.load_cube(path) @@ -1317,52 +1401,58 @@ def test_cosine_latitude_weights_range(self): ) cube.add_dim_coord(lat_coord, 0) weights = iris.analysis.cartography.cosine_latitude_weights(cube) - self.assertTrue(weights.max() <= 1) - self.assertTrue(weights.min() >= 0) + assert weights.max() <= 1 + assert weights.min() >= 0 def test_cosine_latitude_weights_0d(self): # 0d latitude dimension (scalar coordinate) weights = iris.analysis.cartography.cosine_latitude_weights( self.cube_dim_lat[:, 0, :] ) - self.assertEqual(weights.shape, self.cube_dim_lat[:, 0, :].shape) - self.assertAlmostEqual(weights[0, 0], np.cos(np.deg2rad(self.lat1d[0]))) + assert weights.shape == self.cube_dim_lat[:, 0, :].shape + assert weights[0, 0] == pytest.approx(np.cos(np.deg2rad(self.lat1d[0]))) def test_cosine_latitude_weights_1d_singleton(self): # singleton (1-point) 1d latitude coordinate (time, lat, lon) cube = self.cube_dim_lat[:, 0:1, :] weights = iris.analysis.cartography.cosine_latitude_weights(cube) - self.assertEqual(weights.shape, cube.shape) - self.assertAlmostEqual(weights[0, 0, 0], np.cos(np.deg2rad(self.lat1d[0]))) + assert weights.shape == cube.shape + assert weights[0, 0, 0] == pytest.approx(np.cos(np.deg2rad(self.lat1d[0]))) def test_cosine_latitude_weights_1d(self): # 1d latitude coordinate (time, lat, lon) weights = iris.analysis.cartography.cosine_latitude_weights(self.cube_dim_lat) - self.assertEqual(weights.shape, self.cube.shape) - self.assertArrayAlmostEqual(weights[0, :, 0], np.cos(np.deg2rad(self.lat1d))) + assert weights.shape == self.cube.shape + _shared_utils.assert_array_almost_equal( + weights[0, :, 0], np.cos(np.deg2rad(self.lat1d)) + ) def test_cosine_latitude_weights_1d_latitude_first(self): # 1d latitude coordinate with latitude first (lat, time, lon) order = [1, 0, 2] # (lat, time, lon) self.cube_dim_lat.transpose(order) weights = iris.analysis.cartography.cosine_latitude_weights(self.cube_dim_lat) - self.assertEqual(weights.shape, self.cube_dim_lat.shape) - self.assertArrayAlmostEqual(weights[:, 0, 0], np.cos(np.deg2rad(self.lat1d))) + assert weights.shape == self.cube_dim_lat.shape + _shared_utils.assert_array_almost_equal( + weights[:, 0, 0], np.cos(np.deg2rad(self.lat1d)) + ) def test_cosine_latitude_weights_1d_latitude_last(self): # 1d latitude coordinate with latitude last (time, lon, lat) order = [0, 2, 1] # (time, lon, lat) self.cube_dim_lat.transpose(order) weights = iris.analysis.cartography.cosine_latitude_weights(self.cube_dim_lat) - self.assertEqual(weights.shape, self.cube_dim_lat.shape) - self.assertArrayAlmostEqual(weights[0, 0, :], np.cos(np.deg2rad(self.lat1d))) + assert weights.shape == self.cube_dim_lat.shape + _shared_utils.assert_array_almost_equal( + weights[0, 0, :], np.cos(np.deg2rad(self.lat1d)) + ) def test_cosine_latitude_weights_2d_singleton1(self): # 2d latitude coordinate with first dimension singleton cube = self.cube_aux_lat[:, 0:1, :] weights = iris.analysis.cartography.cosine_latitude_weights(cube) - self.assertEqual(weights.shape, cube.shape) - self.assertArrayAlmostEqual( + assert weights.shape == cube.shape + _shared_utils.assert_array_almost_equal( weights[0, :, :], np.cos(np.deg2rad(self.lat2d[0:1, :])) ) @@ -1370,8 +1460,8 @@ def test_cosine_latitude_weights_2d_singleton2(self): # 2d latitude coordinate with second dimension singleton cube = self.cube_aux_lat[:, :, 0:1] weights = iris.analysis.cartography.cosine_latitude_weights(cube) - self.assertEqual(weights.shape, cube.shape) - self.assertArrayAlmostEqual( + assert weights.shape == cube.shape + _shared_utils.assert_array_almost_equal( weights[0, :, :], np.cos(np.deg2rad(self.lat2d[:, 0:1])) ) @@ -1379,47 +1469,56 @@ def test_cosine_latitude_weights_2d_singleton3(self): # 2d latitude coordinate with both dimensions singleton cube = self.cube_aux_lat[:, 0:1, 0:1] weights = iris.analysis.cartography.cosine_latitude_weights(cube) - self.assertEqual(weights.shape, cube.shape) - self.assertArrayAlmostEqual( + assert weights.shape == cube.shape + _shared_utils.assert_array_almost_equal( weights[0, :, :], np.cos(np.deg2rad(self.lat2d[0:1, 0:1])) ) def test_cosine_latitude_weights_2d(self): # 2d latitude coordinate (time, lat, lon) weights = iris.analysis.cartography.cosine_latitude_weights(self.cube_aux_lat) - self.assertEqual(weights.shape, self.cube_aux_lat.shape) - self.assertArrayAlmostEqual(weights[0, :, :], np.cos(np.deg2rad(self.lat2d))) + assert weights.shape == self.cube_aux_lat.shape + _shared_utils.assert_array_almost_equal( + weights[0, :, :], np.cos(np.deg2rad(self.lat2d)) + ) def test_cosine_latitude_weights_2d_latitude_first(self): # 2d latitude coordinate with latitude first (lat, time, lon) order = [1, 0, 2] # (lat, time, lon) self.cube_aux_lat.transpose(order) weights = iris.analysis.cartography.cosine_latitude_weights(self.cube_aux_lat) - self.assertEqual(weights.shape, self.cube_aux_lat.shape) - self.assertArrayAlmostEqual(weights[:, 0, :], np.cos(np.deg2rad(self.lat2d))) + assert weights.shape == self.cube_aux_lat.shape + _shared_utils.assert_array_almost_equal( + weights[:, 0, :], np.cos(np.deg2rad(self.lat2d)) + ) def test_cosine_latitude_weights_2d_latitude_last(self): # 2d latitude coordinate with latitude last (time, lon, lat) order = [0, 2, 1] # (time, lon, lat) self.cube_aux_lat.transpose(order) weights = iris.analysis.cartography.cosine_latitude_weights(self.cube_aux_lat) - self.assertEqual(weights.shape, self.cube_aux_lat.shape) - self.assertArrayAlmostEqual(weights[0, :, :], np.cos(np.deg2rad(self.lat2d.T))) + assert weights.shape == self.cube_aux_lat.shape + _shared_utils.assert_array_almost_equal( + weights[0, :, :], np.cos(np.deg2rad(self.lat2d.T)) + ) def test_cosine_latitude_weights_no_latitude(self): # no coordinate identified as latitude self.cube_dim_lat.remove_coord("grid_latitude") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = iris.analysis.cartography.cosine_latitude_weights(self.cube_dim_lat) def test_cosine_latitude_weights_multiple_latitude(self): # two coordinates identified as latitude - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = iris.analysis.cartography.cosine_latitude_weights(self.cube) -class TestRollingWindow(tests.IrisTest): - def setUp(self): +class TestRollingWindow: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + # XXX Comes from test_aggregated_by cube = iris.cube.Cube( np.array([[6, 10, 12, 18], [8, 12, 14, 20], [18, 12, 10, 6]]), @@ -1450,7 +1549,7 @@ def test_non_mean_operator(self): expected_result = np.array( [[10, 12, 18], [12, 14, 20], [18, 12, 10]], dtype=np.float64 ) - self.assertArrayEqual(expected_result, res_cube.data) + _shared_utils.assert_array_equal(expected_result, res_cube.data) def test_longitude_simple(self): res_cube = self.cube.rolling_window("longitude", iris.analysis.MEAN, window=2) @@ -1460,11 +1559,15 @@ def test_longitude_simple(self): dtype=np.float64, ) - self.assertArrayEqual(expected_result, res_cube.data) + _shared_utils.assert_array_equal(expected_result, res_cube.data) - self.assertCML(res_cube, ("analysis", "rolling_window", "simple_longitude.cml")) + _shared_utils.assert_CML( + self.request, + res_cube, + ("analysis", "rolling_window", "simple_longitude.cml"), + ) - self.assertRaises( + pytest.raises( ValueError, self.cube.rolling_window, "longitude", @@ -1493,12 +1596,12 @@ def test_longitude_masked(self): dtype=np.float64, ) - self.assertMaskedArrayEqual(expected_result, res_cube.data) + _shared_utils.assert_masked_array_almost_equal(expected_result, res_cube.data) def test_longitude_circular(self): cube = self.cube cube.coord("longitude").circular = True - self.assertRaises( + pytest.raises( iris.exceptions.NotYetImplementedError, self.cube.rolling_window, "longitude", @@ -1511,12 +1614,16 @@ def test_different_length_windows(self): expected_result = np.array([[11.5], [13.5], [11.5]], dtype=np.float64) - self.assertArrayEqual(expected_result, res_cube.data) + _shared_utils.assert_array_equal(expected_result, res_cube.data) - self.assertCML(res_cube, ("analysis", "rolling_window", "size_4_longitude.cml")) + _shared_utils.assert_CML( + self.request, + res_cube, + ("analysis", "rolling_window", "size_4_longitude.cml"), + ) # Window too long: - self.assertRaises( + pytest.raises( ValueError, self.cube.rolling_window, "longitude", @@ -1524,7 +1631,7 @@ def test_different_length_windows(self): window=6, ) # Window too small: - self.assertRaises( + pytest.raises( ValueError, self.cube.rolling_window, "longitude", @@ -1533,7 +1640,7 @@ def test_different_length_windows(self): ) def test_bad_coordinate(self): - self.assertRaises( + pytest.raises( KeyError, self.cube.rolling_window, "wibble", @@ -1549,9 +1656,13 @@ def test_latitude_simple(self): dtype=np.float64, ) - self.assertArrayEqual(expected_result, res_cube.data) + _shared_utils.assert_array_equal(expected_result, res_cube.data) - self.assertCML(res_cube, ("analysis", "rolling_window", "simple_latitude.cml")) + _shared_utils.assert_CML( + self.request, + res_cube, + ("analysis", "rolling_window", "simple_latitude.cml"), + ) def test_mean_with_weights_consistency(self): # equal weights should be the same as the mean with no weights @@ -1562,7 +1673,7 @@ def test_mean_with_weights_consistency(self): expected_result = self.cube.rolling_window( "longitude", iris.analysis.MEAN, window=2 ) - self.assertArrayEqual(expected_result.data, res_cube.data) + _shared_utils.assert_array_equal(expected_result.data, res_cube.data) def test_mean_with_weights(self): # rolling window mean with weights @@ -1574,10 +1685,10 @@ def test_mean_with_weights(self): [[10.2, 13.6], [12.2, 15.6], [12.0, 9.0]], dtype=np.float64 ) # use almost equal to compare floats - self.assertArrayAlmostEqual(expected_result, res_cube.data) + _shared_utils.assert_array_almost_equal(expected_result, res_cube.data) -class TestCreateWeightedAggregatorFn(tests.IrisTest): +class TestCreateWeightedAggregatorFn: @staticmethod def aggregator_fn(data, axis, **kwargs): return (data, axis, kwargs) @@ -1587,20 +1698,20 @@ def test_no_weights_supplied(self): self.aggregator_fn, 42, test_kwarg="test" ) output = aggregator_fn("dummy_array", None) - self.assertEqual(len(output), 3) - self.assertEqual(output[0], "dummy_array") - self.assertEqual(output[1], 42) - self.assertEqual(output[2], {"test_kwarg": "test"}) + assert len(output) == 3 + assert output[0] == "dummy_array" + assert output[1] == 42 + assert output[2] == {"test_kwarg": "test"} def test_weights_supplied(self): aggregator_fn = iris.analysis.create_weighted_aggregator_fn( self.aggregator_fn, 42, test_kwarg="test" ) output = aggregator_fn("dummy_array", "w") - self.assertEqual(len(output), 3) - self.assertEqual(output[0], "dummy_array") - self.assertEqual(output[1], 42) - self.assertEqual(output[2], {"test_kwarg": "test", "weights": "w"}) + assert len(output) == 3 + assert output[0] == "dummy_array" + assert output[1] == 42 + assert output[2] == {"test_kwarg": "test", "weights": "w"} def test_weights_in_kwargs(self): kwargs = {"test_kwarg": "test", "weights": "ignored"} @@ -1608,16 +1719,16 @@ def test_weights_in_kwargs(self): self.aggregator_fn, 42, **kwargs ) output = aggregator_fn("dummy_array", "w") - self.assertEqual(len(output), 3) - self.assertEqual(output[0], "dummy_array") - self.assertEqual(output[1], 42) - self.assertEqual(output[2], {"test_kwarg": "test", "weights": "w"}) - self.assertEqual(kwargs, {"test_kwarg": "test", "weights": "ignored"}) + assert len(output) == 3 + assert output[0] == "dummy_array" + assert output[1] == 42 + assert output[2] == {"test_kwarg": "test", "weights": "w"} + assert kwargs == {"test_kwarg": "test", "weights": "ignored"} class TestWeights: @pytest.fixture(autouse=True) - def setup_test_data(self): + def _setup_test_data(self): self.array_lib = np self.target_type = np.ndarray self.create_test_data() @@ -1672,28 +1783,28 @@ def test_init_with_str_dim_coord(self): # DimCoord always realizes points assert isinstance(weights.array, np.ndarray) assert isinstance(weights.units, cf_units.Unit) - np.testing.assert_array_equal(weights.array, [[0, 0, 0], [1, 1, 1]]) + _shared_utils.assert_array_equal(weights.array, [[0, 0, 0], [1, 1, 1]]) assert weights.units == "degrees" def test_init_with_str_aux_coord(self): weights = _Weights("auxcoord", self.cube) assert isinstance(weights.array, self.target_type) assert isinstance(weights.units, cf_units.Unit) - np.testing.assert_array_equal(weights.array, [[3, 3, 3], [4, 4, 4]]) + _shared_utils.assert_array_equal(weights.array, [[3, 3, 3], [4, 4, 4]]) assert weights.units == "s" def test_init_with_str_ancillary_variable(self): weights = _Weights("ancvar", self.cube) assert isinstance(weights.array, self.target_type) assert isinstance(weights.units, cf_units.Unit) - np.testing.assert_array_equal(weights.array, [[5, 6, 7], [5, 6, 7]]) + _shared_utils.assert_array_equal(weights.array, [[5, 6, 7], [5, 6, 7]]) assert weights.units == "kg" def test_init_with_str_cell_measure(self): weights = _Weights("cell_area", self.cube) assert isinstance(weights.array, self.target_type) assert isinstance(weights.units, cf_units.Unit) - np.testing.assert_array_equal(weights.array, self.data) + _shared_utils.assert_array_equal(weights.array, self.data) assert weights.units == "m2" def test_init_with_dim_coord(self): @@ -1701,28 +1812,28 @@ def test_init_with_dim_coord(self): # DimCoord always realizes points assert isinstance(weights.array, np.ndarray) assert isinstance(weights.units, cf_units.Unit) - np.testing.assert_array_equal(weights.array, [[0, 0, 0], [1, 1, 1]]) + _shared_utils.assert_array_equal(weights.array, [[0, 0, 0], [1, 1, 1]]) assert weights.units == "degrees" def test_init_with_aux_coord(self): weights = _Weights(self.aux_coord, self.cube) assert isinstance(weights.array, self.target_type) assert isinstance(weights.units, cf_units.Unit) - np.testing.assert_array_equal(weights.array, [[3, 3, 3], [4, 4, 4]]) + _shared_utils.assert_array_equal(weights.array, [[3, 3, 3], [4, 4, 4]]) assert weights.units == "s" def test_init_with_ancillary_variable(self): weights = _Weights(self.ancillary_variable, self.cube) assert isinstance(weights.array, self.target_type) assert isinstance(weights.units, cf_units.Unit) - np.testing.assert_array_equal(weights.array, [[5, 6, 7], [5, 6, 7]]) + _shared_utils.assert_array_equal(weights.array, [[5, 6, 7], [5, 6, 7]]) assert weights.units == "kg" def test_init_with_cell_measure(self): weights = _Weights(self.cell_measure, self.cube) assert isinstance(weights.array, self.target_type) assert isinstance(weights.units, cf_units.Unit) - np.testing.assert_array_equal(weights.array, self.data) + _shared_utils.assert_array_equal(weights.array, self.data) assert weights.units == "m2" def test_init_with_list(self): @@ -1738,7 +1849,7 @@ class TestWeightsLazy(TestWeights): """Repeat tests from ``TestWeights`` with lazy arrays.""" @pytest.fixture(autouse=True) - def setup_test_data(self): + def _setup_test_data(self): self.array_lib = da self.target_type = da.core.Array self.create_test_data() @@ -1756,7 +1867,7 @@ def test__Groupby_repr(): @pytest.mark.parametrize( - "kwargs,expected", + ("kwargs", "expected"), [ ({}, "kg m-2"), ({"test": "m"}, "kg m-2"), @@ -1785,7 +1896,3 @@ def test_sum_units_func(kwargs, expected): # changed if the units have not changed (even when weights units are "1") if result == units: assert result.origin == expected - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/test_analysis_calculus.py b/lib/iris/tests/test_analysis_calculus.py index 70c1077def..74e0f90d8e 100644 --- a/lib/iris/tests/test_analysis_calculus.py +++ b/lib/iris/tests/test_analysis_calculus.py @@ -3,12 +3,8 @@ # 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. -# import iris tests first so that some things can be initialised before importing anything else -import iris.tests as tests # isort:skip - -import unittest - import numpy as np +import pytest import iris import iris.analysis.calculus @@ -16,18 +12,19 @@ import iris.coords from iris.coords import DimCoord import iris.cube +from iris.tests import _shared_utils import iris.tests.stock -class TestCubeDelta(tests.IrisTest): - @tests.skip_data +class TestCubeDelta: + @_shared_utils.skip_data def test_invalid(self): cube = iris.tests.stock.realistic_4d() - with self.assertRaises(iris.exceptions.CoordinateMultiDimError): + with pytest.raises(iris.exceptions.CoordinateMultiDimError): _ = iris.analysis.calculus.cube_delta(cube, "surface_altitude") - with self.assertRaises(iris.exceptions.CoordinateMultiDimError): + with pytest.raises(iris.exceptions.CoordinateMultiDimError): _ = iris.analysis.calculus.cube_delta(cube, "altitude") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = iris.analysis.calculus.cube_delta(cube, "forecast_period") def test_delta_coord_lookup(self): @@ -44,13 +41,13 @@ def test_delta_coord_lookup(self): cube.add_dim_coord(coord, 0) delta = iris.analysis.calculus.cube_delta(cube, "projection_x_coordinate") delta_coord = delta.coord("projection_x_coordinate") - self.assertEqual(delta_coord, delta.coord(coord)) - self.assertEqual(coord, cube.coord(delta_coord)) + assert delta_coord == delta.coord(coord) + assert coord == cube.coord(delta_coord) -class TestDeltaAndMidpoint(tests.IrisTest): +class TestDeltaAndMidpoint: def _simple_filename(self, suffix): - return tests.get_result_path( + return _shared_utils.get_result_path( ("analysis", "delta_and_midpoint", "simple%s.cml" % suffix) ) @@ -61,13 +58,13 @@ def test_simple1_delta_midpoint(self): units="degrees", circular=True, ) - self.assertXMLElement(a, self._simple_filename("1")) + _shared_utils.assert_XML_element(a, self._simple_filename("1")) delta = iris.analysis.calculus._construct_delta_coord(a) - self.assertXMLElement(delta, self._simple_filename("1_delta")) + _shared_utils.assert_XML_element(delta, self._simple_filename("1_delta")) midpoint = iris.analysis.calculus._construct_midpoint_coord(a) - self.assertXMLElement(midpoint, self._simple_filename("1_midpoint")) + _shared_utils.assert_XML_element(midpoint, self._simple_filename("1_midpoint")) def test_simple2_delta_midpoint(self): a = iris.coords.DimCoord( @@ -76,13 +73,13 @@ def test_simple2_delta_midpoint(self): units="degrees", circular=True, ) - self.assertXMLElement(a, self._simple_filename("2")) + _shared_utils.assert_XML_element(a, self._simple_filename("2")) delta = iris.analysis.calculus._construct_delta_coord(a) - self.assertXMLElement(delta, self._simple_filename("2_delta")) + _shared_utils.assert_XML_element(delta, self._simple_filename("2_delta")) midpoint = iris.analysis.calculus._construct_midpoint_coord(a) - self.assertXMLElement(midpoint, self._simple_filename("2_midpoint")) + _shared_utils.assert_XML_element(midpoint, self._simple_filename("2_midpoint")) def test_simple3_delta_midpoint(self): a = iris.coords.DimCoord( @@ -92,13 +89,13 @@ def test_simple3_delta_midpoint(self): circular=True, ) a.guess_bounds(0.5) - self.assertXMLElement(a, self._simple_filename("3")) + _shared_utils.assert_XML_element(a, self._simple_filename("3")) delta = iris.analysis.calculus._construct_delta_coord(a) - self.assertXMLElement(delta, self._simple_filename("3_delta")) + _shared_utils.assert_XML_element(delta, self._simple_filename("3_delta")) midpoint = iris.analysis.calculus._construct_midpoint_coord(a) - self.assertXMLElement(midpoint, self._simple_filename("3_midpoint")) + _shared_utils.assert_XML_element(midpoint, self._simple_filename("3_midpoint")) def test_simple4_delta_midpoint(self): a = iris.coords.AuxCoord( @@ -108,13 +105,13 @@ def test_simple4_delta_midpoint(self): ) a.guess_bounds() b = a.copy() - self.assertXMLElement(b, self._simple_filename("4")) + _shared_utils.assert_XML_element(b, self._simple_filename("4")) delta = iris.analysis.calculus._construct_delta_coord(b) - self.assertXMLElement(delta, self._simple_filename("4_delta")) + _shared_utils.assert_XML_element(delta, self._simple_filename("4_delta")) midpoint = iris.analysis.calculus._construct_midpoint_coord(b) - self.assertXMLElement(midpoint, self._simple_filename("4_midpoint")) + _shared_utils.assert_XML_element(midpoint, self._simple_filename("4_midpoint")) def test_simple5_not_degrees_delta_midpoint(self): # Not sure it makes sense to have a circular coordinate which does not have a modulus but test it anyway. @@ -124,13 +121,13 @@ def test_simple5_not_degrees_delta_midpoint(self): units="meter", circular=True, ) - self.assertXMLElement(a, self._simple_filename("5")) + _shared_utils.assert_XML_element(a, self._simple_filename("5")) delta = iris.analysis.calculus._construct_delta_coord(a) - self.assertXMLElement(delta, self._simple_filename("5_delta")) + _shared_utils.assert_XML_element(delta, self._simple_filename("5_delta")) midpoints = iris.analysis.calculus._construct_midpoint_coord(a) - self.assertXMLElement(midpoints, self._simple_filename("5_midpoint")) + _shared_utils.assert_XML_element(midpoints, self._simple_filename("5_midpoint")) def test_simple6_delta_midpoint(self): a = iris.coords.DimCoord( @@ -140,7 +137,7 @@ def test_simple6_delta_midpoint(self): circular=True, ) midpoints = iris.analysis.calculus._construct_midpoint_coord(a) - self.assertXMLElement(midpoints, self._simple_filename("6")) + _shared_utils.assert_XML_element(midpoints, self._simple_filename("6")) def test_singular_delta(self): # Test single valued coordinate mid-points when circular @@ -149,7 +146,7 @@ def test_singular_delta(self): ) r_expl = iris.analysis.calculus._construct_delta_coord(lon) - self.assertXMLElement( + _shared_utils.assert_XML_element( r_expl, ( "analysis", @@ -160,7 +157,7 @@ def test_singular_delta(self): # Test single valued coordinate mid-points when not circular lon.circular = False - with self.assertRaises(ValueError): + with pytest.raises(ValueError): iris.analysis.calculus._construct_delta_coord(lon) def test_singular_midpoint(self): @@ -170,7 +167,7 @@ def test_singular_midpoint(self): ) r_expl = iris.analysis.calculus._construct_midpoint_coord(lon) - self.assertXMLElement( + _shared_utils.assert_XML_element( r_expl, ( "analysis", @@ -181,12 +178,13 @@ def test_singular_midpoint(self): # Test single valued coordinate mid-points when not circular lon.circular = False - with self.assertRaises(ValueError): + with pytest.raises(ValueError): iris.analysis.calculus._construct_midpoint_coord(lon) -class TestCoordTrig(tests.IrisTest): - def setUp(self): +class TestCoordTrig: + @pytest.fixture(autouse=True) + def _setup(self): points = np.arange(20, dtype=np.float32) * 2.3 bounds = np.concatenate([[points - 0.5 * 2.3], [points + 0.5 * 2.3]]).T self.lat = iris.coords.AuxCoord( @@ -204,41 +202,41 @@ def test_sin(self): sin_of_coord_radians = iris.analysis.calculus._coord_sin(self.rlat) # Check the values are correct (within a tolerance) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( np.sin(self.rlat.points), sin_of_coord.points ) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( np.sin(self.rlat.bounds), sin_of_coord.bounds ) # Check that the results of the sin function are almost equal when operating on a coord with degrees and radians - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( sin_of_coord.points, sin_of_coord_radians.points ) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( sin_of_coord.bounds, sin_of_coord_radians.bounds ) - self.assertEqual(sin_of_coord.name(), "sin(latitude)") - self.assertEqual(sin_of_coord.units, "1") + assert sin_of_coord.name() == "sin(latitude)" + assert sin_of_coord.units == "1" def test_cos(self): cos_of_coord = iris.analysis.calculus._coord_cos(self.lat) cos_of_coord_radians = iris.analysis.calculus._coord_cos(self.rlat) # Check the values are correct (within a tolerance) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( np.cos(self.rlat.points), cos_of_coord.points ) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( np.cos(self.rlat.bounds), cos_of_coord.bounds ) # Check that the results of the cos function are almost equal when operating on a coord with degrees and radians - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( cos_of_coord.points, cos_of_coord_radians.points ) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( cos_of_coord.bounds, cos_of_coord_radians.bounds ) @@ -248,15 +246,19 @@ def test_cos(self): points=np.array([1], dtype=np.float32) ) - self.assertXMLElement(cos_of_coord, ("analysis", "calculus", "cos_simple.xml")) - self.assertXMLElement( + _shared_utils.assert_XML_element( + cos_of_coord, ("analysis", "calculus", "cos_simple.xml") + ) + _shared_utils.assert_XML_element( cos_of_coord_radians, ("analysis", "calculus", "cos_simple_radians.xml"), ) -class TestCalculusSimple3(tests.IrisTest): - def setUp(self): +class TestCalculusSimple3: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request data = np.arange(2500, dtype=np.float32).reshape(50, 50) cube = iris.cube.Cube(data, standard_name="x_wind", units="km/h") @@ -285,15 +287,21 @@ def setUp(self): def test_diff_wrt_lon(self): t = iris.analysis.calculus.differentiate(self.cube, "longitude") - self.assertCMLApproxData(t, ("analysis", "calculus", "handmade2_wrt_lon.cml")) + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "handmade2_wrt_lon.cml") + ) def test_diff_wrt_lat(self): t = iris.analysis.calculus.differentiate(self.cube, "latitude") - self.assertCMLApproxData(t, ("analysis", "calculus", "handmade2_wrt_lat.cml")) + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "handmade2_wrt_lat.cml") + ) -class TestCalculusSimple2(tests.IrisTest): - def setUp(self): +class TestCalculusSimple2: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request data = np.array( [ [1, 2, 3, 4, 5], @@ -344,47 +352,57 @@ def setUp(self): def test_diff_wrt_x(self): t = iris.analysis.calculus.differentiate(self.cube, "x") - self.assertCMLApproxData(t, ("analysis", "calculus", "handmade_wrt_x.cml")) + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "handmade_wrt_x.cml") + ) def test_diff_wrt_y(self): t = iris.analysis.calculus.differentiate(self.cube, "y") - self.assertCMLApproxData(t, ("analysis", "calculus", "handmade_wrt_y.cml")) + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "handmade_wrt_y.cml") + ) def test_diff_wrt_lon(self): t = iris.analysis.calculus.differentiate(self.cube, "longitude") - self.assertCMLApproxData(t, ("analysis", "calculus", "handmade_wrt_lon.cml")) + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "handmade_wrt_lon.cml") + ) def test_diff_wrt_lat(self): t = iris.analysis.calculus.differentiate(self.cube, "latitude") - self.assertCMLApproxData(t, ("analysis", "calculus", "handmade_wrt_lat.cml")) + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "handmade_wrt_lat.cml") + ) def test_delta_wrt_x(self): t = iris.analysis.calculus.cube_delta(self.cube, "x") - self.assertCMLApproxData( - t, ("analysis", "calculus", "delta_handmade_wrt_x.cml") + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "delta_handmade_wrt_x.cml") ) def test_delta_wrt_y(self): t = iris.analysis.calculus.cube_delta(self.cube, "y") - self.assertCMLApproxData( - t, ("analysis", "calculus", "delta_handmade_wrt_y.cml") + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "delta_handmade_wrt_y.cml") ) def test_delta_wrt_lon(self): t = iris.analysis.calculus.cube_delta(self.cube, "longitude") - self.assertCMLApproxData( - t, ("analysis", "calculus", "delta_handmade_wrt_lon.cml") + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "delta_handmade_wrt_lon.cml") ) def test_delta_wrt_lat(self): t = iris.analysis.calculus.cube_delta(self.cube, "latitude") - self.assertCMLApproxData( - t, ("analysis", "calculus", "delta_handmade_wrt_lat.cml") + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "delta_handmade_wrt_lat.cml") ) -class TestCalculusSimple1(tests.IrisTest): - def setUp(self): +class TestCalculusSimple1: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request data = np.array( [ [1, 2, 3, 4, 5], @@ -410,14 +428,14 @@ def setUp(self): def test_diff_wrt_x(self): t = iris.analysis.calculus.differentiate(self.cube, "x") - self.assertCMLApproxData( - t, ("analysis", "calculus", "handmade_simple_wrt_x.cml") + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "handmade_simple_wrt_x.cml") ) def test_delta_wrt_x(self): t = iris.analysis.calculus.cube_delta(self.cube, "x") - self.assertCMLApproxData( - t, ("analysis", "calculus", "delta_handmade_simple_wrt_x.cml") + _shared_utils.assert_CML_approx_data( + self.request, t, ("analysis", "calculus", "delta_handmade_simple_wrt_x.cml") ) @@ -501,7 +519,11 @@ def build_cube(data, spherical=False): return cube -class TestCalculusWKnownSolutions(tests.IrisTest): +class TestCalculusWKnownSolutions: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + def get_coord_pts(self, cube): """Return (x_pts, x_ones, y_pts, y_ones, z_pts, z_ones) for the given cube.""" x = cube.coord(axis="X") @@ -568,7 +590,7 @@ def test_contrived_differential1(self): data = -sin_x_pts * y_ones result = df_dlon.copy(data=data) - np.testing.assert_array_almost_equal(result.data, df_dlon.data, decimal=3) + _shared_utils.assert_array_almost_equal(result.data, df_dlon.data, decimal=3) def test_contrived_differential2(self): # testing : @@ -585,7 +607,7 @@ def test_contrived_differential2(self): x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(r) result = r.copy(data=y_pts * 2.0 * x_ones * z_ones) - np.testing.assert_array_almost_equal(result.data, r.data, decimal=6) + _shared_utils.assert_array_almost_equal(result.data, r.data, decimal=6) def test_contrived_non_spherical_curl1(self): # testing : @@ -604,15 +626,16 @@ def test_contrived_non_spherical_curl1(self): r = iris.analysis.calculus.curl(u, v) # Curl returns None when there is no components of Curl - self.assertEqual(r[0], None) - self.assertEqual(r[1], None) + assert r[0] is None + assert r[1] is None cube = r[2] - self.assertCML( + _shared_utils.assert_CML( + self.request, cube, ("analysis", "calculus", "grad_contrived_non_spherical1.cml"), checksum=False, ) - self.assertTrue(np.all(np.abs(cube.data - (-1.0)) < 1.0e-7)) + assert np.all(np.abs(cube.data - (-1.0)) < 1.0e-7) def test_contrived_non_spherical_curl2(self): # testing : @@ -639,18 +662,19 @@ def test_contrived_non_spherical_curl2(self): # result.data = y_pts * 2. * x_ones * z_ones # print(repr(r[0].data[0:1, 0:5, 0:25:5])) # print(repr(result.data[0:1, 0:5, 0:25:5])) - # np.testing.assert_array_almost_equal(result.data, r[0].data, decimal=2) + # _shared_utils.assert_array_almost_equal(result.data, r[0].data, decimal=2) # # result = r[1].copy(data=True) # x_pts, x_ones, y_pts, y_ones, z_pts, z_ones = self.get_coord_pts(result) # result.data = pow(z_pts, 2) * x_ones * y_ones - # np.testing.assert_array_almost_equal(result.data, r[1].data, decimal=6) + # _shared_utils.assert_array_almost_equal(result.data, r[1].data, decimal=6) result = r[2].copy() result.data = result.data * 0 + 1 - np.testing.assert_array_almost_equal(result.data, r[2].data, decimal=4) + _shared_utils.assert_array_almost_equal(result.data, r[2].data, decimal=4) - self.assertCML( + _shared_utils.assert_CML( + self.request, r, ("analysis", "calculus", "curl_contrived_cartesian2.cml"), checksum=False, @@ -681,11 +705,14 @@ def test_contrived_spherical_curl1(self): result = r.copy(data=r.data * 0) # Note: This numerical comparison was created when the radius was 1000 times smaller - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( result.data[5:-5], r.data[5:-5] / 1000.0, decimal=1 ) - self.assertCML( - r, ("analysis", "calculus", "grad_contrived1.cml"), checksum=False + _shared_utils.assert_CML( + self.request, + r, + ("analysis", "calculus", "grad_contrived1.cml"), + checksum=False, ) def test_contrived_spherical_curl2(self): @@ -730,22 +757,25 @@ def test_contrived_spherical_curl2(self): result = r.copy(data=-2 * cos_x_pts * cos_y_pts) # Note: This numerical comparison was created when the radius was 1000 times smaller - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( result.data[30:-30, :], r.data[30:-30, :] / 1000.0, decimal=1 ) - self.assertCML( - r, ("analysis", "calculus", "grad_contrived2.cml"), checksum=False + _shared_utils.assert_CML( + self.request, + r, + ("analysis", "calculus", "grad_contrived2.cml"), + checksum=False, ) -class TestCurlInterface(tests.IrisTest): +class TestCurlInterface: def test_non_conformed(self): u = build_cube(np.empty((50, 20)), spherical=True) v = u.copy() y = v.coord("latitude") y.points = y.points + 5 - self.assertRaises(ValueError, iris.analysis.calculus.curl, u, v) + pytest.raises(ValueError, iris.analysis.calculus.curl, u, v) def test_standard_name(self): nx = 20 @@ -758,26 +788,26 @@ def test_standard_name(self): w.rename("w_wind") r = iris.analysis.calculus.spatial_vectors_with_phenom_name(u, v) - self.assertEqual(r, (("u", "v", "w"), "wind")) + assert r == (("u", "v", "w"), "wind") r = iris.analysis.calculus.spatial_vectors_with_phenom_name(u, v, w) - self.assertEqual(r, (("u", "v", "w"), "wind")) + assert r == (("u", "v", "w"), "wind") - self.assertRaises( + pytest.raises( ValueError, iris.analysis.calculus.spatial_vectors_with_phenom_name, u, None, w, ) - self.assertRaises( + pytest.raises( ValueError, iris.analysis.calculus.spatial_vectors_with_phenom_name, None, None, w, ) - self.assertRaises( + pytest.raises( ValueError, iris.analysis.calculus.spatial_vectors_with_phenom_name, None, @@ -789,22 +819,22 @@ def test_standard_name(self): v.rename("y foobar wibble") w.rename("z foobar wibble") r = iris.analysis.calculus.spatial_vectors_with_phenom_name(u, v) - self.assertEqual(r, (("x", "y", "z"), "foobar wibble")) + assert r == (("x", "y", "z"), "foobar wibble") r = iris.analysis.calculus.spatial_vectors_with_phenom_name(u, v, w) - self.assertEqual(r, (("x", "y", "z"), "foobar wibble")) + assert r == (("x", "y", "z"), "foobar wibble") u.rename("wibble foobar") v.rename("wobble foobar") w.rename("tipple foobar") # r = iris.analysis.calculus.spatial_vectors_with_phenom_name(u, v, w) #should raise a Value Error... - self.assertRaises( + pytest.raises( ValueError, iris.analysis.calculus.spatial_vectors_with_phenom_name, u, v, ) - self.assertRaises( + pytest.raises( ValueError, iris.analysis.calculus.spatial_vectors_with_phenom_name, u, @@ -816,14 +846,14 @@ def test_standard_name(self): v.rename("northward_foobar") w.rename("upward_foobar") r = iris.analysis.calculus.spatial_vectors_with_phenom_name(u, v) - self.assertEqual(r, (("eastward", "northward", "upward"), "foobar")) + assert r == (("eastward", "northward", "upward"), "foobar") r = iris.analysis.calculus.spatial_vectors_with_phenom_name(u, v, w) - self.assertEqual(r, (("eastward", "northward", "upward"), "foobar")) + assert r == (("eastward", "northward", "upward"), "foobar") # Change it to have an inconsistent phenomenon v.rename("northward_foobar2") - self.assertRaises( + pytest.raises( ValueError, iris.analysis.calculus.spatial_vectors_with_phenom_name, u, @@ -837,8 +867,4 @@ def test_rotated_pole(self): v.rename("v_wind") x, y, z = iris.analysis.calculus.curl(u, v) - self.assertEqual(z.coord_system(), u.coord_system()) - - -if __name__ == "__main__": - unittest.main() + assert z.coord_system() == u.coord_system() diff --git a/lib/iris/tests/test_lazy_aggregate_by.py b/lib/iris/tests/test_lazy_aggregate_by.py index 908ed90bf8..16e2799f1d 100644 --- a/lib/iris/tests/test_lazy_aggregate_by.py +++ b/lib/iris/tests/test_lazy_aggregate_by.py @@ -2,18 +2,19 @@ # # 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. -import unittest +import pytest from iris._lazy_data import as_lazy_data from iris.analysis import SUM +from iris.cube import Cube from iris.tests import test_aggregate_by # Simply redo the tests of test_aggregate_by.py with lazy data class TestLazyAggregateBy(test_aggregate_by.TestAggregateBy): - def setUp(self): - super().setUp() - + @pytest.fixture(autouse=True) + def _setup_subclass(self, _setup): + # Requests _setup to ensure this fixture runs AFTER _setup. self.cube_single.data = as_lazy_data(self.cube_single.data) self.cube_multi.data = as_lazy_data(self.cube_multi.data) self.cube_single_masked.data = as_lazy_data(self.cube_single_masked.data) @@ -21,28 +22,46 @@ def setUp(self): self.cube_easy.data = as_lazy_data(self.cube_easy.data) self.cube_easy_weighted.data = as_lazy_data(self.cube_easy_weighted.data) - assert self.cube_single.has_lazy_data() - assert self.cube_multi.has_lazy_data() - assert self.cube_single_masked.has_lazy_data() - assert self.cube_multi_masked.has_lazy_data() - assert self.cube_easy.has_lazy_data() - assert self.cube_easy_weighted.has_lazy_data() - - def tearDown(self): - super().tearDown() + @pytest.fixture(autouse=True) + def _lazy_checks(self, _setup_subclass): + # Requests _setup_subclass to ensure this fixture runs AFTER _setup_subclass. + # TODO: ASSERTS IN FIXTURES ARE AN ANTIPATTERN, find an alternative. + # https://github.com/m-burst/flake8-pytest-style/issues/31 + # (have given this a few hours without success, something to revisit). + def _checker(cubes: list[Cube]): + for cube in cubes: + assert cube.has_lazy_data() + + _checker( + [ + self.cube_single, + self.cube_multi, + self.cube_single_masked, + self.cube_multi_masked, + self.cube_easy, + self.cube_easy_weighted, + ] + ) - # Note: weighted easy cube is not expected to have lazy data since - # WPERCENTILE is not lazy. - assert self.cube_single.has_lazy_data() - assert self.cube_multi.has_lazy_data() - assert self.cube_single_masked.has_lazy_data() - assert self.cube_multi_masked.has_lazy_data() - assert self.cube_easy.has_lazy_data() + yield + + _checker( + [ + self.cube_single, + self.cube_multi, + self.cube_single_masked, + self.cube_multi_masked, + self.cube_easy, + # Note: weighted easy cube is not expected to have lazy data since + # WPERCENTILE is not lazy. + ] + ) class TestLazyAggregateByWeightedByCube(TestLazyAggregateBy): - def setUp(self): - super().setUp() + @pytest.fixture(autouse=True) + def _setup_sub2(self, _setup_subclass): + # Requests _setup_subclass to ensure this fixture runs AFTER _setup_subclass. self.weights_single = self.cube_single[:, 0, 0].copy(self.weights_single) self.weights_single.units = "m2" @@ -55,7 +74,7 @@ def test_str_aggregation_weighted_sum_single(self): SUM, weights=self.weights_single, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" def test_coord_aggregation_weighted_sum_single(self): aggregateby_cube = self.cube_single.aggregated_by( @@ -63,7 +82,7 @@ def test_coord_aggregation_weighted_sum_single(self): SUM, weights=self.weights_single, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" def test_str_aggregation_weighted_sum_multi(self): aggregateby_cube = self.cube_multi.aggregated_by( @@ -71,7 +90,7 @@ def test_str_aggregation_weighted_sum_multi(self): SUM, weights=self.weights_multi, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" def test_str_aggregation_rev_order_weighted_sum_multi(self): aggregateby_cube = self.cube_multi.aggregated_by( @@ -79,7 +98,7 @@ def test_str_aggregation_rev_order_weighted_sum_multi(self): SUM, weights=self.weights_multi, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" def test_coord_aggregation_weighted_sum_multi(self): aggregateby_cube = self.cube_multi.aggregated_by( @@ -87,7 +106,7 @@ def test_coord_aggregation_weighted_sum_multi(self): SUM, weights=self.weights_multi, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") + assert aggregateby_cube.units == "kelvin m2" def test_coord_aggregation_rev_order_weighted_sum_multi(self): aggregateby_cube = self.cube_multi.aggregated_by( @@ -95,8 +114,4 @@ def test_coord_aggregation_rev_order_weighted_sum_multi(self): SUM, weights=self.weights_multi, ) - self.assertEqual(aggregateby_cube.units, "kelvin m2") - - -if __name__ == "__main__": - unittest.main() + assert aggregateby_cube.units == "kelvin m2" diff --git a/lib/iris/tests/test_mapping.py b/lib/iris/tests/test_mapping.py index 4f59bf8d31..2c3c2fc0a0 100644 --- a/lib/iris/tests/test_mapping.py +++ b/lib/iris/tests/test_mapping.py @@ -4,21 +4,18 @@ # See LICENSE in the root of the repository for full licensing details. """Tests map creation.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import cartopy.crs as ccrs import numpy as np -import numpy.testing as np_testing +import pytest import iris import iris.coord_systems import iris.cube +from iris.tests import _shared_utils import iris.tests.stock # Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import matplotlib.pyplot as plt import iris.plot as iplt @@ -30,11 +27,11 @@ ) -@tests.skip_plot -@tests.skip_data -class TestBasic(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_plot +@_shared_utils.skip_data +class TestBasic(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): self.cube = iris.tests.stock.realistic_4d() def test_contourf(self): @@ -54,23 +51,22 @@ def test_unmappable(self): self.check_graphic() def test_default_projection_and_extent(self): - self.assertEqual( - iplt.default_projection(self.cube), - ccrs.RotatedPole(357.5 - 180, 37.5, globe=_DEFAULT_GLOBE), + assert iplt.default_projection(self.cube) == ccrs.RotatedPole( + 357.5 - 180, 37.5, globe=_DEFAULT_GLOBE ) - np_testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( iplt.default_projection_extent(self.cube), (3.59579163e02, 3.59669159e02, -1.28250003e-01, -3.82499993e-02), decimal=3, ) -@tests.skip_data -@tests.skip_plot -class TestUnmappable(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestUnmappable(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): src_cube = iris.tests.stock.global_pp() # Make a cube that can't be located on the globe. @@ -96,12 +92,14 @@ def test_simple(self): self.check_graphic() -@tests.skip_data -@tests.skip_plot -class TestMappingSubRegion(tests.GraphicsTest): - def setUp(self): - super().setUp() - cube_path = tests.get_data_path(("PP", "aPProt1", "rotatedMHtimecube.pp")) +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestMappingSubRegion(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): + cube_path = _shared_utils.get_data_path( + ("PP", "aPProt1", "rotatedMHtimecube.pp") + ) cube = iris.load_cube(cube_path)[0] # make the data smaller to speed things up. self.cube = cube[::10, ::10] @@ -135,22 +133,21 @@ def test_simple(self): self.check_graphic() def test_default_projection_and_extent(self): - self.assertEqual( - iplt.default_projection(self.cube), - ccrs.RotatedPole(357.5 - 180, 37.5, globe=_DEFAULT_GLOBE), + assert iplt.default_projection(self.cube) == ccrs.RotatedPole( + 357.5 - 180, 37.5, globe=_DEFAULT_GLOBE ) - np_testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( iplt.default_projection_extent(self.cube), (313.01998901, 391.11999512, -22.48999977, 24.80999947), ) -@tests.skip_data -@tests.skip_plot -class TestLowLevel(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestLowLevel(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): self.cube = iris.tests.stock.global_pp() self.few = 4 self.few_levels = list(range(280, 300, 5)) @@ -178,11 +175,11 @@ def test_keywords(self): self.check_graphic() -@tests.skip_data -@tests.skip_plot -class TestBoundedCube(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestBoundedCube(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): self.cube = iris.tests.stock.global_pp() # Add some bounds to this data (this will actually make the bounds # invalid as they will straddle the north pole and overlap on the @@ -202,28 +199,25 @@ def test_grid(self): self.check_graphic() def test_default_projection_and_extent(self): - self.assertEqual( - iplt.default_projection(self.cube), - ccrs.PlateCarree( - globe=self.cube.coord_system("CoordSystem").as_cartopy_globe() - ), + assert iplt.default_projection(self.cube) == ccrs.PlateCarree( + globe=self.cube.coord_system("CoordSystem").as_cartopy_globe() ) - np_testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( iplt.default_projection_extent(self.cube), [0.0, 360.0, -89.99995422, 89.99998474], ) - np_testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( iplt.default_projection_extent(self.cube, mode=iris.coords.BOUND_MODE), [-1.875046, 358.124954, -90, 90], ) -@tests.skip_data -@tests.skip_plot -class TestLimitedAreaCube(tests.GraphicsTest): - def setUp(self): - super().setUp() - cube_path = tests.get_data_path(("PP", "aPProt1", "rotated.pp")) +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestLimitedAreaCube(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): + cube_path = _shared_utils.get_data_path(("PP", "aPProt1", "rotated.pp")) self.cube = iris.load_cube(cube_path)[::20, ::20] self.cube.coord("grid_latitude").guess_bounds() self.cube.coord("grid_longitude").guess_bounds() @@ -240,7 +234,3 @@ def test_scatter(self): iplt.points(self.cube) plt.gca().coastlines("110m") self.check_graphic() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/test_plot.py b/lib/iris/tests/test_plot.py index b263313b90..f68a9cf32a 100644 --- a/lib/iris/tests/test_plot.py +++ b/lib/iris/tests/test_plot.py @@ -3,24 +3,20 @@ # 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. -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from functools import wraps -import types -import warnings +from contextlib import nullcontext import cf_units import numpy as np +import pytest import iris import iris.analysis import iris.coords as coords +from iris.tests import _shared_utils import iris.tests.stock # Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import matplotlib.pyplot as plt import iris.plot as iplt @@ -28,7 +24,7 @@ import iris.symbols -@tests.skip_data +@_shared_utils.skip_data def simple_cube(): cube = iris.tests.stock.realistic_4d() cube = cube[:, 0, 0, :] @@ -36,8 +32,8 @@ def simple_cube(): return cube -@tests.skip_plot -class TestSimple(tests.GraphicsTest): +@_shared_utils.skip_plot +class TestSimple(_shared_utils.GraphicsTest): def test_points(self): cube = simple_cube() qplt.contourf(cube) @@ -49,8 +45,8 @@ def test_bounds(self): self.check_graphic() -@tests.skip_plot -class TestMissingCoord(tests.GraphicsTest): +@_shared_utils.skip_plot +class TestMissingCoord(_shared_utils.GraphicsTest): def _check(self, cube): qplt.contourf(cube) self.check_graphic() @@ -75,12 +71,12 @@ def test_none(self): self._check(cube) -@tests.skip_data -@tests.skip_plot -class TestMissingCS(tests.GraphicsTest): - @tests.skip_data +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestMissingCS(_shared_utils.GraphicsTest): + @_shared_utils.skip_data def test_missing_cs(self): - cube = tests.stock.simple_pp() + cube = iris.tests.stock.simple_pp() cube.coord("latitude").coord_system = None cube.coord("longitude").coord_system = None qplt.contourf(cube) @@ -88,11 +84,11 @@ def test_missing_cs(self): self.check_graphic() -@tests.skip_plot -@tests.skip_data -class TestHybridHeight(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_plot +@_shared_utils.skip_data +class TestHybridHeight(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): self.cube = iris.tests.stock.realistic_4d()[0, :15, 0, :] def _check(self, plt_method, test_altitude=True): @@ -131,23 +127,25 @@ def test_orography(self): self.check_graphic() # TODO: Test bounds once they are supported. - with self.assertRaises(NotImplementedError): - qplt.pcolor(self.cube) + qplt.pcolor(self.cube) + with pytest.raises(NotImplementedError): iplt.orography_at_bounds(self.cube) - iplt.outline(self.cube) - self.check_graphic() + # iplt.outline(self.cube) + # self.check_graphic() -@tests.skip_plot -@tests.skip_data -class Test1dPlotMultiArgs(tests.GraphicsTest): +@_shared_utils.skip_plot +@_shared_utils.skip_data +class Test1dPlotMultiArgs(_shared_utils.GraphicsTest): # tests for iris.plot using multi-argument calling convention - - def setUp(self): - super().setUp() - self.cube1d = _load_4d_testcube()[0, :, 0, 0] + @pytest.fixture(autouse=True) + def _set_draw_method(self): self.draw_method = iplt.plot + @pytest.fixture(autouse=True) + def _setup(self, load_4d_testcube): + self.cube1d = load_4d_testcube[0, :, 0, 0] + def test_cube(self): # just plot a cube against its dim coord self.draw_method(self.cube1d) # altitude vs temp @@ -204,50 +202,51 @@ def test_cube_cube(self): def test_incompatible_objects(self): # incompatible objects (not the same length) should raise an error - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="are not compatible"): self.draw_method(self.cube1d.coord("time"), (self.cube1d)) - def test_multimidmensional(self): + def test_multimidmensional(self, load_4d_testcube): # multidimensional cubes are not allowed - cube = _load_4d_testcube()[0, :, :, 0] - with self.assertRaises(ValueError): + cube = load_4d_testcube[0, :, :, 0] + with pytest.raises(ValueError, match="must be 1-dimensional"): self.draw_method(cube) def test_not_cube_or_coord(self): # inputs must be cubes or coordinates, otherwise an error should be # raised xdim = np.arange(self.cube1d.shape[0]) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.draw_method(xdim, self.cube1d) def test_plot_old_coords_kwarg(self): # Coords used to be a valid kwarg to plot, but it was deprecated and # we are maintaining a reasonable exception, check that it is raised # here. - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.draw_method(self.cube1d, coords=None) -@tests.skip_plot +@_shared_utils.skip_plot class Test1dQuickplotPlotMultiArgs(Test1dPlotMultiArgs): # tests for iris.plot using multi-argument calling convention - - def setUp(self): - tests.GraphicsTest.setUp(self) - self.cube1d = _load_4d_testcube()[0, :, 0, 0] + @pytest.fixture(autouse=True) + def _set_draw_method(self): self.draw_method = qplt.plot -@tests.skip_data -@tests.skip_plot -class Test1dScatter(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_data +@_shared_utils.skip_plot +class Test1dScatter(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _set_draw_method(self): + self.draw_method = iplt.scatter + + @pytest.fixture(autouse=True) + def _setup(self): self.cube = iris.load_cube( - tests.get_data_path(("NAME", "NAMEIII_trajectory.txt")), + _shared_utils.get_data_path(("NAME", "NAMEIII_trajectory.txt")), "Temperature", ) - self.draw_method = iplt.scatter def test_coord_coord(self): x = self.cube.coord("longitude") @@ -280,7 +279,7 @@ def test_cube_coord(self): def test_cube_cube(self): x = iris.load_cube( - tests.get_data_path(("NAME", "NAMEIII_trajectory.txt")), + _shared_utils.get_data_path(("NAME", "NAMEIII_trajectory.txt")), "Rel Humidity", ) y = self.cube @@ -292,42 +291,38 @@ def test_incompatible_objects(self): # cubes/coordinates of different sizes cannot be plotted x = self.cube y = self.cube.coord("altitude")[:-1] - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="are not compatible"): self.draw_method(x, y) - def test_multidimensional(self): + def test_multidimensional(self, load_4d_testcube): # multidimensional cubes/coordinates are not allowed - x = _load_4d_testcube()[0, :, :, 0] + x = load_4d_testcube[0, :, :, 0] y = x.coord("model_level_number") - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="must be 1-dimensional"): self.draw_method(x, y) def test_not_cube_or_coord(self): # inputs must be cubes or coordinates x = np.arange(self.cube.shape[0]) y = self.cube - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.draw_method(x, y) -@tests.skip_data -@tests.skip_plot +@_shared_utils.skip_data +@_shared_utils.skip_plot class Test1dQuickplotScatter(Test1dScatter): - def setUp(self): - tests.GraphicsTest.setUp(self) - self.cube = iris.load_cube( - tests.get_data_path(("NAME", "NAMEIII_trajectory.txt")), - "Temperature", - ) + @pytest.fixture(autouse=True) + def _set_draw_method(self): self.draw_method = qplt.scatter -@tests.skip_data -@tests.skip_plot -class Test2dPoints(tests.GraphicsTest): - def setUp(self): - super().setUp() - pp_file = tests.get_data_path(("PP", "globClim1", "u_wind.pp")) +@_shared_utils.skip_data +@_shared_utils.skip_plot +class Test2dPoints(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): + pp_file = _shared_utils.get_data_path(("PP", "globClim1", "u_wind.pp")) self.cube = iris.load(pp_file)[0][0] def test_circular_changes(self): @@ -339,16 +334,19 @@ def test_circular_changes(self): self.check_graphic() -@tests.skip_data -@tests.skip_plot -class Test1dFillBetween(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_data +@_shared_utils.skip_plot +class Test1dFillBetween(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _set_draw_method(self): + self.draw_method = iplt.fill_between + + @pytest.fixture(autouse=True) + def _setup(self): self.cube = iris.load_cube( - tests.get_data_path(("NetCDF", "testing", "small_theta_colpex.nc")), + _shared_utils.get_data_path(("NetCDF", "testing", "small_theta_colpex.nc")), "air_potential_temperature", )[0, 0] - self.draw_method = iplt.fill_between def test_coord_coord(self): x = self.cube.coord("grid_latitude") @@ -383,7 +381,7 @@ def test_incompatible_objects_x_odd(self): x = self.cube.coord("grid_latitude")[:-1] y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN) y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX) - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="are not all compatible"): self.draw_method(x, y1, y2) def test_incompatible_objects_y1_odd(self): @@ -391,7 +389,7 @@ def test_incompatible_objects_y1_odd(self): x = self.cube.coord("grid_latitude") y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN)[:-1] y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX) - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="are not all compatible"): self.draw_method(x, y1, y2) def test_incompatible_objects_y2_odd(self): @@ -399,7 +397,7 @@ def test_incompatible_objects_y2_odd(self): x = self.cube.coord("grid_latitude") y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN) y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX)[:-1] - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="are not all compatible"): self.draw_method(x, y1, y2) def test_incompatible_objects_all_odd(self): @@ -407,7 +405,7 @@ def test_incompatible_objects_all_odd(self): x = self.cube.coord("grid_latitude") y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN)[:-1] y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX)[:-2] - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="are not all compatible"): self.draw_method(x, y1, y2) def test_multidimensional(self): @@ -415,7 +413,7 @@ def test_multidimensional(self): x = self.cube.coord("grid_latitude") y1 = self.cube y2 = self.cube - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="must be 1-dimensional"): self.draw_method(x, y1, y2) def test_not_cube_or_coord(self): @@ -423,64 +421,55 @@ def test_not_cube_or_coord(self): x = np.arange(self.cube.shape[0]) y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN) y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.draw_method(x, y1, y2) -@tests.skip_data -@tests.skip_plot +@_shared_utils.skip_data +@_shared_utils.skip_plot class Test1dQuickplotFillBetween(Test1dFillBetween): - def setUp(self): - tests.GraphicsTest.setUp(self) - self.cube = iris.load_cube( - tests.get_data_path(("NetCDF", "testing", "small_theta_colpex.nc")), - "air_potential_temperature", - )[0, 0] + @pytest.fixture(autouse=True) + def _set_draw_method(self): self.draw_method = qplt.fill_between -@tests.skip_data -@tests.skip_plot -class TestAttributePositive(tests.GraphicsTest): +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestAttributePositive(_shared_utils.GraphicsTest): def test_1d_positive_up(self): - path = tests.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) + path = _shared_utils.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) cube = iris.load_cube(path) qplt.plot(cube.coord("depth"), cube[0, :, 60, 80]) self.check_graphic() def test_1d_positive_down(self): - path = tests.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) + path = _shared_utils.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) cube = iris.load_cube(path) qplt.plot(cube[0, :, 60, 80], cube.coord("depth")) self.check_graphic() def test_2d_positive_up(self): - path = tests.get_data_path(("NetCDF", "testing", "small_theta_colpex.nc")) + path = _shared_utils.get_data_path( + ("NetCDF", "testing", "small_theta_colpex.nc") + ) cube = iris.load_cube(path, "air_potential_temperature")[0, :, 42, :] qplt.pcolormesh(cube) self.check_graphic() def test_2d_positive_down(self): - path = tests.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) + path = _shared_utils.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) cube = iris.load_cube(path)[0, :, 42, :] qplt.pcolormesh(cube) self.check_graphic() -# Caches _load_4d_testcube so subsequent calls are faster -def cache(fn, cache={}): - def inner(*args, **kwargs): - key = fn.__name__ - if key not in cache: - cache[key] = fn(*args, **kwargs) - return cache[key] - - return inner +@_shared_utils.skip_data +@pytest.fixture(scope="module") +def load_4d_testcube(): + """Load the realistic_4d() cube with specific modifications. - -@cache -@tests.skip_data -def _load_4d_testcube(): + Scoped to only load once - used many times so this is much faster. + """ # Load example 4d data (TZYX). test_cube = iris.tests.stock.realistic_4d() # Replace forecast_period coord with a multi-valued version. @@ -507,10 +496,15 @@ def _load_4d_testcube(): return test_cube -@cache -def _load_wind_no_bounds(): +@_shared_utils.skip_data +@pytest.fixture(scope="module") +def load_wind_no_bounds(): + """Load a cube representing wind data but with no coordinate bounds. + + Scoped to only load once - used many times so this is much faster. + """ # Load the COLPEX data => TZYX - path = tests.get_data_path(("PP", "COLPEX", "small_eastward_wind.pp")) + path = _shared_utils.get_data_path(("PP", "COLPEX", "small_eastward_wind.pp")) wind = iris.load_cube(path, "x_wind") # Remove bounds from all coords that have them. @@ -538,7 +532,7 @@ def _date_series(src_cube): return cube -@tests.skip_plot +@_shared_utils.skip_plot class SliceMixin: """Mixin class providing tests for each 2-dimensional permutation of axes. @@ -546,184 +540,123 @@ class SliceMixin: and self.results to be a dictionary containing the desired test results. """ + @pytest.fixture(autouse=True) + def _set_warnings_stance(self): + # Defining in a fixture enables inheritance by classes that expect a + # warning - setting self.warning_checker to the pytest.warns() context + # manager instead. + self.warning_checker = nullcontext + def test_yx(self): cube = self.wind[0, 0, :, :] - self.draw_method(cube) + with self.warning_checker(UserWarning): + self.draw_method(cube) self.check_graphic() def test_zx(self): cube = self.wind[0, :, 0, :] - self.draw_method(cube) + with self.warning_checker(UserWarning): + self.draw_method(cube) self.check_graphic() def test_tx(self): cube = _time_series(self.wind[:, 0, 0, :]) - self.draw_method(cube) + with self.warning_checker(UserWarning): + self.draw_method(cube) self.check_graphic() def test_zy(self): cube = self.wind[0, :, :, 0] - self.draw_method(cube) + with self.warning_checker(UserWarning): + self.draw_method(cube) self.check_graphic() def test_ty(self): cube = _time_series(self.wind[:, 0, :, 0]) - self.draw_method(cube) + with self.warning_checker(UserWarning): + self.draw_method(cube) self.check_graphic() def test_tz(self): cube = _time_series(self.wind[:, :, 0, 0]) - self.draw_method(cube) + with self.warning_checker(UserWarning): + self.draw_method(cube) self.check_graphic() -@tests.skip_data -class TestContour(tests.GraphicsTest, SliceMixin): +@_shared_utils.skip_data +class TestContour(_shared_utils.GraphicsTest, SliceMixin): """Test the iris.plot.contour routine.""" - def setUp(self): - super().setUp() - self.wind = _load_4d_testcube() + @pytest.fixture(autouse=True) + def _setup(self, load_4d_testcube): + self.wind = load_4d_testcube self.draw_method = iplt.contour -@tests.skip_data -class TestContourf(tests.GraphicsTest, SliceMixin): +@_shared_utils.skip_data +class TestContourf(_shared_utils.GraphicsTest, SliceMixin): """Test the iris.plot.contourf routine.""" - def setUp(self): - super().setUp() - self.wind = _load_4d_testcube() + @pytest.fixture(autouse=True) + def _setup(self, load_4d_testcube): + self.wind = load_4d_testcube self.draw_method = iplt.contourf -@tests.skip_data -class TestPcolor(tests.GraphicsTest, SliceMixin): +@_shared_utils.skip_data +class TestPcolor(_shared_utils.GraphicsTest, SliceMixin): """Test the iris.plot.pcolor routine.""" - def setUp(self): - super().setUp() - self.wind = _load_4d_testcube() + @pytest.fixture(autouse=True) + def _setup(self, load_4d_testcube): + self.wind = load_4d_testcube self.draw_method = iplt.pcolor -@tests.skip_data -class TestPcolormesh(tests.GraphicsTest, SliceMixin): +@_shared_utils.skip_data +class TestPcolormesh(_shared_utils.GraphicsTest, SliceMixin): """Test the iris.plot.pcolormesh routine.""" - def setUp(self): - super().setUp() - self.wind = _load_4d_testcube() + @pytest.fixture(autouse=True) + def _setup(self, load_4d_testcube): + self.wind = load_4d_testcube self.draw_method = iplt.pcolormesh -def check_warnings(method): - """Decorator that adds a catch_warnings and filter to assert - the method being decorated issues a UserWarning. - - """ - - @wraps(method) - def decorated_method(self, *args, **kwargs): - # Force reset of iris.coords warnings registry to avoid suppression of - # repeated warnings. warnings.resetwarnings() does not do this. - if hasattr(coords, "__warningregistry__"): - coords.__warningregistry__.clear() - - # Check that method raises warning. - with warnings.catch_warnings(): - warnings.simplefilter("error") - with self.assertRaises(UserWarning): - return method(self, *args, **kwargs) - - return decorated_method - +class SliceWarningsMixin(SliceMixin): + @pytest.fixture(autouse=True) + def _set_warnings_stance(self): + self.warning_checker = pytest.warns -def ignore_warnings(method): - """Decorator that adds a catch_warnings and filter to suppress - any warnings issues by the method being decorated. - - """ - - @wraps(method) - def decorated_method(self, *args, **kwargs): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - return method(self, *args, **kwargs) - - return decorated_method - - -class CheckForWarningsMetaclass(type): - """Metaclass that adds a further test for each base class test - that checks that each test raises a UserWarning. Each base - class test is then overridden to ignore warnings in order to - check the underlying functionality. - - """ - def __new__(cls, name, bases, local): - def add_decorated_methods(attr_dict, target_dict, decorator): - for key, value in attr_dict.items(): - if isinstance(value, types.FunctionType) and key.startswith("test"): - new_key = "_".join((key, decorator.__name__)) - if new_key not in target_dict: - wrapped = decorator(value) - wrapped.__name__ = new_key - target_dict[new_key] = wrapped - else: - raise RuntimeError( - "A attribute called {!r} already exists.".format(new_key) - ) - - def override_with_decorated_methods(attr_dict, target_dict, decorator): - for key, value in attr_dict.items(): - if isinstance(value, types.FunctionType) and key.startswith("test"): - target_dict[key] = decorator(value) - - # Add decorated versions of base methods - # to check for warnings. - for base in bases: - add_decorated_methods(base.__dict__, local, check_warnings) - - # Override base methods to ignore warnings. - for base in bases: - override_with_decorated_methods(base.__dict__, local, ignore_warnings) - - return type.__new__(cls, name, bases, local) - - -@tests.skip_data -class TestPcolorNoBounds( - tests.GraphicsTest, SliceMixin, metaclass=CheckForWarningsMetaclass -): +@_shared_utils.skip_data +class TestPcolorNoBounds(_shared_utils.GraphicsTest, SliceWarningsMixin): """Test the iris.plot.pcolor routine on a cube with coordinates that have no bounds. """ - def setUp(self): - super().setUp() - self.wind = _load_wind_no_bounds() + @pytest.fixture(autouse=True) + def _setup(self, load_wind_no_bounds): + self.wind = load_wind_no_bounds self.draw_method = iplt.pcolor -@tests.skip_data -class TestPcolormeshNoBounds( - tests.GraphicsTest, SliceMixin, metaclass=CheckForWarningsMetaclass -): +@_shared_utils.skip_data +class TestPcolormeshNoBounds(_shared_utils.GraphicsTest, SliceWarningsMixin): """Test the iris.plot.pcolormesh routine on a cube with coordinates that have no bounds. """ - def setUp(self): - super().setUp() - self.wind = _load_wind_no_bounds() + @pytest.fixture(autouse=True) + def _setup(self, load_wind_no_bounds): + self.wind = load_wind_no_bounds self.draw_method = iplt.pcolormesh -@tests.skip_plot +@_shared_utils.skip_plot class Slice1dMixin: """Mixin class providing tests for each 1-dimensional permutation of axes. @@ -760,46 +693,26 @@ def test_t_dates(self): self.check_graphic() -@tests.skip_data -class TestPlot(tests.GraphicsTest, Slice1dMixin): +@_shared_utils.skip_data +class TestPlot(_shared_utils.GraphicsTest, Slice1dMixin): """Test the iris.plot.plot routine.""" - def setUp(self): - super().setUp() - self.wind = _load_4d_testcube() + @pytest.fixture(autouse=True) + def _setup(self, load_4d_testcube): + self.wind = load_4d_testcube self.draw_method = iplt.plot -@tests.skip_data -class TestQuickplotPlot(tests.GraphicsTest, Slice1dMixin): +@_shared_utils.skip_data +class TestQuickplotPlot(_shared_utils.GraphicsTest, Slice1dMixin): """Test the iris.quickplot.plot routine.""" - def setUp(self): - super().setUp() - self.wind = _load_4d_testcube() + @pytest.fixture(autouse=True) + def _setup(self, load_4d_testcube): + self.wind = load_4d_testcube self.draw_method = qplt.plot -_load_cube_once_cache: dict[tuple[str, str], iris.cube.Cube] = {} - - -def load_cube_once(filename, constraint): - """Same syntax as load_cube, but will only load a file once. - - Then cache the answer in a dictionary. - - """ - global _load_cube_once_cache - key = (filename, str(constraint)) - cube = _load_cube_once_cache.get(key, None) - - if cube is None: - cube = iris.load_cube(filename, constraint) - _load_cube_once_cache[key] = cube - - return cube - - class LambdaStr: """Provides a callable function which has a sensible __repr__.""" @@ -814,17 +727,23 @@ def __repr__(self): return self.repr -@tests.skip_data -@tests.skip_plot -class TestPlotCoordinatesGiven(tests.GraphicsTest): - def setUp(self): - super().setUp() - filename = tests.get_data_path(("PP", "COLPEX", "theta_and_orog_subset.pp")) - self.cube = load_cube_once(filename, "air_potential_temperature") - if self.cube.coord_dims("time") != (0,): +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestPlotCoordinatesGiven(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True, scope="class") + def _get_cube(self): + # Class-scoped to avoid wastefully reloading the same Cube repeatedly. + filename = _shared_utils.get_data_path( + ("PP", "COLPEX", "theta_and_orog_subset.pp") + ) + cube = iris.load_cube(filename, "air_potential_temperature") + if cube.coord_dims("time") != (0,): # A quick fix for data which has changed since we support time-varying orography - self.cube.transpose((1, 0, 2, 3)) + cube.transpose((1, 0, 2, 3)) + self.__class__.cube = cube + @pytest.fixture(autouse=True) + def _setup(self): self.draw_module = iris.plot self.contourf = LambdaStr( "iris.plot.contourf", @@ -926,26 +845,35 @@ def test_y(self): def test_badcoords(self): cube = self.cube[0, 0, :, :] draw_fn = getattr(self.draw_module, "contourf") - self.assertRaises( + pytest.raises( ValueError, draw_fn, cube, coords=["grid_longitude", "grid_longitude"], + match="don't span the 2 data dimensions", ) - self.assertRaises( + pytest.raises( ValueError, draw_fn, cube, coords=["grid_longitude", "grid_longitude", "grid_latitude"], + match="should have the same length", ) - self.assertRaises( + pytest.raises( iris.exceptions.CoordinateNotFoundError, draw_fn, cube, coords=["grid_longitude", "wibble"], + match="but found none", + ) + pytest.raises( + ValueError, + draw_fn, + cube, + coords=[], + match="should have the same length", ) - self.assertRaises(ValueError, draw_fn, cube, coords=[]) - self.assertRaises( + pytest.raises( ValueError, draw_fn, cube, @@ -953,8 +881,9 @@ def test_badcoords(self): cube.coord("grid_longitude"), cube.coord("grid_longitude"), ], + match="don't span the 2 data dimensions", ) - self.assertRaises( + pytest.raises( ValueError, draw_fn, cube, @@ -963,6 +892,7 @@ def test_badcoords(self): cube.coord("grid_longitude"), cube.coord("grid_longitude"), ], + match="should have the same length", ) def test_non_cube_coordinate(self): @@ -977,21 +907,21 @@ def test_non_cube_coordinate(self): self.draw("contourf", cube, coords=["grid_latitude", x]) -@tests.skip_data -@tests.skip_plot -class TestPlotHist(tests.GraphicsTest): +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestPlotHist(_shared_utils.GraphicsTest): def test_cube(self): cube = simple_cube()[0] iplt.hist(cube, bins=np.linspace(287.7, 288.2, 11)) self.check_graphic() -@tests.skip_data -@tests.skip_plot -class TestPlotDimAndAuxCoordsKwarg(tests.GraphicsTest): - def setUp(self): - super().setUp() - filename = tests.get_data_path( +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestPlotDimAndAuxCoordsKwarg(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): + filename = _shared_utils.get_data_path( ("NetCDF", "rotated", "xy", "rotPole_landAreaFraction.nc") ) self.cube = iris.load_cube(filename) @@ -1033,8 +963,8 @@ def test_yx_order(self): self.check_graphic() -@tests.skip_plot -class TestSymbols(tests.GraphicsTest): +@_shared_utils.skip_plot +class TestSymbols(_shared_utils.GraphicsTest): def test_cloud_cover(self): iplt.symbols( list(range(10)), @@ -1046,10 +976,11 @@ def test_cloud_cover(self): self.check_graphic() -@tests.skip_plot -class TestPlottingExceptions(tests.IrisTest): - def setUp(self): - self.bounded_cube = tests.stock.lat_lon_cube() +@_shared_utils.skip_plot +class TestPlottingExceptions: + @pytest.fixture(autouse=True) + def _setup(self): + self.bounded_cube = iris.tests.stock.lat_lon_cube() self.bounded_cube.coord("latitude").guess_bounds() self.bounded_cube.coord("longitude").guess_bounds() @@ -1064,7 +995,7 @@ def test_boundmode_multidim(self): ), [0, 1], ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="Could not get XY grid from bounds"): iplt.pcolormesh(cube, coords=["longitude", "latitude"]) def test_boundmode_4bounds(self): @@ -1077,7 +1008,7 @@ def test_boundmode_4bounds(self): ).transpose() cube.remove_coord("latitude") cube.add_aux_coord(lat, 0) - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="Could not get XY grid from bounds."): iplt.pcolormesh(cube, coords=["longitude", "latitude"]) def test_different_coord_systems(self): @@ -1086,15 +1017,15 @@ def test_different_coord_systems(self): lon = cube.coord("longitude") lat.coord_system = iris.coord_systems.GeogCS(7000000) lon.coord_system = iris.coord_systems.GeogCS(7000001) - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match="must have equal coordinate systems"): iplt.pcolormesh(cube, coords=["longitude", "latitude"]) -@tests.skip_data -@tests.skip_plot -class TestPlotOtherCoordSystems(tests.GraphicsTest): +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestPlotOtherCoordSystems(_shared_utils.GraphicsTest): def test_plot_tmerc(self): - filename = tests.get_data_path( + filename = _shared_utils.get_data_path( ("NetCDF", "transverse_mercator", "tmean_1910_1910.nc") ) self.cube = iris.load_cube(filename) @@ -1103,10 +1034,10 @@ def test_plot_tmerc(self): self.check_graphic() -@tests.skip_plot -class TestPlotCitation(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_plot +class TestPlotCitation(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): self.figure = plt.figure() self.axes = self.figure.gca() self.text = ( @@ -1126,7 +1057,3 @@ def test_figure(self): def test_axes(self): iplt.citation(self.text, axes=self.axes) self.check_graphic() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/test_pp_cf.py b/lib/iris/tests/test_pp_cf.py index 8b0af5a5c3..538ce7c385 100644 --- a/lib/iris/tests/test_pp_cf.py +++ b/lib/iris/tests/test_pp_cf.py @@ -3,16 +3,16 @@ # 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. -# import iris tests first so that some things can be initialised before importing anything else -import iris.tests as tests # isort:skip - import os import tempfile +import pytest + import iris import iris.coords from iris.fileformats.netcdf import _thread_safe_nc from iris.fileformats.pp import STASH +from iris.tests import _shared_utils import iris.util @@ -62,10 +62,14 @@ def callback_aaxzc_n10r13xy_b_pp(cube, field, filename): cube.add_aux_coord(height_coord) -@tests.skip_data -class TestAll(tests.IrisTest, tests.PPTest): +@_shared_utils.skip_data +class TestAll: _ref_dir = ("usecases", "pp_to_cf_conversion") + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + def _test_file(self, name): """Main test routine that is called for each of the files listed below.""" pp_path = self._src_pp_path(name) @@ -80,7 +84,9 @@ def _test_file(self, name): else: fname_name = name - self.assertCML(cubes, self._ref_dir + ("from_pp", fname_name + ".cml")) + _shared_utils.assert_CML( + self.request, cubes, self._ref_dir + ("from_pp", fname_name + ".cml") + ) # 2) Save the Cube and check the netCDF nc_filenames = [] @@ -99,7 +105,8 @@ def _test_file(self, name): ) # Check the netCDF file against CDL expected output. - self.assertCDL( + _shared_utils.assert_CDL( + self.request, file_nc, self._ref_dir + ("to_netcdf", "%s_%d.cdl" % (fname_name, index)), ) @@ -109,7 +116,8 @@ def _test_file(self, name): for index, nc_filename in enumerate(nc_filenames): # Read netCDF to Cube. cube = iris.load_cube(nc_filename) - self.assertCML( + _shared_utils.assert_CML( + self.request, cube, self._ref_dir + ("from_netcdf", "%s_%d.cml" % (fname_name, index)), ) @@ -122,15 +130,15 @@ def _test_file(self, name): self._test_pp_save(cubes, name) def _src_pp_path(self, name): - return tests.get_data_path(("PP", "cf_processing", name)) + return _shared_utils.get_data_path(("PP", "cf_processing", name)) def _test_pp_save(self, cubes, name): # If there's no existing reference file then make it from the *source* data - reference_txt_path = tests.get_result_path( + reference_txt_path = _shared_utils.get_result_path( self._ref_dir + ("to_pp", name + ".txt") ) reference_pp_path = self._src_pp_path(name) - with self.cube_save_test( + with _shared_utils.pp_cube_save_test( reference_txt_path, reference_pp_path=reference_pp_path ) as temp_pp_path: iris.save(cubes, temp_pp_path) @@ -187,7 +195,3 @@ def attach_tests(): attach_tests() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/test_pp_module.py b/lib/iris/tests/test_pp_module.py index 3a8e988a4d..72ed851275 100644 --- a/lib/iris/tests/test_pp_module.py +++ b/lib/iris/tests/test_pp_module.py @@ -3,58 +3,54 @@ # 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. -# import iris tests first so that some things can be initialised before importing anything else -import iris.tests as tests # isort:skip - from copy import deepcopy import os from types import GeneratorType -import unittest -from unittest import mock import cftime from numpy.testing import assert_array_equal +import pytest -import iris.fileformats import iris.fileformats.pp as pp -import iris.util +from iris.tests import _shared_utils -@tests.skip_data -class TestPPCopy(tests.IrisTest): - def setUp(self): - self.filename = tests.get_data_path(("PP", "aPPglob1", "global.pp")) +@_shared_utils.skip_data +class TestPPCopy: + @pytest.fixture(autouse=True) + def _setup(self): + self.filename = _shared_utils.get_data_path(("PP", "aPPglob1", "global.pp")) def test_copy_field_deferred(self): field = next(pp.load(self.filename)) clone = field.copy() - self.assertEqual(field, clone) + assert field == clone clone.lbyr = 666 - self.assertNotEqual(field, clone) + assert field != clone def test_deepcopy_field_deferred(self): field = next(pp.load(self.filename)) clone = deepcopy(field) - self.assertEqual(field, clone) + assert field == clone clone.lbyr = 666 - self.assertNotEqual(field, clone) + assert field != clone def test_copy_field_non_deferred(self): field = next(pp.load(self.filename, True)) clone = field.copy() - self.assertEqual(field, clone) + assert field == clone clone.data[0][0] = 666 - self.assertNotEqual(field, clone) + assert field != clone def test_deepcopy_field_non_deferred(self): field = next(pp.load(self.filename, True)) clone = deepcopy(field) - self.assertEqual(field, clone) + assert field == clone clone.data[0][0] = 666 - self.assertNotEqual(field, clone) + assert field != clone -class IrisPPTest(tests.IrisTest): +class IrisPPTest: def check_pp(self, pp_fields, reference_filename): """Checks the given iterable of PPField objects matches the reference file, or creates the reference file if it doesn't exist. @@ -68,11 +64,11 @@ def check_pp(self, pp_fields, reference_filename): pp_field.data test_string = str(pp_fields) - reference_path = tests.get_result_path(reference_filename) + reference_path = _shared_utils.get_result_path(reference_filename) if os.path.isfile(reference_path): with open(reference_path, "r") as reference_fh: reference = "".join(reference_fh.readlines()) - self._assert_str_same( + _shared_utils._assert_str_same( reference + "\n", test_string + "\n", reference_filename, @@ -83,48 +79,49 @@ def check_pp(self, pp_fields, reference_filename): reference_fh.writelines(test_string) -class TestPPHeaderDerived(tests.IrisTest): - def setUp(self): +class TestPPHeaderDerived: + @pytest.fixture(autouse=True) + def _setup(self): self.pp = pp.PPField2() self.pp.lbuser = (0, 1, 2, 3, 4, 5, 6) self.pp.lbtim = 11 self.pp.lbproc = 65539 def test_standard_access(self): - self.assertEqual(self.pp.lbtim, 11) + assert self.pp.lbtim == 11 def test_lbtim_access(self): - self.assertEqual(self.pp.lbtim[0], 1) - self.assertEqual(self.pp.lbtim.ic, 1) + assert self.pp.lbtim[0] == 1 + assert self.pp.lbtim.ic == 1 def test_lbtim_setter(self): self.pp.lbtim[4] = 4 self.pp.lbtim[0] = 4 - self.assertEqual(self.pp.lbtim[0], 4) - self.assertEqual(self.pp.lbtim.ic, 4) + assert self.pp.lbtim[0] == 4 + assert self.pp.lbtim.ic == 4 self.pp.lbtim.ib = 9 - self.assertEqual(self.pp.lbtim.ib, 9) - self.assertEqual(self.pp.lbtim[1], 9) + assert self.pp.lbtim.ib == 9 + assert self.pp.lbtim[1] == 9 def test_set_lbuser(self): self.pp.stash = "m02s12i003" - self.assertEqual(self.pp.stash, pp.STASH(2, 12, 3)) + assert self.pp.stash == pp.STASH(2, 12, 3) self.pp.lbuser[6] = 5 - self.assertEqual(self.pp.stash, pp.STASH(5, 12, 3)) + assert self.pp.stash == pp.STASH(5, 12, 3) self.pp.lbuser[3] = 4321 - self.assertEqual(self.pp.stash, pp.STASH(5, 4, 321)) + assert self.pp.stash == pp.STASH(5, 4, 321) def test_set_stash(self): self.pp.stash = "m02s12i003" - self.assertEqual(self.pp.stash, pp.STASH(2, 12, 3)) + assert self.pp.stash == pp.STASH(2, 12, 3) self.pp.stash = pp.STASH(3, 13, 4) - self.assertEqual(self.pp.stash, pp.STASH(3, 13, 4)) - self.assertEqual(self.pp.lbuser[3], self.pp.stash.lbuser3()) - self.assertEqual(self.pp.lbuser[6], self.pp.stash.lbuser6()) + assert self.pp.stash == pp.STASH(3, 13, 4) + assert self.pp.lbuser[3] == self.pp.stash.lbuser3() + assert self.pp.lbuser[6] == self.pp.stash.lbuser6() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.pp.stash = (4, 15, 5) def test_lbproc_bad_access(self): @@ -133,141 +130,144 @@ def test_lbproc_bad_access(self): except AttributeError: pass except Exception as err: - self.fail("Should return a better error: " + str(err)) + pytest.fail("Should return a better error: " + str(err)) -@tests.skip_data +@_shared_utils.skip_data class TestPPField_GlobalTemperature(IrisPPTest): - def setUp(self): - self.original_pp_filepath = tests.get_data_path(("PP", "aPPglob1", "global.pp")) + @pytest.fixture(autouse=True) + def _setup(self): + self.original_pp_filepath = _shared_utils.get_data_path( + ("PP", "aPPglob1", "global.pp") + ) self.r = list(pp.load(self.original_pp_filepath)) def test_full_file(self): self.check_pp(self.r[0:10], ("PP", "global_test.pp.txt")) def test_lbtim_access(self): - self.assertEqual(self.r[0].lbtim[0], 2) - self.assertEqual(self.r[0].lbtim.ic, 2) + assert self.r[0].lbtim[0] == 2 + assert self.r[0].lbtim.ic == 2 def test_t1_t2_access(self): field = self.r[0] calendar = "360_day" - self.assertEqual( - field.t1.timetuple(), - cftime.datetime(1994, 12, 1, 0, 0, calendar=calendar).timetuple(), + assert ( + field.t1.timetuple() + == cftime.datetime(1994, 12, 1, 0, 0, calendar=calendar).timetuple() ) - def test_save_single(self): - temp_filename = iris.util.create_temp_filename(".pp") + def test_save_single(self, tmp_path): + temp_filename = tmp_path / "foo.pp" with open(temp_filename, "wb") as temp_fh: self.r[0].save(temp_fh) - self.assertEqual( - self.file_checksum(temp_filename), - self.file_checksum(self.original_pp_filepath), - ) - os.remove(temp_filename) + assert _shared_utils.file_checksum( + temp_filename + ) == _shared_utils.file_checksum(self.original_pp_filepath) - def test_save_api(self): + def test_save_api(self, tmp_path): filepath = self.original_pp_filepath f = next(pp.load(filepath)) - temp_filename = iris.util.create_temp_filename(".pp") + temp_filename = tmp_path / "foo.pp" with open(temp_filename, "wb") as temp_fh: f.save(temp_fh) - self.assertEqual( - self.file_checksum(temp_filename), self.file_checksum(filepath) - ) + assert _shared_utils.file_checksum( + temp_filename + ) == _shared_utils.file_checksum(filepath) - os.remove(temp_filename) - -@tests.skip_data +@_shared_utils.skip_data class TestPackedPP(IrisPPTest): - def test_wgdos(self): - filepath = tests.get_data_path( + def test_wgdos(self, mocker, tmp_path): + filepath = _shared_utils.get_data_path( ("PP", "wgdos_packed", "nae.20100104-06_0001.pp") ) r = pp.load(filepath) # Check that the result is a generator and convert to a list so that we # can index and get the first one - self.assertEqual(type(r), GeneratorType) + assert isinstance(r, GeneratorType) r = list(r) self.check_pp(r, ("PP", "nae_unpacked.pp.txt")) # check that trying to save this field again raises an error # (we cannot currently write WGDOS packed fields without mo_pack) - temp_filename = iris.util.create_temp_filename(".pp") - with mock.patch("iris.fileformats.pp.mo_pack", None): - with self.assertRaises(NotImplementedError): - with open(temp_filename, "wb") as temp_fh: - r[0].save(temp_fh) - os.remove(temp_filename) - - @unittest.skipIf(pp.mo_pack is None, "Requires mo_pack.") - def test_wgdos_mo_pack(self): - filepath = tests.get_data_path( + temp_filename = tmp_path / "foo.pp" + mocker.patch("iris.fileformats.pp.mo_pack", None) + with pytest.raises(NotImplementedError): + with open(temp_filename, "wb") as temp_fh: + r[0].save(temp_fh) + + @pytest.mark.skipif(pp.mo_pack is None, reason="Requires mo_pack.") + def test_wgdos_mo_pack(self, tmp_path): + filepath = _shared_utils.get_data_path( ("PP", "wgdos_packed", "nae.20100104-06_0001.pp") ) orig_fields = pp.load(filepath) - with self.temp_filename(".pp") as temp_filename: - with open(temp_filename, "wb") as fh: - for field in orig_fields: - field.save(fh) - saved_fields = pp.load(temp_filename) - for orig_field, saved_field in zip(orig_fields, saved_fields): - assert_array_equal(orig_field.data, saved_field.data) + temp_filename = tmp_path / "foo.pp" + with open(temp_filename, "wb") as fh: + for field in orig_fields: + field.save(fh) + saved_fields = pp.load(temp_filename) + for orig_field, saved_field in zip(orig_fields, saved_fields): + assert_array_equal(orig_field.data, saved_field.data) - def test_rle(self): - r = pp.load(tests.get_data_path(("PP", "ocean_rle", "ocean_rle.pp"))) + def test_rle(self, tmp_path): + r = pp.load(_shared_utils.get_data_path(("PP", "ocean_rle", "ocean_rle.pp"))) # Check that the result is a generator and convert to a list so that we # can index and get the first one - self.assertEqual(type(r), GeneratorType) + assert isinstance(r, GeneratorType) r = list(r) self.check_pp(r, ("PP", "rle_unpacked.pp.txt")) # check that trying to save this field again raises an error # (we cannot currently write RLE packed fields) - with self.temp_filename(".pp") as temp_filename: - with self.assertRaises(NotImplementedError): - with open(temp_filename, "wb") as temp_fh: - r[0].save(temp_fh) + temp_filename = tmp_path / "foo.pp" + with pytest.raises(NotImplementedError): + with open(temp_filename, "wb") as temp_fh: + r[0].save(temp_fh) -@tests.skip_data +@_shared_utils.skip_data class TestPPFile(IrisPPTest): def test_lots_of_extra_data(self): r = pp.load( - tests.get_data_path( + _shared_utils.get_data_path( ("PP", "cf_processing", "HadCM2_ts_SAT_ann_18602100.b.pp") ) ) r = list(r) - self.assertEqual(r[0].lbcode.ix, 13) - self.assertEqual(r[0].lbcode.iy, 23) - self.assertEqual(len(r[0].lbcode), 5) + assert r[0].lbcode.ix == 13 + assert r[0].lbcode.iy == 23 + assert len(r[0].lbcode) == 5 self.check_pp(r, ("PP", "extra_data_time_series.pp.txt")) -@tests.skip_data +@_shared_utils.skip_data class TestPPFileExtraXData(IrisPPTest): - def setUp(self): - self.original_pp_filepath = tests.get_data_path(("PP", "ukV1", "ukVpmslont.pp")) + @pytest.fixture(autouse=True) + def _setup(self): + self.original_pp_filepath = _shared_utils.get_data_path( + ("PP", "ukV1", "ukVpmslont.pp") + ) self.r = list(pp.load(self.original_pp_filepath))[0:5] def test_full_file(self): self.check_pp(self.r, ("PP", "extra_x_data.pp.txt")) - def test_save_single(self): - filepath = tests.get_data_path(("PP", "ukV1", "ukVpmslont_first_field.pp")) + def test_save_single(self, tmp_path): + filepath = _shared_utils.get_data_path( + ("PP", "ukV1", "ukVpmslont_first_field.pp") + ) f = next(pp.load(filepath)) - temp_filename = iris.util.create_temp_filename(".pp") + temp_filename = tmp_path / "foo.pp" with open(temp_filename, "wb") as temp_fh: f.save(temp_fh) @@ -275,36 +275,36 @@ def test_save_single(self): # force the data to be loaded (this was done for f when save was run) s.data - self._assert_str_same( + _shared_utils._assert_str_same( str(s) + "\n", str(f) + "\n", "", type_comparison_name="PP files" ) - self.assertEqual( - self.file_checksum(temp_filename), self.file_checksum(filepath) - ) - os.remove(temp_filename) + assert _shared_utils.file_checksum( + temp_filename + ) == _shared_utils.file_checksum(filepath) -@tests.skip_data +@_shared_utils.skip_data class TestPPFileWithExtraCharacterData(IrisPPTest): - def setUp(self): - self.original_pp_filepath = tests.get_data_path( + @pytest.fixture(autouse=True) + def _setup(self): + self.original_pp_filepath = _shared_utils.get_data_path( ("PP", "globClim1", "dec_subset.pp") ) self.r = pp.load(self.original_pp_filepath) self.r_loaded_data = pp.load(self.original_pp_filepath, read_data=True) # Check that the result is a generator and convert to a list so that we can index and get the first one - self.assertEqual(type(self.r), GeneratorType) + assert isinstance(self.r, GeneratorType) self.r = list(self.r) - self.assertEqual(type(self.r_loaded_data), GeneratorType) + assert isinstance(self.r_loaded_data, GeneratorType) self.r_loaded_data = list(self.r_loaded_data) def test_extra_field_title(self): - self.assertEqual( - self.r[0].field_title, - "AJHQA Time mean !C Atmos u compnt of wind after timestep at 9.998 metres !C 01/12/2007 00:00 -> 01/01/2008 00:00", + assert ( + self.r[0].field_title + == "AJHQA Time mean !C Atmos u compnt of wind after timestep at 9.998 metres !C 01/12/2007 00:00 -> 01/01/2008 00:00" ) def test_full_file(self): @@ -314,11 +314,13 @@ def test_full_file(self): ("PP", "extra_char_data.w_data_loaded.pp.txt"), ) - def test_save_single(self): - filepath = tests.get_data_path(("PP", "model_comp", "dec_first_field.pp")) + def test_save_single(self, tmp_path): + filepath = _shared_utils.get_data_path( + ("PP", "model_comp", "dec_first_field.pp") + ) f = next(pp.load(filepath)) - temp_filename = iris.util.create_temp_filename(".pp") + temp_filename = tmp_path / "foo.pp" with open(temp_filename, "wb") as temp_fh: f.save(temp_fh) @@ -326,60 +328,59 @@ def test_save_single(self): # force the data to be loaded (this was done for f when save was run) s.data - self._assert_str_same( + _shared_utils._assert_str_same( str(s) + "\n", str(f) + "\n", "", type_comparison_name="PP files" ) - self.assertEqual( - self.file_checksum(temp_filename), self.file_checksum(filepath) - ) - os.remove(temp_filename) + assert _shared_utils.file_checksum( + temp_filename + ) == _shared_utils.file_checksum(filepath) -class TestSplittableInt(tests.IrisTest): +class TestSplittableInt: def test_3(self): t = pp.SplittableInt(3) - self.assertEqual(t[0], 3) + assert t[0] == 3 def test_grow_str_list(self): t = pp.SplittableInt(3) t[1] = 3 - self.assertEqual(t[1], 3) + assert t[1] == 3 t[5] = 4 - self.assertEqual(t[5], 4) + assert t[5] == 4 - self.assertEqual(int(t), 400033) + assert int(t) == 400033 - self.assertEqual(t, 400033) - self.assertNotEqual(t, 33) + assert t == 400033 + assert t != 33 - self.assertTrue(t >= 400033) - self.assertFalse(t >= 400034) + assert t >= 400033 + assert not t >= 400034 - self.assertTrue(t <= 400033) - self.assertFalse(t <= 400032) + assert t <= 400033 + assert not t <= 400032 - self.assertTrue(t > 400032) - self.assertFalse(t > 400034) + assert t > 400032 + assert not t > 400034 - self.assertTrue(t < 400034) - self.assertFalse(t < 400032) + assert t < 400034 + assert not t < 400032 def test_name_mapping(self): t = pp.SplittableInt(33214, {"ones": 0, "tens": 1, "hundreds": 2}) - self.assertEqual(t.ones, 4) - self.assertEqual(t.tens, 1) - self.assertEqual(t.hundreds, 2) + assert t.ones == 4 + assert t.tens == 1 + assert t.hundreds == 2 t.ones = 9 t.tens = 4 t.hundreds = 0 - self.assertEqual(t.ones, 9) - self.assertEqual(t.tens, 4) - self.assertEqual(t.hundreds, 0) + assert t.ones == 9 + assert t.tens == 4 + assert t.hundreds == 0 def test_name_mapping_multi_index(self): t = pp.SplittableInt( @@ -390,69 +391,66 @@ def test_name_mapping_multi_index(self): "backwards": slice(None, None, -1), }, ) - self.assertEqual(t.weird_number, 324) - self.assertEqual(t.last_few, 13) - self.assertRaises(ValueError, setattr, t, "backwards", 1) - self.assertRaises(ValueError, setattr, t, "last_few", 1) - self.assertEqual(t.backwards, 41233) - self.assertEqual(t, 33214) + assert t.weird_number == 324 + assert t.last_few == 13 + pytest.raises(ValueError, setattr, t, "backwards", 1) + pytest.raises(ValueError, setattr, t, "last_few", 1) + assert t.backwards == 41233 + assert t == 33214 t.weird_number = 99 # notice that this will zero the 5th number - self.assertEqual(t, 3919) + assert t == 3919 t.weird_number = 7899 - self.assertEqual(t, 7083919) + assert t == 7083919 t.foo = 1 t = pp.SplittableInt(33214, {"ix": slice(None, 2), "iy": slice(2, 4)}) - self.assertEqual(t.ix, 14) - self.assertEqual(t.iy, 32) + assert t.ix == 14 + assert t.iy == 32 t.ix = 21 - self.assertEqual(t, 33221) + assert t == 33221 t = pp.SplittableInt(33214, {"ix": slice(-1, 2)}) - self.assertEqual(t.ix, 0) + assert t.ix == 0 t = pp.SplittableInt(4, {"ix": slice(None, 2), "iy": slice(2, 4)}) - self.assertEqual(t.ix, 4) - self.assertEqual(t.iy, 0) + assert t.ix == 4 + assert t.iy == 0 def test_33214(self): t = pp.SplittableInt(33214) - self.assertEqual(t[4], 3) - self.assertEqual(t[3], 3) - self.assertEqual(t[2], 2) - self.assertEqual(t[1], 1) - self.assertEqual(t[0], 4) + assert t[4] == 3 + assert t[3] == 3 + assert t[2] == 2 + assert t[1] == 1 + assert t[0] == 4 # The rest should be zero for i in range(5, 100): - self.assertEqual(t[i], 0) + assert t[i] == 0 def test_negative_number(self): - self.assertRaises(ValueError, pp.SplittableInt, -5) - try: + with pytest.raises( + ValueError, + match="Negative numbers not supported with splittable integers object", + ): _ = pp.SplittableInt(-5) - except ValueError as err: - self.assertEqual( - str(err), - "Negative numbers not supported with splittable integers object", - ) -class TestSplittableIntEquality(tests.IrisTest): +class TestSplittableIntEquality: def test_not_implemented(self): class Terry: pass sin = pp.SplittableInt(0) - self.assertIs(sin.__eq__(Terry()), NotImplemented) - self.assertIs(sin.__ne__(Terry()), NotImplemented) + assert sin.__eq__(Terry()) is NotImplemented + assert sin.__ne__(Terry()) is NotImplemented -class TestPPDataProxyEquality(tests.IrisTest): +class TestPPDataProxyEquality: def test_not_implemented(self): class Terry: pass @@ -467,19 +465,15 @@ class Terry: "beans", "eggs", ) - self.assertIs(pox.__eq__(Terry()), NotImplemented) - self.assertIs(pox.__ne__(Terry()), NotImplemented) + assert pox.__eq__(Terry()) is NotImplemented + assert pox.__ne__(Terry()) is NotImplemented -class TestPPFieldEquality(tests.IrisTest): +class TestPPFieldEquality: def test_not_implemented(self): class Terry: pass pox = pp.PPField3() - self.assertIs(pox.__eq__(Terry()), NotImplemented) - self.assertIs(pox.__ne__(Terry()), NotImplemented) - - -if __name__ == "__main__": - tests.main() + assert pox.__eq__(Terry()) is NotImplemented + assert pox.__ne__(Terry()) is NotImplemented diff --git a/lib/iris/tests/test_pp_stash.py b/lib/iris/tests/test_pp_stash.py index e5b6953bf3..8123336c03 100644 --- a/lib/iris/tests/test_pp_stash.py +++ b/lib/iris/tests/test_pp_stash.py @@ -3,104 +3,98 @@ # 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. -# import iris tests first so that some things can be initialised before importing anything else -import iris.tests as tests # isort:skip +import pytest import iris import iris.fileformats.pp import iris.io +from iris.tests import _shared_utils import iris.tests.stock import iris.util -class TestPPStash(tests.IrisTest): - @tests.skip_data +class TestPPStash: + @_shared_utils.skip_data def test_cube_attributes(self): - cube = tests.stock.simple_pp() - self.assertEqual("m01s16i203", cube.attributes["STASH"]) - self.assertNotEqual("m01s16i999", cube.attributes["STASH"]) - self.assertEqual(cube.attributes["STASH"], "m01s16i203") - self.assertNotEqual(cube.attributes["STASH"], "m01s16i999") - - @tests.skip_data + cube = iris.tests.stock.simple_pp() + assert "m01s16i203" == cube.attributes["STASH"] + assert "m01s16i999" != cube.attributes["STASH"] + # Also exercise iris.fileformats.pp.STASH eq and ne methods. + assert cube.attributes["STASH"] == "m01s16i203" + assert cube.attributes["STASH"] != "m01s16i999" + + @_shared_utils.skip_data def test_ppfield(self): - data_path = tests.get_data_path(("PP", "simple_pp", "global.pp")) + data_path = _shared_utils.get_data_path(("PP", "simple_pp", "global.pp")) pps = iris.fileformats.pp.load(data_path) for pp in pps: - self.assertEqual("m01s16i203", pp.stash) - self.assertNotEqual("m01s16i999", pp.stash) - self.assertEqual(pp.stash, "m01s16i203") - self.assertNotEqual(pp.stash, "m01s16i999") + assert "m01s16i203" == pp.stash + assert "m01s16i999" != pp.stash + # Also exercise iris.fileformats.pp.STASH eq and ne methods. + assert pp.stash == "m01s16i203" + assert pp.stash != "m01s16i999" def test_stash_against_stash(self): - self.assertEqual( - iris.fileformats.pp.STASH(1, 2, 3), - iris.fileformats.pp.STASH(1, 2, 3), - ) - self.assertNotEqual( - iris.fileformats.pp.STASH(1, 2, 3), - iris.fileformats.pp.STASH(2, 3, 4), - ) + assert iris.fileformats.pp.STASH(1, 2, 3) == iris.fileformats.pp.STASH(1, 2, 3) + assert iris.fileformats.pp.STASH(1, 2, 3) != iris.fileformats.pp.STASH(2, 3, 4) def test_stash_against_str(self): - self.assertEqual(iris.fileformats.pp.STASH(1, 2, 3), "m01s02i003") - self.assertEqual("m01s02i003", iris.fileformats.pp.STASH(1, 2, 3)) - self.assertNotEqual(iris.fileformats.pp.STASH(1, 2, 3), "m02s03i004") - self.assertNotEqual("m02s03i004", iris.fileformats.pp.STASH(1, 2, 3)) + # Also exercise iris.fileformats.pp.STASH eq and ne methods. + assert iris.fileformats.pp.STASH(1, 2, 3) == "m01s02i003" + assert "m01s02i003" == iris.fileformats.pp.STASH(1, 2, 3) + assert iris.fileformats.pp.STASH(1, 2, 3) != "m02s03i004" + assert "m02s03i004" != iris.fileformats.pp.STASH(1, 2, 3) def test_irregular_stash_str(self): - self.assertEqual(iris.fileformats.pp.STASH(1, 2, 3), "m01s02i0000000003") - self.assertEqual(iris.fileformats.pp.STASH(1, 2, 3), "m01s02i3") - self.assertEqual(iris.fileformats.pp.STASH(1, 2, 3), "m01s2i3") - self.assertEqual(iris.fileformats.pp.STASH(1, 2, 3), "m1s2i3") - - self.assertEqual("m01s02i0000000003", iris.fileformats.pp.STASH(1, 2, 3)) - self.assertEqual("m01s02i3", iris.fileformats.pp.STASH(1, 2, 3)) - self.assertEqual("m01s2i3", iris.fileformats.pp.STASH(1, 2, 3)) - self.assertEqual("m1s2i3", iris.fileformats.pp.STASH(1, 2, 3)) - - self.assertNotEqual(iris.fileformats.pp.STASH(2, 3, 4), "m01s02i0000000003") - self.assertNotEqual(iris.fileformats.pp.STASH(2, 3, 4), "m01s02i3") - self.assertNotEqual(iris.fileformats.pp.STASH(2, 3, 4), "m01s2i3") - self.assertNotEqual(iris.fileformats.pp.STASH(2, 3, 4), "m1s2i3") - - self.assertNotEqual("m01s02i0000000003", iris.fileformats.pp.STASH(2, 3, 4)) - self.assertNotEqual("m01s02i3", iris.fileformats.pp.STASH(2, 3, 4)) - self.assertNotEqual("m01s2i3", iris.fileformats.pp.STASH(2, 3, 4)) - self.assertNotEqual("m1s2i3", iris.fileformats.pp.STASH(2, 3, 4)) - - self.assertEqual(iris.fileformats.pp.STASH.from_msi("M01s02i003"), "m01s02i003") - self.assertEqual("m01s02i003", iris.fileformats.pp.STASH.from_msi("M01s02i003")) + # Also exercise iris.fileformats.pp.STASH eq and ne methods. + assert iris.fileformats.pp.STASH(1, 2, 3) == "m01s02i0000000003" + assert iris.fileformats.pp.STASH(1, 2, 3) == "m01s02i3" + assert iris.fileformats.pp.STASH(1, 2, 3) == "m01s2i3" + assert iris.fileformats.pp.STASH(1, 2, 3) == "m1s2i3" + + assert "m01s02i0000000003" == iris.fileformats.pp.STASH(1, 2, 3) + assert "m01s02i3" == iris.fileformats.pp.STASH(1, 2, 3) + assert "m01s2i3" == iris.fileformats.pp.STASH(1, 2, 3) + assert "m1s2i3" == iris.fileformats.pp.STASH(1, 2, 3) + + assert iris.fileformats.pp.STASH(2, 3, 4) != "m01s02i0000000003" + assert iris.fileformats.pp.STASH(2, 3, 4) != "m01s02i3" + assert iris.fileformats.pp.STASH(2, 3, 4) != "m01s2i3" + assert iris.fileformats.pp.STASH(2, 3, 4) != "m1s2i3" + + assert "m01s02i0000000003" != iris.fileformats.pp.STASH(2, 3, 4) + assert "m01s02i3" != iris.fileformats.pp.STASH(2, 3, 4) + assert "m01s2i3" != iris.fileformats.pp.STASH(2, 3, 4) + assert "m1s2i3" != iris.fileformats.pp.STASH(2, 3, 4) + + assert iris.fileformats.pp.STASH.from_msi("M01s02i003") == "m01s02i003" + assert "m01s02i003" == iris.fileformats.pp.STASH.from_msi("M01s02i003") def test_illegal_stash_str_range(self): - self.assertEqual(iris.fileformats.pp.STASH(0, 2, 3), "m??s02i003") - self.assertNotEqual(iris.fileformats.pp.STASH(0, 2, 3), "m01s02i003") + # Also exercise iris.fileformats.pp.STASH eq and ne methods. + assert iris.fileformats.pp.STASH(0, 2, 3) == "m??s02i003" + assert iris.fileformats.pp.STASH(0, 2, 3) != "m01s02i003" - self.assertEqual("m??s02i003", iris.fileformats.pp.STASH(0, 2, 3)) - self.assertNotEqual("m01s02i003", iris.fileformats.pp.STASH(0, 2, 3)) + assert "m??s02i003" == iris.fileformats.pp.STASH(0, 2, 3) + assert "m01s02i003" != iris.fileformats.pp.STASH(0, 2, 3) - self.assertEqual(iris.fileformats.pp.STASH(0, 2, 3), "m??s02i003") - self.assertEqual(iris.fileformats.pp.STASH(0, 2, 3), "m00s02i003") - self.assertEqual("m??s02i003", iris.fileformats.pp.STASH(0, 2, 3)) - self.assertEqual("m00s02i003", iris.fileformats.pp.STASH(0, 2, 3)) + assert iris.fileformats.pp.STASH(0, 2, 3) == "m??s02i003" + assert iris.fileformats.pp.STASH(0, 2, 3) == "m00s02i003" + assert "m??s02i003" == iris.fileformats.pp.STASH(0, 2, 3) + assert "m00s02i003" == iris.fileformats.pp.STASH(0, 2, 3) - self.assertEqual(iris.fileformats.pp.STASH(100, 2, 3), "m??s02i003") - self.assertEqual(iris.fileformats.pp.STASH(100, 2, 3), "m100s02i003") - self.assertEqual("m??s02i003", iris.fileformats.pp.STASH(100, 2, 3)) - self.assertEqual("m100s02i003", iris.fileformats.pp.STASH(100, 2, 3)) + assert iris.fileformats.pp.STASH(100, 2, 3) == "m??s02i003" + assert iris.fileformats.pp.STASH(100, 2, 3) == "m100s02i003" + assert "m??s02i003" == iris.fileformats.pp.STASH(100, 2, 3) + assert "m100s02i003" == iris.fileformats.pp.STASH(100, 2, 3) def test_illegal_stash_stash_range(self): - self.assertEqual( - iris.fileformats.pp.STASH(0, 2, 3), - iris.fileformats.pp.STASH(0, 2, 3), - ) - self.assertEqual( - iris.fileformats.pp.STASH(100, 2, 3), - iris.fileformats.pp.STASH(100, 2, 3), + assert iris.fileformats.pp.STASH(0, 2, 3) == iris.fileformats.pp.STASH(0, 2, 3) + assert iris.fileformats.pp.STASH(100, 2, 3) == iris.fileformats.pp.STASH( + 100, 2, 3 ) - self.assertEqual( - iris.fileformats.pp.STASH(100, 2, 3), - iris.fileformats.pp.STASH(999, 2, 3), + assert iris.fileformats.pp.STASH(100, 2, 3) == iris.fileformats.pp.STASH( + 999, 2, 3 ) def test_illegal_stash_format(self): @@ -112,9 +106,9 @@ def test_illegal_stash_format(self): for test_value, reference in test_values: msg = "Expected STASH code .* {!r}".format(test_value) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): test_value == iris.fileformats.pp.STASH(*reference) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): iris.fileformats.pp.STASH(*reference) == test_value def test_illegal_stash_type(self): @@ -125,16 +119,12 @@ def test_illegal_stash_type(self): for test_value, reference in test_values: msg = "Expected STASH code .* {!r}".format(test_value) - with self.assertRaisesRegex(TypeError, msg): + with pytest.raises(TypeError, match=msg): iris.fileformats.pp.STASH.from_msi(test_value) == reference - with self.assertRaisesRegex(TypeError, msg): + with pytest.raises(TypeError, match=msg): reference == iris.fileformats.pp.STASH.from_msi(test_value) def test_stash_lbuser(self): stash = iris.fileformats.pp.STASH(2, 32, 456) - self.assertEqual(stash.lbuser6(), 2) - self.assertEqual(stash.lbuser3(), 32456) - - -if __name__ == "__main__": - tests.main() + assert stash.lbuser6() == 2 + assert stash.lbuser3() == 32456 diff --git a/lib/iris/tests/test_pp_to_cube.py b/lib/iris/tests/test_pp_to_cube.py index a61703761f..88eca67d6d 100644 --- a/lib/iris/tests/test_pp_to_cube.py +++ b/lib/iris/tests/test_pp_to_cube.py @@ -3,26 +3,28 @@ # 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. -# import iris tests first so that some things can be initialised before importing anything else -import iris.tests as tests # isort:skip +from uuid import uuid4 -import os +import pytest import iris import iris.fileformats.pp import iris.fileformats.pp_load_rules import iris.fileformats.rules import iris.io +from iris.tests import _shared_utils import iris.tests.stock import iris.util -@tests.skip_data -class TestPPLoadCustom(tests.IrisTest): - def setUp(self): +@_shared_utils.skip_data +class TestPPLoadCustom: + @pytest.fixture(autouse=True) + def _setup(self, request): self.subcubes = iris.cube.CubeList() - filename = tests.get_data_path(("PP", "aPPglob1", "global.pp")) + filename = _shared_utils.get_data_path(("PP", "aPPglob1", "global.pp")) self.template = next(iris.fileformats.pp.load(filename)) + self.request = request def _field_to_cube(self, field): cube, _, _ = iris.fileformats.rules._make_cube( @@ -38,7 +40,7 @@ def test_lbtim_2(self): cube = self._field_to_cube(field) self.subcubes.append(cube) cube = self.subcubes.merge()[0] - self.assertCML(cube, ("pp_load_rules", "lbtim_2.cml")) + _shared_utils.assert_CML(self.request, cube, ("pp_load_rules", "lbtim_2.cml")) def _ocean_depth(self, bounded=False): lbuser = list(self.template.lbuser) @@ -62,16 +64,21 @@ def _ocean_depth(self, bounded=False): def test_ocean_depth(self): self._ocean_depth() cube = self.subcubes.merge()[0] - self.assertCML(cube, ("pp_load_rules", "ocean_depth.cml")) + _shared_utils.assert_CML( + self.request, cube, ("pp_load_rules", "ocean_depth.cml") + ) def test_ocean_depth_bounded(self): self._ocean_depth(bounded=True) cube = self.subcubes.merge()[0] - self.assertCML(cube, ("pp_load_rules", "ocean_depth_bounded.cml")) + _shared_utils.assert_CML( + self.request, cube, ("pp_load_rules", "ocean_depth_bounded.cml") + ) -class TestReferences(tests.IrisTest): - def setUp(self): +class TestReferences: + @pytest.fixture(autouse=True) + def _setup(self): target = iris.tests.stock.simple_2d() target.data = target.data.astype("f4") self.target = target @@ -82,7 +89,7 @@ def test_regrid_missing_coord(self): # coords, ensure the re-grid fails nicely - i.e. returns None. self.target.remove_coord("bar") new_ref, _ = iris.fileformats.rules._ensure_aligned({}, self.ref, self.target) - self.assertIsNone(new_ref) + assert new_ref is None def test_regrid_codimension(self): # If the target cube has two of the source dimension coords @@ -93,48 +100,56 @@ def test_regrid_codimension(self): new_foo.rename("foo") self.target.add_aux_coord(new_foo, 0) new_ref, _ = iris.fileformats.rules._ensure_aligned({}, self.ref, self.target) - self.assertIsNone(new_ref) + assert new_ref is None def test_regrid_identity(self): new_ref, _ = iris.fileformats.rules._ensure_aligned({}, self.ref, self.target) # Bounds don't make it through the re-grid process self.ref.coord("bar").bounds = None self.ref.coord("foo").bounds = None - self.assertEqual(new_ref, self.ref) + assert new_ref == self.ref -@tests.skip_data -class TestPPLoading(tests.IrisTest): - def test_simple(self): +@_shared_utils.skip_data +class TestPPLoading: + def test_simple(self, request): cube = iris.tests.stock.simple_pp() - self.assertCML(cube, ("cube_io", "pp", "load", "global.cml")) + _shared_utils.assert_CML(request, cube, ("cube_io", "pp", "load", "global.cml")) + +@_shared_utils.skip_data +class TestPPLoadRules: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request -@tests.skip_data -class TestPPLoadRules(tests.IrisTest): def test_pp_load_rules(self): # Test PP loading and rule evaluation. cube = iris.tests.stock.simple_pp() - self.assertCML(cube, ("pp_load_rules", "global.cml")) + _shared_utils.assert_CML(self.request, cube, ("pp_load_rules", "global.cml")) - data_path = tests.get_data_path(("PP", "rotated_uk", "rotated_uk.pp")) + data_path = _shared_utils.get_data_path(("PP", "rotated_uk", "rotated_uk.pp")) cube = iris.load(data_path)[0] - self.assertCML(cube, ("pp_load_rules", "rotated_uk.cml")) + _shared_utils.assert_CML( + self.request, cube, ("pp_load_rules", "rotated_uk.cml") + ) def test_lbproc(self): - data_path = tests.get_data_path( + data_path = _shared_utils.get_data_path( ("PP", "meanMaxMin", "200806081200__qwpb.T24.pp") ) # Set up standard name and T+24 constraint constraint = iris.Constraint("air_temperature", forecast_period=24) cubes = iris.load(data_path, constraint) cubes = iris.cube.CubeList([cubes[0], cubes[3], cubes[1], cubes[2], cubes[4]]) - self.assertCML(cubes, ("pp_load_rules", "lbproc_mean_max_min.cml")) + _shared_utils.assert_CML( + self.request, cubes, ("pp_load_rules", "lbproc_mean_max_min.cml") + ) - def test_cell_methods(self): + def test_cell_methods(self, tmp_path): # Test cell methods are created for correct values of lbproc - orig_file = tests.get_data_path(("PP", "aPPglob1", "global.pp")) + orig_file = _shared_utils.get_data_path(("PP", "aPPglob1", "global.pp")) # Values that result in cell methods being created cell_method_values = { @@ -158,7 +173,7 @@ def test_cell_methods(self): f.lbproc = value # set value # Write out pp file - temp_filename = iris.util.create_temp_filename(".pp") + temp_filename = (tmp_path / str(uuid4())).with_suffix(".pp") with open(temp_filename, "wb") as temp_fh: f.save(temp_fh) @@ -167,16 +182,14 @@ def test_cell_methods(self): if value in cell_method_values: # Check for cell method on cube - self.assertEqual(cube.cell_methods[0].method, cell_method_values[value]) + assert cube.cell_methods[0].method == cell_method_values[value] else: # Check no cell method was created for values other than 128, 4096, 8192 - self.assertEqual(len(cube.cell_methods), 0) + assert len(cube.cell_methods) == 0 - os.remove(temp_filename) - - def test_process_flags(self): + def test_process_flags(self, tmp_path): # Test that process flags are created for correct values of lbproc - orig_file = tests.get_data_path(("PP", "aPPglob1", "global.pp")) + orig_file = _shared_utils.get_data_path(("PP", "aPPglob1", "global.pp")) # Values that result in process flags attribute NOT being created omit_process_flags_values = (64, 128, 4096, 8192) @@ -187,7 +200,7 @@ def test_process_flags(self): f.lbproc = value # set value # Write out pp file - temp_filename = iris.util.create_temp_filename(".pp") + temp_filename = (tmp_path / str(uuid4())).with_suffix(".pp") with open(temp_filename, "wb") as temp_fh: f.save(temp_fh) @@ -196,16 +209,14 @@ def test_process_flags(self): if value in omit_process_flags_values: # Check ukmo__process_flags attribute not created - self.assertEqual(cube.attributes.get("ukmo__process_flags", None), None) + assert cube.attributes.get("ukmo__process_flags", None) is None else: # Check ukmo__process_flags attribute contains correct values - self.assertIn( - iris.fileformats.pp.lbproc_map[value], - cube.attributes["ukmo__process_flags"], + assert ( + iris.fileformats.pp.lbproc_map[value] + in cube.attributes["ukmo__process_flags"] ) - os.remove(temp_filename) - # Test multiple flag values multiple_bit_values = ((128, 32), (4096, 1024), (8192, 1024)) @@ -220,7 +231,7 @@ def test_process_flags(self): f.lbproc = sum(bit_values) # set value # Write out pp file - temp_filename = iris.util.create_temp_filename(".pp") + temp_filename = (tmp_path / str(uuid4())).with_suffix(".pp") with open(temp_filename, "wb") as temp_fh: f.save(temp_fh) @@ -228,14 +239,6 @@ def test_process_flags(self): cube = iris.load_cube(temp_filename) # Check the process flags created - self.assertEqual( - set(cube.attributes["ukmo__process_flags"]), - set(multiple_map[sum(bit_values)]), - "Mismatch between expected and actual process flags.", - ) - - os.remove(temp_filename) - - -if __name__ == "__main__": - tests.main() + assert set(cube.attributes["ukmo__process_flags"]) == set( + multiple_map[sum(bit_values)] + ), "Mismatch between expected and actual process flags." diff --git a/lib/iris/tests/test_quickplot.py b/lib/iris/tests/test_quickplot.py index 25bd8904a7..17ef68e64b 100644 --- a/lib/iris/tests/test_quickplot.py +++ b/lib/iris/tests/test_quickplot.py @@ -4,36 +4,24 @@ # See LICENSE in the root of the repository for full licensing details. """Tests the high-level plotting interface.""" -# import iris tests first so that some things can be initialised before importing anything else -import iris.tests as tests # isort:skip - import numpy as np +import pytest import iris +from iris.tests import _shared_utils import iris.tests.test_plot as test_plot # Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import matplotlib.pyplot as plt import iris.plot as iplt import iris.quickplot as qplt -# Caches _load_theta so subsequent calls are faster -def cache(fn, cache={}): - def inner(*args, **kwargs): - key = "result" - if not cache: - cache[key] = fn(*args, **kwargs) - return cache[key] - - return inner - - -@cache -def _load_theta(): - path = tests.get_data_path(("PP", "COLPEX", "theta_and_orog_subset.pp")) +@pytest.fixture(scope="module") +def load_theta(): + path = _shared_utils.get_data_path(("PP", "COLPEX", "theta_and_orog_subset.pp")) theta = iris.load_cube(path, "air_potential_temperature") # Improve the unit @@ -42,17 +30,11 @@ def _load_theta(): return theta -@tests.skip_data -@tests.skip_plot +@_shared_utils.skip_data +@_shared_utils.skip_plot class TestQuickplotCoordinatesGiven(test_plot.TestPlotCoordinatesGiven): - def setUp(self): - tests.GraphicsTest.setUp(self) - filename = tests.get_data_path(("PP", "COLPEX", "theta_and_orog_subset.pp")) - self.cube = test_plot.load_cube_once(filename, "air_potential_temperature") - if self.cube.coord_dims("time") != (0,): - # A quick fix for data which has changed since we support time-varying orography - self.cube.transpose((1, 0, 2, 3)) - + @pytest.fixture(autouse=True) + def _setup(self): self.draw_module = iris.quickplot self.contourf = test_plot.LambdaStr( "iris.quickplot.contourf", @@ -105,12 +87,12 @@ def setUp(self): } -@tests.skip_data -@tests.skip_plot -class TestLabels(tests.GraphicsTest): - def setUp(self): - super().setUp() - self.theta = _load_theta() +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestLabels(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self, load_theta): + self.theta = load_theta def _slice(self, coords): """Returns the first cube containing the requested coordinates.""" @@ -163,12 +145,12 @@ def test_contourf_axes_specified(self): qplt.contourf(self._small(), axes=axes1) # Ensure that the correct axes got the appropriate title. - self.assertEqual(axes2.get_title(), "This should not be changed") - self.assertEqual(axes1.get_title(), "Air potential temperature") + assert axes2.get_title() == "This should not be changed" + assert axes1.get_title() == "Air potential temperature" # Check that the axes labels were set correctly. - self.assertEqual(axes1.get_xlabel(), "Grid longitude / degrees") - self.assertEqual(axes1.get_ylabel(), "Altitude / m") + assert axes1.get_xlabel() == "Grid longitude / degrees" + assert axes1.get_ylabel() == "Altitude / m" def test_contourf_nameless(self): cube = self._small() @@ -231,12 +213,12 @@ def test_alignment(self): self.check_graphic() -@tests.skip_data -@tests.skip_plot -class TestTimeReferenceUnitsLabels(tests.GraphicsTest): - def setUp(self): - super().setUp() - path = tests.get_data_path(("PP", "aPProt1", "rotatedMHtimecube.pp")) +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestTimeReferenceUnitsLabels(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): + path = _shared_utils.get_data_path(("PP", "aPProt1", "rotatedMHtimecube.pp")) self.cube = iris.load_cube(path)[:, 0, 0] def test_reference_time_units(self): @@ -251,13 +233,13 @@ def test_not_reference_time_units(self): self.check_graphic() -@tests.skip_data -@tests.skip_plot -class TestSubplotColorbar(tests.IrisTest): - def setUp(self): - theta = _load_theta() +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestSubplotColorbar: + @pytest.fixture(autouse=True) + def _setup(self, load_theta): coords = ["model_level_number", "grid_longitude"] - self.data = next(theta.slices(coords)) + self.data = next(load_theta.slices(coords)) spec = (1, 1, 1) self.figure1 = plt.figure() self.axes1 = self.figure1.add_subplot(*spec) @@ -265,9 +247,9 @@ def setUp(self): self.axes2 = self.figure2.add_subplot(*spec) def _check(self, mappable, figure, axes): - self.assertIs(mappable.axes, axes) - self.assertIs(mappable.colorbar.mappable, mappable) - self.assertIs(mappable.colorbar.ax.get_figure(), figure) + assert mappable.axes is axes + assert mappable.colorbar.mappable is mappable + assert mappable.colorbar.ax.get_figure() is figure def test_with_axes1(self): # plot using the first figure subplot axes (explicit) @@ -285,9 +267,9 @@ def test_without_axes__default(self): self._check(mappable, self.figure2, self.axes2) -@tests.skip_data -@tests.skip_plot -class TestPlotHist(tests.GraphicsTest): +@_shared_utils.skip_data +@_shared_utils.skip_plot +class TestPlotHist(_shared_utils.GraphicsTest): def test_horizontal(self): cube = test_plot.simple_cube()[0] qplt.hist(cube, bins=np.linspace(287.7, 288.2, 11)) @@ -297,7 +279,3 @@ def test_vertical(self): cube = test_plot.simple_cube()[0] qplt.hist(cube, bins=np.linspace(287.7, 288.2, 11), orientation="horizontal") self.check_graphic() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/test_util.py b/lib/iris/tests/test_util.py index 56774f89f8..dabe90581c 100644 --- a/lib/iris/tests/test_util.py +++ b/lib/iris/tests/test_util.py @@ -4,30 +4,28 @@ # See LICENSE in the root of the repository for full licensing details. """Test iris.util.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import inspect from io import StringIO import cf_units import numpy as np +import pytest import iris.analysis import iris.coords +from iris.tests import _shared_utils import iris.tests.stock as stock import iris.util -class TestMonotonic(tests.IrisTest): - def assertMonotonic(self, array, direction=None, **kwargs): +class TestMonotonic: + def assert_monotonic(self, array, direction=None, **kwargs): if direction is not None: mono, dir = iris.util.monotonic(array, return_direction=True, **kwargs) if not mono: - self.fail("Array was not monotonic:/n %r" % array) + pytest.fail("Array was not monotonic:/n %r" % array) if dir != np.sign(direction): - self.fail( + pytest.fail( "Array was monotonic but not in the direction expected:" "/n + requested direction: %s/n + resultant direction: %s" % (direction, dir) @@ -35,51 +33,52 @@ def assertMonotonic(self, array, direction=None, **kwargs): else: mono = iris.util.monotonic(array, **kwargs) if not mono: - self.fail("Array was not monotonic:/n %r" % array) + pytest.fail("Array was not monotonic:/n %r" % array) - def assertNotMonotonic(self, array, **kwargs): + def assert_not_monotonic(self, array, **kwargs): mono = iris.util.monotonic(array, **kwargs) if mono: - self.fail("Array was monotonic when it shouldn't be:/n %r" % array) + pytest.fail("Array was monotonic when it shouldn't be:/n %r" % array) def test_monotonic_pve(self): a = np.array([3, 4, 5.3]) - self.assertMonotonic(a) - self.assertMonotonic(a, direction=1) + self.assert_monotonic(a) + self.assert_monotonic(a, direction=1) # test the reverse for negative monotonic. a = a[::-1] - self.assertMonotonic(a) - self.assertMonotonic(a, direction=-1) + self.assert_monotonic(a) + self.assert_monotonic(a, direction=-1) def test_not_monotonic(self): b = np.array([3, 5.3, 4]) - self.assertNotMonotonic(b) + self.assert_not_monotonic(b) def test_monotonic_strict(self): b = np.array([3, 5.3, 4]) - self.assertNotMonotonic(b, strict=True) - self.assertNotMonotonic(b) + self.assert_not_monotonic(b, strict=True) + self.assert_not_monotonic(b) b = np.array([3, 5.3, 5.3]) - self.assertNotMonotonic(b, strict=True) - self.assertMonotonic(b, direction=1) + self.assert_not_monotonic(b, strict=True) + self.assert_monotonic(b, direction=1) b = b[::-1] - self.assertNotMonotonic(b, strict=True) - self.assertMonotonic(b, direction=-1) + self.assert_not_monotonic(b, strict=True) + self.assert_monotonic(b, direction=-1) b = np.array([0.0]) - self.assertRaises(ValueError, iris.util.monotonic, b) - self.assertRaises(ValueError, iris.util.monotonic, b, strict=True) + pytest.raises(ValueError, iris.util.monotonic, b) + pytest.raises(ValueError, iris.util.monotonic, b, strict=True) b = np.array([0.0, 0.0]) - self.assertNotMonotonic(b, strict=True) - self.assertMonotonic(b) + self.assert_not_monotonic(b, strict=True) + self.assert_monotonic(b) -class TestClipString(tests.IrisTest): - def setUp(self): +class TestClipString: + @pytest.fixture(autouse=True) + def _setup(self): self.test_string = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." self.rider = "**^^**$$..--__" # A good chance at being unique and not in the string to be tested! @@ -90,43 +89,32 @@ def test_oversize_string(self): result = iris.util.clip_string(self.test_string, clip_length, self.rider) # Check the length is between what we requested ( + rider length) and the length of the original string - self.assertTrue( - clip_length + len(self.rider) <= len(result) < len(self.test_string), - "String was not clipped.", - ) + fail_message = "String was not clipped." + clip_rider_len = clip_length + len(self.rider) + assert clip_rider_len <= len(result) < len(self.test_string), fail_message # Also test the rider was added - self.assertTrue( - self.rider in result, - "Rider was not added to the string when it should have been.", - ) + fail_message = "Rider was not added to the string when it should have been." + assert self.rider in result, fail_message def test_undersize_string(self): # Test with a clip length that is longer than the string clip_length = 10999 result = iris.util.clip_string(self.test_string, clip_length, self.rider) - self.assertEqual( - len(result), - len(self.test_string), - "String was clipped when it should not have been.", - ) + fail_message = "String was clipped when it should not have been." + assert len(result) == len(self.test_string), fail_message # Also test that no rider was added on the end if the string was not clipped - self.assertFalse( - self.rider in result, - "Rider was adding to the string when it should not have been.", - ) + fail_message = "Rider was adding to the string when it should not have been." + assert self.rider not in result, fail_message def test_invalid_clip_lengths(self): # Clip values less than or equal to zero are not valid for clip_length in [0, -100]: result = iris.util.clip_string(self.test_string, clip_length, self.rider) - self.assertEqual( - len(result), - len(self.test_string), - "String was clipped when it should not have been.", - ) + fail_message = "String was clipped when it should not have been." + assert len(result) == len(self.test_string), fail_message def test_default_values(self): # Get the default values specified in the function @@ -137,12 +125,10 @@ def test_default_values(self): self.test_string, arg_dict["clip_length"], arg_dict["rider"] ) - self.assertLess(len(result), len(self.test_string), "String was not clipped.") + assert len(result) < len(self.test_string), "String was not clipped." rider_returned = result[-len(arg_dict["rider"]) :] - self.assertEqual( - rider_returned, arg_dict["rider"], "Default rider was not applied." - ) + assert rider_returned == arg_dict["rider"], "Default rider was not applied." def test_trim_string_with_no_spaces(self): clip_length = 200 @@ -155,16 +141,18 @@ def test_trim_string_with_no_spaces(self): expected_length = clip_length + len(self.rider) # Check the length of the returned string is equal to clip length + length of rider - self.assertEqual( - len(result), - expected_length, + assert len(result) == expected_length, ( "Mismatch in expected length of clipped string. Length was %s, " - "expected value is %s" % (len(result), expected_length), + "expected value is %s" % (len(result), expected_length) ) -@tests.skip_data -class TestDescribeDiff(iris.tests.IrisTest): +@_shared_utils.skip_data +class TestDescribeDiff: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + def test_identical(self): test_cube_a = stock.realistic_4d() test_cube_b = stock.realistic_4d() @@ -173,7 +161,9 @@ def test_identical(self): iris.util.describe_diff(test_cube_a, test_cube_b, output_file=return_sio) return_str = return_sio.getvalue() - self.assertString(return_str, "compatible_cubes.str.txt") + _shared_utils.assert_string( + self.request, return_str, "compatible_cubes.str.txt" + ) def test_different(self): # test incompatible attributes @@ -187,7 +177,9 @@ def test_different(self): iris.util.describe_diff(test_cube_a, test_cube_b, output_file=return_sio) return_str = return_sio.getvalue() - self.assertString(return_str, "incompatible_attr.str.txt") + _shared_utils.assert_string( + self.request, return_str, "incompatible_attr.str.txt" + ) # test incompatible names test_cube_a = stock.realistic_4d() @@ -199,7 +191,9 @@ def test_different(self): iris.util.describe_diff(test_cube_a, test_cube_b, output_file=return_sio) return_str = return_sio.getvalue() - self.assertString(return_str, "incompatible_name.str.txt") + _shared_utils.assert_string( + self.request, return_str, "incompatible_name.str.txt" + ) # test incompatible unit test_cube_a = stock.realistic_4d() @@ -211,7 +205,9 @@ def test_different(self): iris.util.describe_diff(test_cube_a, test_cube_b, output_file=return_sio) return_str = return_sio.getvalue() - self.assertString(return_str, "incompatible_unit.str.txt") + _shared_utils.assert_string( + self.request, return_str, "incompatible_unit.str.txt" + ) # test incompatible methods test_cube_a = stock.realistic_4d() @@ -223,9 +219,11 @@ def test_different(self): iris.util.describe_diff(test_cube_a, test_cube_b, output_file=return_sio) return_str = return_sio.getvalue() - self.assertString(return_str, "incompatible_meth.str.txt") + _shared_utils.assert_string( + self.request, return_str, "incompatible_meth.str.txt" + ) - def test_output_file(self): + def test_output_file(self, tmp_path): # test incompatible attributes test_cube_a = stock.realistic_4d() test_cube_b = stock.realistic_4d().collapsed( @@ -237,13 +235,9 @@ def test_output_file(self): test_cube_a.standard_name = "relative_humidity" test_cube_a.units = cf_units.Unit("m") - with self.temp_filename() as filename: + with tmp_path / "tmp" as filename: with open(filename, "w") as f: iris.util.describe_diff(test_cube_a, test_cube_b, output_file=f) f.close() - self.assertFilesEqual(filename, "incompatible_cubes.str.txt") - - -if __name__ == "__main__": - tests.main() + _shared_utils.assert_files_equal(filename, "incompatible_cubes.str.txt") diff --git a/lib/iris/tests/unit/aux_factory/test_AtmosphereSigmaFactory.py b/lib/iris/tests/unit/aux_factory/test_AtmosphereSigmaFactory.py index 61339b60ba..c505db8b8b 100644 --- a/lib/iris/tests/unit/aux_factory/test_AtmosphereSigmaFactory.py +++ b/lib/iris/tests/unit/aux_factory/test_AtmosphereSigmaFactory.py @@ -7,24 +7,20 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - from cf_units import Unit import numpy as np +import pytest from iris.aux_factory import AtmosphereSigmaFactory from iris.coords import AuxCoord, DimCoord -class Test___init__(tests.IrisTest): - def setUp(self): - self.pressure_at_top = mock.Mock(units=Unit("Pa"), nbounds=0, shape=()) - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock(units=Unit("Pa"), nbounds=0) +class Test___init__: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.pressure_at_top = mocker.Mock(units=Unit("Pa"), nbounds=0, shape=()) + self.sigma = mocker.Mock(units=Unit("1"), nbounds=0) + self.surface_air_pressure = mocker.Mock(units=Unit("Pa"), nbounds=0) self.kwargs = dict( pressure_at_top=self.pressure_at_top, sigma=self.sigma, @@ -32,11 +28,11 @@ def setUp(self): ) def test_insufficient_coordinates_no_args(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): AtmosphereSigmaFactory() def test_insufficient_coordinates_no_ptop(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): AtmosphereSigmaFactory( pressure_at_top=None, sigma=self.sigma, @@ -44,7 +40,7 @@ def test_insufficient_coordinates_no_ptop(self): ) def test_insufficient_coordinates_no_sigma(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): AtmosphereSigmaFactory( pressure_at_top=self.pressure_at_top, sigma=None, @@ -52,7 +48,7 @@ def test_insufficient_coordinates_no_sigma(self): ) def test_insufficient_coordinates_no_ps(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): AtmosphereSigmaFactory( pressure_at_top=self.pressure_at_top, sigma=self.sigma, @@ -67,7 +63,7 @@ def test_ptop_shapes(self): def test_ptop_invalid_shapes(self): for shape in [(2,), (1, 1)]: self.pressure_at_top.shape = shape - with self.assertRaises(ValueError): + with pytest.raises(ValueError): AtmosphereSigmaFactory(**self.kwargs) def test_sigma_bounds(self): @@ -78,7 +74,7 @@ def test_sigma_bounds(self): def test_sigma_invalid_bounds(self): for n_bounds in [-1, 1, 3]: self.sigma.nbounds = n_bounds - with self.assertRaises(ValueError): + with pytest.raises(ValueError): AtmosphereSigmaFactory(**self.kwargs) def test_sigma_units(self): @@ -89,7 +85,7 @@ def test_sigma_units(self): def test_sigma_invalid_units(self): for units in ["Pa", "m"]: self.sigma.units = Unit(units) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): AtmosphereSigmaFactory(**self.kwargs) def test_ptop_ps_units(self): @@ -102,7 +98,7 @@ def test_ptop_ps_invalid_units(self): for units in [("Pa", "1"), ("1", "Pa"), ("bar", "Pa"), ("Pa", "hPa")]: self.pressure_at_top.units = Unit(units[0]) self.surface_air_pressure.units = Unit(units[1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): AtmosphereSigmaFactory(**self.kwargs) def test_ptop_units(self): @@ -115,27 +111,29 @@ def test_ptop_invalid_units(self): for units in ["1", "m", "kg", None]: self.pressure_at_top.units = Unit(units) self.surface_air_pressure.units = Unit(units) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): AtmosphereSigmaFactory(**self.kwargs) -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.pressure_at_top = mock.Mock(units=Unit("Pa"), nbounds=0, shape=()) - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock(units=Unit("Pa"), nbounds=0) - self.kwargs = dict( - pressure_at_top=self.pressure_at_top, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, +class Test_dependencies: + @pytest.fixture() + def sample_kwargs(self, mocker): + pressure_at_top = mocker.Mock(units=Unit("Pa"), nbounds=0, shape=()) + sigma = mocker.Mock(units=Unit("1"), nbounds=0) + surface_air_pressure = mocker.Mock(units=Unit("Pa"), nbounds=0) + kwargs = dict( + pressure_at_top=pressure_at_top, + sigma=sigma, + surface_air_pressure=surface_air_pressure, ) + return kwargs - def test_values(self): - factory = AtmosphereSigmaFactory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) + def test_values(self, sample_kwargs): + factory = AtmosphereSigmaFactory(**sample_kwargs) + assert factory.dependencies == sample_kwargs -class Test__derive(tests.IrisTest): +class Test__derive: def test_function_scalar(self): assert AtmosphereSigmaFactory._derive(0, 0, 0) == 0 assert AtmosphereSigmaFactory._derive(3, 0, 0) == 3 @@ -156,7 +154,7 @@ def test_function_array(self): ) -class Test_make_coord(tests.IrisTest): +class Test_make_coord: @staticmethod def coord_dims(coord): mapping = dict( @@ -178,7 +176,8 @@ def derive(pressure_at_top, sigma, surface_air_pressure, coord=True): ) return result - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.pressure_at_top = AuxCoord( [3.0], long_name="pressure_at_top", @@ -225,14 +224,15 @@ def test_derived_coord(self): coord.bounds = None # Check points and metadata - self.assertEqual(expected_coord, coord) + assert coord == expected_coord -class Test_update(tests.IrisTest): - def setUp(self): - self.pressure_at_top = mock.Mock(units=Unit("Pa"), nbounds=0, shape=()) - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock(units=Unit("Pa"), nbounds=0) +class Test_update: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.pressure_at_top = mocker.Mock(units=Unit("Pa"), nbounds=0, shape=()) + self.sigma = mocker.Mock(units=Unit("1"), nbounds=0) + self.surface_air_pressure = mocker.Mock(units=Unit("Pa"), nbounds=0) self.kwargs = dict( pressure_at_top=self.pressure_at_top, sigma=self.sigma, @@ -240,41 +240,37 @@ def setUp(self): ) self.factory = AtmosphereSigmaFactory(**self.kwargs) - def test_pressure_at_top(self): - new_pressure_at_top = mock.Mock(units=Unit("Pa"), nbounds=0, shape=()) + def test_pressure_at_top(self, mocker): + new_pressure_at_top = mocker.Mock(units=Unit("Pa"), nbounds=0, shape=()) self.factory.update(self.pressure_at_top, new_pressure_at_top) - self.assertIs(self.factory.pressure_at_top, new_pressure_at_top) + assert self.factory.pressure_at_top is new_pressure_at_top - def test_pressure_at_top_wrong_shape(self): - new_pressure_at_top = mock.Mock(units=Unit("Pa"), nbounds=0, shape=(2,)) - with self.assertRaises(ValueError): + def test_pressure_at_top_wrong_shape(self, mocker): + new_pressure_at_top = mocker.Mock(units=Unit("Pa"), nbounds=0, shape=(2,)) + with pytest.raises(ValueError): self.factory.update(self.pressure_at_top, new_pressure_at_top) - def test_sigma(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=0) + def test_sigma(self, mocker): + new_sigma = mocker.Mock(units=Unit("1"), nbounds=0) self.factory.update(self.sigma, new_sigma) - self.assertIs(self.factory.sigma, new_sigma) + assert self.factory.sigma is new_sigma - def test_sigma_too_many_bounds(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): + def test_sigma_too_many_bounds(self, mocker): + new_sigma = mocker.Mock(units=Unit("1"), nbounds=4) + with pytest.raises(ValueError): self.factory.update(self.sigma, new_sigma) - def test_sigma_incompatible_units(self): - new_sigma = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + def test_sigma_incompatible_units(self, mocker): + new_sigma = mocker.Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.sigma, new_sigma) - def test_surface_air_pressure(self): - new_surface_air_pressure = mock.Mock(units=Unit("Pa"), nbounds=0) + def test_surface_air_pressure(self, mocker): + new_surface_air_pressure = mocker.Mock(units=Unit("Pa"), nbounds=0) self.factory.update(self.surface_air_pressure, new_surface_air_pressure) - self.assertIs(self.factory.surface_air_pressure, new_surface_air_pressure) + assert self.factory.surface_air_pressure is new_surface_air_pressure - def test_surface_air_pressure_incompatible_units(self): - new_surface_air_pressure = mock.Mock(units=Unit("mbar"), nbounds=0) - with self.assertRaises(ValueError): + def test_surface_air_pressure_incompatible_units(self, mocker): + new_surface_air_pressure = mocker.Mock(units=Unit("mbar"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.surface_air_pressure, new_surface_air_pressure) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_AuxCoordFactory.py b/lib/iris/tests/unit/aux_factory/test_AuxCoordFactory.py index 5e136395b5..468e5f5d5a 100644 --- a/lib/iris/tests/unit/aux_factory/test_AuxCoordFactory.py +++ b/lib/iris/tests/unit/aux_factory/test_AuxCoordFactory.py @@ -6,25 +6,23 @@ # Import iris.tests first so that some things can be initialised before # importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - import numpy as np +import pytest import iris from iris._lazy_data import as_lazy_data, is_lazy_data from iris.aux_factory import AuxCoordFactory from iris.coords import AuxCoord +from iris.tests._shared_utils import assert_array_equal, get_data_path, skip_data -class Test__nd_points(tests.IrisTest): +class Test__nd_points: def test_numpy_scalar_coord__zero_ndim(self): points = np.array(1) coord = AuxCoord(points) result = AuxCoordFactory._nd_points(coord, (), 0) expected = np.array([1]) - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) def test_numpy_scalar_coord(self): value = 1 @@ -32,55 +30,55 @@ def test_numpy_scalar_coord(self): coord = AuxCoord(points) result = AuxCoordFactory._nd_points(coord, (), 2) expected = np.array(value).reshape(1, 1) - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) def test_numpy_simple(self): points = np.arange(12).reshape(4, 3) coord = AuxCoord(points) result = AuxCoordFactory._nd_points(coord, (0, 1), 2) expected = points - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) def test_numpy_complex(self): points = np.arange(12).reshape(4, 3) coord = AuxCoord(points) result = AuxCoordFactory._nd_points(coord, (3, 2), 5) expected = points.T[np.newaxis, np.newaxis, ..., np.newaxis] - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) def test_lazy_simple(self): raw_points = np.arange(12).reshape(4, 3) points = as_lazy_data(raw_points, raw_points.shape) coord = AuxCoord(points) - self.assertTrue(is_lazy_data(coord.core_points())) + assert is_lazy_data(coord.core_points()) result = AuxCoordFactory._nd_points(coord, (0, 1), 2) # Check we haven't triggered the loading of the coordinate values. - self.assertTrue(is_lazy_data(coord.core_points())) - self.assertTrue(is_lazy_data(result)) + assert is_lazy_data(coord.core_points()) + assert is_lazy_data(result) expected = raw_points - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) def test_lazy_complex(self): raw_points = np.arange(12).reshape(4, 3) points = as_lazy_data(raw_points, raw_points.shape) coord = AuxCoord(points) - self.assertTrue(is_lazy_data(coord.core_points())) + assert is_lazy_data(coord.core_points()) result = AuxCoordFactory._nd_points(coord, (3, 2), 5) # Check we haven't triggered the loading of the coordinate values. - self.assertTrue(is_lazy_data(coord.core_points())) - self.assertTrue(is_lazy_data(result)) + assert is_lazy_data(coord.core_points()) + assert is_lazy_data(result) expected = raw_points.T[np.newaxis, np.newaxis, ..., np.newaxis] - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) -class Test__nd_bounds(tests.IrisTest): +class Test__nd_bounds: def test_numpy_scalar_coord__zero_ndim(self): points = np.array(0.5) bounds = np.arange(2) coord = AuxCoord(points, bounds=bounds) result = AuxCoordFactory._nd_bounds(coord, (), 0) expected = bounds - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) def test_numpy_scalar_coord(self): points = np.array(0.5) @@ -88,7 +86,7 @@ def test_numpy_scalar_coord(self): coord = AuxCoord(points, bounds=bounds) result = AuxCoordFactory._nd_bounds(coord, (), 2) expected = bounds[np.newaxis] - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) def test_numpy_simple(self): points = np.arange(12).reshape(4, 3) @@ -96,7 +94,7 @@ def test_numpy_simple(self): coord = AuxCoord(points, bounds=bounds) result = AuxCoordFactory._nd_bounds(coord, (0, 1), 2) expected = bounds - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) def test_numpy_complex(self): points = np.arange(12).reshape(4, 3) @@ -104,7 +102,7 @@ def test_numpy_complex(self): coord = AuxCoord(points, bounds=bounds) result = AuxCoordFactory._nd_bounds(coord, (3, 2), 5) expected = bounds.transpose((1, 0, 2)).reshape(1, 1, 3, 4, 1, 2) - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) def test_lazy_simple(self): raw_points = np.arange(12).reshape(4, 3) @@ -112,13 +110,13 @@ def test_lazy_simple(self): raw_bounds = np.arange(24).reshape(4, 3, 2) bounds = as_lazy_data(raw_bounds, raw_bounds.shape) coord = AuxCoord(points, bounds=bounds) - self.assertTrue(is_lazy_data(coord.core_bounds())) + assert is_lazy_data(coord.core_bounds()) result = AuxCoordFactory._nd_bounds(coord, (0, 1), 2) # Check we haven't triggered the loading of the coordinate values. - self.assertTrue(is_lazy_data(coord.core_bounds())) - self.assertTrue(is_lazy_data(result)) + assert is_lazy_data(coord.core_bounds()) + assert is_lazy_data(result) expected = raw_bounds - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) def test_lazy_complex(self): raw_points = np.arange(12).reshape(4, 3) @@ -126,39 +124,37 @@ def test_lazy_complex(self): raw_bounds = np.arange(24).reshape(4, 3, 2) bounds = as_lazy_data(raw_bounds, raw_bounds.shape) coord = AuxCoord(points, bounds=bounds) - self.assertTrue(is_lazy_data(coord.core_bounds())) + assert is_lazy_data(coord.core_bounds()) result = AuxCoordFactory._nd_bounds(coord, (3, 2), 5) # Check we haven't triggered the loading of the coordinate values. - self.assertTrue(is_lazy_data(coord.core_bounds())) - self.assertTrue(is_lazy_data(result)) + assert is_lazy_data(coord.core_bounds()) + assert is_lazy_data(result) expected = raw_bounds.transpose((1, 0, 2)).reshape(1, 1, 3, 4, 1, 2) - self.assertArrayEqual(result, expected) + assert_array_equal(result, expected) -@tests.skip_data -class Test_lazy_aux_coords(tests.IrisTest): - def setUp(self): - path = tests.get_data_path(["NetCDF", "testing", "small_theta_colpex.nc"]) +@skip_data +class Test_lazy_aux_coords: + @pytest.fixture() + def sample_cube(self, mocker): + path = get_data_path(["NetCDF", "testing", "small_theta_colpex.nc"]) # While loading, "turn off" loading small variables as real data. - with mock.patch("iris.fileformats.netcdf.loader._LAZYVAR_MIN_BYTES", 0): - self.cube = iris.load_cube(path, "air_potential_temperature") + mocker.patch("iris.fileformats.netcdf.loader._LAZYVAR_MIN_BYTES", 0) + cube = iris.load_cube(path, "air_potential_temperature") + return cube - def _check_lazy(self): - coords = self.cube.aux_coords + self.cube.derived_coords + def _check_lazy(self, cube): + coords = cube.aux_coords + cube.derived_coords for coord in coords: - self.assertTrue(coord.has_lazy_points()) + assert coord.has_lazy_points() if coord.has_bounds(): - self.assertTrue(coord.has_lazy_bounds()) + assert coord.has_lazy_bounds() - def test_lazy_coord_loading(self): + def test_lazy_coord_loading(self, sample_cube): # Test that points and bounds arrays stay lazy upon cube loading. - self._check_lazy() + self._check_lazy(sample_cube) - def test_lazy_coord_printing(self): + def test_lazy_coord_printing(self, sample_cube): # Test that points and bounds arrays stay lazy after cube printing. - _ = str(self.cube) - self._check_lazy() - - -if __name__ == "__main__": - tests.main() + _ = str(sample_cube) + self._check_lazy(sample_cube) diff --git a/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py b/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py index d352cf663b..e3caf0c114 100644 --- a/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py +++ b/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py @@ -7,33 +7,40 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock +from unittest.mock import Mock import cf_units import numpy as np +import pytest import iris from iris.aux_factory import HybridPressureFactory -class Test___init__(tests.IrisTest): - def setUp(self): - self.delta = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) - self.sigma = mock.Mock(units=cf_units.Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) +def create_default_sample_parts(self): + self.delta = Mock(units=cf_units.Unit("Pa"), nbounds=0) + self.sigma = Mock(units=cf_units.Unit("1"), nbounds=0) + self.surface_air_pressure = Mock(units=cf_units.Unit("Pa"), nbounds=0) + self.factory = HybridPressureFactory( + delta=self.delta, + sigma=self.sigma, + surface_air_pressure=self.surface_air_pressure, + ) + + +class Test___init__: + @pytest.fixture(autouse=True) + def _setup(self): + create_default_sample_parts(self) def test_insufficient_coords(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): HybridPressureFactory() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): HybridPressureFactory( delta=None, sigma=self.sigma, surface_air_pressure=None ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): HybridPressureFactory( delta=None, sigma=None, @@ -42,7 +49,7 @@ def test_insufficient_coords(self): def test_incompatible_delta_units(self): self.delta.units = cf_units.Unit("m") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): HybridPressureFactory( delta=self.delta, sigma=self.sigma, @@ -51,7 +58,7 @@ def test_incompatible_delta_units(self): def test_incompatible_sigma_units(self): self.sigma.units = cf_units.Unit("Pa") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): HybridPressureFactory( delta=self.delta, sigma=self.sigma, @@ -60,7 +67,7 @@ def test_incompatible_sigma_units(self): def test_incompatible_surface_air_pressure_units(self): self.surface_air_pressure.units = cf_units.Unit("unknown") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): HybridPressureFactory( delta=self.delta, sigma=self.sigma, @@ -70,7 +77,7 @@ def test_incompatible_surface_air_pressure_units(self): def test_different_pressure_units(self): self.delta.units = cf_units.Unit("hPa") self.surface_air_pressure.units = cf_units.Unit("Pa") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): HybridPressureFactory( delta=self.delta, sigma=self.sigma, @@ -79,7 +86,7 @@ def test_different_pressure_units(self): def test_too_many_delta_bounds(self): self.delta.nbounds = 4 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): HybridPressureFactory( delta=self.delta, sigma=self.sigma, @@ -88,7 +95,7 @@ def test_too_many_delta_bounds(self): def test_too_many_sigma_bounds(self): self.sigma.nbounds = 4 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): HybridPressureFactory( delta=self.delta, sigma=self.sigma, @@ -101,29 +108,28 @@ def test_factory_metadata(self): sigma=self.sigma, surface_air_pressure=self.surface_air_pressure, ) - self.assertEqual(factory.standard_name, "air_pressure") - self.assertIsNone(factory.long_name) - self.assertIsNone(factory.var_name) - self.assertEqual(factory.units, self.delta.units) - self.assertEqual(factory.units, self.surface_air_pressure.units) - self.assertIsNone(factory.coord_system) - self.assertEqual(factory.attributes, {}) + assert factory.standard_name == "air_pressure" + assert factory.long_name is None + assert factory.var_name is None + assert factory.units == self.delta.units + assert factory.units == self.surface_air_pressure.units + assert factory.coord_system is None + assert factory.attributes == {} def test_promote_sigma_units_unknown_to_dimensionless(self): - sigma = mock.Mock(units=cf_units.Unit("unknown"), nbounds=0) + sigma = Mock(units=cf_units.Unit("unknown"), nbounds=0) factory = HybridPressureFactory( delta=self.delta, sigma=sigma, surface_air_pressure=self.surface_air_pressure, ) - self.assertEqual("1", factory.dependencies["sigma"].units) + assert factory.dependencies["sigma"].units == "1" -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.delta = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) - self.sigma = mock.Mock(units=cf_units.Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) +class Test_dependencies: + @pytest.fixture(autouse=True) + def _setup(self): + create_default_sample_parts(self) def test_value(self): kwargs = dict( @@ -132,16 +138,18 @@ def test_value(self): surface_air_pressure=self.surface_air_pressure, ) factory = HybridPressureFactory(**kwargs) - self.assertEqual(factory.dependencies, kwargs) + assert factory.dependencies == kwargs -class Test_make_coord(tests.IrisTest): +class Test_make_coord: @staticmethod def coords_dims_func(coord): mapping = dict(level_pressure=(0,), sigma=(0,), surface_air_pressure=(1, 2)) return mapping[coord.name()] - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): + # Create standard data objects for coord testing self.delta = iris.coords.DimCoord( [0.0, 1.0, 2.0], long_name="level_pressure", units="Pa" ) @@ -166,7 +174,7 @@ def test_points_only(self): surface_air_pressure=self.surface_air_pressure, ) derived_coord = factory.make_coord(self.coords_dims_func) - self.assertEqual(expected_coord, derived_coord) + assert derived_coord == expected_coord def test_none_delta(self): delta_pts = 0 @@ -177,10 +185,11 @@ def test_none_delta(self): expected_points, standard_name="air_pressure", units="Pa" ) factory = HybridPressureFactory( - sigma=self.sigma, surface_air_pressure=self.surface_air_pressure + sigma=self.sigma, + surface_air_pressure=self.surface_air_pressure, ) derived_coord = factory.make_coord(self.coords_dims_func) - self.assertEqual(expected_coord, derived_coord) + assert derived_coord == expected_coord def test_none_sigma(self): delta_pts = self.delta.points[..., np.newaxis, np.newaxis] @@ -191,10 +200,11 @@ def test_none_sigma(self): expected_points, standard_name="air_pressure", units="Pa" ) factory = HybridPressureFactory( - delta=self.delta, surface_air_pressure=self.surface_air_pressure + delta=self.delta, + surface_air_pressure=self.surface_air_pressure, ) derived_coord = factory.make_coord(self.coords_dims_func) - self.assertEqual(expected_coord, derived_coord) + assert derived_coord == expected_coord def test_none_surface_air_pressure(self): # Note absence of broadcasting as multidimensional coord @@ -205,7 +215,7 @@ def test_none_surface_air_pressure(self): ) factory = HybridPressureFactory(delta=self.delta, sigma=self.sigma) derived_coord = factory.make_coord(self.coords_dims_func) - self.assertEqual(expected_coord, derived_coord) + assert derived_coord == expected_coord def test_with_bounds(self): self.delta.guess_bounds(0) @@ -232,66 +242,55 @@ def test_with_bounds(self): surface_air_pressure=self.surface_air_pressure, ) derived_coord = factory.make_coord(self.coords_dims_func) - self.assertEqual(expected_coord, derived_coord) + assert derived_coord == expected_coord -class Test_update(tests.IrisTest): - def setUp(self): - self.delta = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) - self.sigma = mock.Mock(units=cf_units.Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) - - self.factory = HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) +class Test_update: + @pytest.fixture(autouse=True) + def _setup(self): + create_default_sample_parts(self) def test_good_delta(self): - new_delta_coord = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) + new_delta_coord = Mock(units=cf_units.Unit("Pa"), nbounds=0) self.factory.update(self.delta, new_delta_coord) - self.assertIs(self.factory.delta, new_delta_coord) + assert self.factory.delta is new_delta_coord def test_bad_delta(self): - new_delta_coord = mock.Mock(units=cf_units.Unit("1"), nbounds=0) - with self.assertRaises(ValueError): + new_delta_coord = Mock(units=cf_units.Unit("1"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.delta, new_delta_coord) def test_alternative_bad_delta(self): - new_delta_coord = mock.Mock(units=cf_units.Unit("Pa"), nbounds=4) - with self.assertRaises(ValueError): + new_delta_coord = Mock(units=cf_units.Unit("Pa"), nbounds=4) + with pytest.raises(ValueError): self.factory.update(self.delta, new_delta_coord) def test_good_surface_air_pressure(self): - new_surface_p_coord = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) + new_surface_p_coord = Mock(units=cf_units.Unit("Pa"), nbounds=0) self.factory.update(self.surface_air_pressure, new_surface_p_coord) - self.assertIs(self.factory.surface_air_pressure, new_surface_p_coord) + assert self.factory.surface_air_pressure is new_surface_p_coord def test_bad_surface_air_pressure(self): - new_surface_p_coord = mock.Mock(units=cf_units.Unit("km"), nbounds=0) - with self.assertRaises(ValueError): + new_surface_p_coord = Mock(units=cf_units.Unit("km"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.surface_air_pressure, new_surface_p_coord) def test_non_dependency(self): - old_coord = mock.Mock() - new_coord = mock.Mock() + old_coord = Mock() + new_coord = Mock() orig_dependencies = self.factory.dependencies self.factory.update(old_coord, new_coord) - self.assertEqual(orig_dependencies, self.factory.dependencies) + assert self.factory.dependencies == orig_dependencies def test_none_delta(self): self.factory.update(self.delta, None) - self.assertIsNone(self.factory.delta) + assert self.factory.delta is None def test_none_sigma(self): self.factory.update(self.sigma, None) - self.assertIsNone(self.factory.sigma) + assert self.factory.sigma is None def test_insufficient_coords(self): self.factory.update(self.delta, None) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.factory.update(self.surface_air_pressure, None) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py b/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py index e0955842bd..e702b8d2e2 100644 --- a/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py +++ b/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py @@ -7,27 +7,25 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock +from unittest.mock import Mock from cf_units import Unit import numpy as np +import pytest from iris.aux_factory import OceanSFactory from iris.coords import AuxCoord, DimCoord -class Test___init__(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.a = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.b = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) +class Test___init__: + @pytest.fixture(autouse=True) + def _setup(self): + self.s = Mock(units=Unit("1"), nbounds=0) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.a = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.b = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.kwargs = dict( s=self.s, eta=self.eta, @@ -38,9 +36,9 @@ def setUp(self): ) def test_insufficient_coordinates(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory( s=None, eta=self.eta, @@ -49,7 +47,7 @@ def test_insufficient_coordinates(self): b=self.b, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory( s=self.s, eta=None, @@ -58,7 +56,7 @@ def test_insufficient_coordinates(self): b=self.b, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory( s=self.s, eta=self.eta, @@ -67,7 +65,7 @@ def test_insufficient_coordinates(self): b=self.b, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory( s=self.s, eta=self.eta, @@ -76,7 +74,7 @@ def test_insufficient_coordinates(self): b=self.b, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory( s=self.s, eta=self.eta, @@ -85,7 +83,7 @@ def test_insufficient_coordinates(self): b=None, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory( s=self.s, eta=self.eta, @@ -97,59 +95,60 @@ def test_insufficient_coordinates(self): def test_s_too_many_bounds(self): self.s.nbounds = 4 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory(**self.kwargs) def test_a_non_scalar(self): self.a.shape = (2,) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory(**self.kwargs) def test_b_non_scalar(self): self.b.shape = (2,) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory(**self.kwargs) def test_depth_c_non_scalar(self): self.depth_c.shape = (2,) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory(**self.kwargs) def test_s_incompatible_units(self): self.s.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory(**self.kwargs) def test_eta_incompatible_units(self): self.eta.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory(**self.kwargs) def test_depth_c_incompatible_units(self): self.depth_c.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory(**self.kwargs) def test_depth_incompatible_units(self): self.depth.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSFactory(**self.kwargs) def test_promote_s_units_unknown_to_dimensionless(self): - s = mock.Mock(units=Unit("unknown"), nbounds=0) + s = Mock(units=Unit("unknown"), nbounds=0) self.kwargs["s"] = s factory = OceanSFactory(**self.kwargs) - self.assertEqual("1", factory.dependencies["s"].units) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.a = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.b = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) + assert factory.dependencies["s"].units == "1" + + +class Test_dependencies: + @pytest.fixture(autouse=True) + def _setup(self): + self.s = Mock(units=Unit("1"), nbounds=0) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.a = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.b = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.kwargs = dict( s=self.s, eta=self.eta, @@ -161,10 +160,10 @@ def setUp(self): def test_values(self): factory = OceanSFactory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) + assert factory.dependencies == self.kwargs -class Test_make_coord(tests.IrisTest): +class Test_make_coord: @staticmethod def coord_dims(coord): mapping = dict(s=(0,), eta=(1, 2), depth=(1, 2), a=(), b=(), depth_c=()) @@ -186,7 +185,8 @@ def derive(s, eta, depth, a, b, depth_c, coord=True): ) return result - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.s = DimCoord( np.arange(-0.975, 0, 0.05, dtype=float), units="1", long_name="s" ) @@ -225,17 +225,18 @@ def test_derived_points(self): # Calculate the actual result. factory = OceanSFactory(**self.kwargs) coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - -class Test_update(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.a = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.b = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) + assert coord == expected_coord + + +class Test_update: + @pytest.fixture(autouse=True) + def _setup(self): + self.s = Mock(units=Unit("1"), nbounds=0) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.a = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.b = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.kwargs = dict( s=self.s, eta=self.eta, @@ -247,75 +248,71 @@ def setUp(self): self.factory = OceanSFactory(**self.kwargs) def test_s(self): - new_s = mock.Mock(units=Unit("1"), nbounds=0) + new_s = Mock(units=Unit("1"), nbounds=0) self.factory.update(self.s, new_s) - self.assertIs(self.factory.s, new_s) + assert self.factory.s is new_s def test_s_too_many_bounds(self): - new_s = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): + new_s = Mock(units=Unit("1"), nbounds=4) + with pytest.raises(ValueError): self.factory.update(self.s, new_s) def test_s_incompatible_units(self): - new_s = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_s = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.s, new_s) def test_eta(self): - new_eta = mock.Mock(units=Unit("m"), nbounds=0) + new_eta = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.eta, new_eta) - self.assertIs(self.factory.eta, new_eta) + assert self.factory.eta is new_eta def test_eta_incompatible_units(self): - new_eta = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_eta = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.eta, new_eta) def test_depth(self): - new_depth = mock.Mock(units=Unit("m"), nbounds=0) + new_depth = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.depth, new_depth) - self.assertIs(self.factory.depth, new_depth) + assert self.factory.depth is new_depth def test_depth_incompatible_units(self): - new_depth = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_depth = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.depth, new_depth) def test_a(self): - new_a = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) + new_a = Mock(units=Unit("1"), nbounds=0, shape=(1,)) self.factory.update(self.a, new_a) - self.assertIs(self.factory.a, new_a) + assert self.factory.a is new_a def test_a_non_scalar(self): - new_a = mock.Mock(units=Unit("1"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): + new_a = Mock(units=Unit("1"), nbounds=0, shape=(10,)) + with pytest.raises(ValueError): self.factory.update(self.a, new_a) def test_b(self): - new_b = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) + new_b = Mock(units=Unit("1"), nbounds=0, shape=(1,)) self.factory.update(self.b, new_b) - self.assertIs(self.factory.b, new_b) + assert self.factory.b is new_b def test_b_non_scalar(self): - new_b = mock.Mock(units=Unit("1"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): + new_b = Mock(units=Unit("1"), nbounds=0, shape=(10,)) + with pytest.raises(ValueError): self.factory.update(self.b, new_b) def test_depth_c(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) + new_depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.factory.update(self.depth_c, new_depth_c) - self.assertIs(self.factory.depth_c, new_depth_c) + assert self.factory.depth_c is new_depth_c def test_depth_c_non_scalar(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): + new_depth_c = Mock(units=Unit("m"), nbounds=0, shape=(10,)) + with pytest.raises(ValueError): self.factory.update(self.depth_c, new_depth_c) def test_depth_c_incompatible_units(self): - new_depth_c = mock.Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) - with self.assertRaises(ValueError): + new_depth_c = Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) + with pytest.raises(ValueError): self.factory.update(self.depth_c, new_depth_c) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py b/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py index 7cb42f7274..82e7cd2a7b 100644 --- a/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py +++ b/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py @@ -7,26 +7,24 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock +from unittest.mock import Mock from cf_units import Unit import numpy as np +import pytest from iris.aux_factory import OceanSg1Factory from iris.coords import AuxCoord, DimCoord -class Test___init__(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) +class Test___init__: + @pytest.fixture(autouse=True) + def _setup(self): + self.s = Mock(units=Unit("1"), nbounds=0) + self.c = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.kwargs = dict( s=self.s, c=self.c, @@ -36,9 +34,9 @@ def setUp(self): ) def test_insufficient_coordinates(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory( s=None, c=self.c, @@ -46,7 +44,7 @@ def test_insufficient_coordinates(self): depth=self.depth, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory( s=self.s, c=None, @@ -54,7 +52,7 @@ def test_insufficient_coordinates(self): depth=self.depth, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory( s=self.s, c=self.c, @@ -62,7 +60,7 @@ def test_insufficient_coordinates(self): depth=self.depth, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory( s=self.s, c=self.c, @@ -70,7 +68,7 @@ def test_insufficient_coordinates(self): depth=None, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory( s=self.s, c=self.c, @@ -81,61 +79,62 @@ def test_insufficient_coordinates(self): def test_s_too_many_bounds(self): self.s.nbounds = 4 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory(**self.kwargs) def test_c_too_many_bounds(self): self.c.nbounds = 4 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory(**self.kwargs) def test_depth_c_non_scalar(self): self.depth_c.shape = (2,) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory(**self.kwargs) def test_s_incompatible_units(self): self.s.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory(**self.kwargs) def test_c_incompatible_units(self): self.c.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory(**self.kwargs) def test_eta_incompatible_units(self): self.eta.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory(**self.kwargs) def test_depth_c_incompatible_units(self): self.depth_c.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory(**self.kwargs) def test_depth_incompatible_units(self): self.depth.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg1Factory(**self.kwargs) def test_promote_c_and_s_units_unknown_to_dimensionless(self): - c = mock.Mock(units=Unit("unknown"), nbounds=0) - s = mock.Mock(units=Unit("unknown"), nbounds=0) + c = Mock(units=Unit("unknown"), nbounds=0) + s = Mock(units=Unit("unknown"), nbounds=0) self.kwargs["c"] = c self.kwargs["s"] = s factory = OceanSg1Factory(**self.kwargs) - self.assertEqual("1", factory.dependencies["c"].units) - self.assertEqual("1", factory.dependencies["s"].units) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) + assert factory.dependencies["c"].units == "1" + assert factory.dependencies["s"].units == "1" + + +class Test_dependencies: + @pytest.fixture(autouse=True) + def _setup(self): + self.s = Mock(units=Unit("1"), nbounds=0) + self.c = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.kwargs = dict( s=self.s, c=self.c, @@ -146,10 +145,10 @@ def setUp(self): def test_values(self): factory = OceanSg1Factory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) + assert factory.dependencies == self.kwargs -class Test_make_coord(tests.IrisTest): +class Test_make_coord: @staticmethod def coord_dims(coord): mapping = dict(s=(0,), c=(0,), eta=(1, 2), depth=(1, 2), depth_c=()) @@ -169,7 +168,8 @@ def derive(s, c, eta, depth, depth_c, coord=True): ) return result - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.s = DimCoord(np.linspace(-0.985, -0.014, 36), units="1", long_name="s") self.c = DimCoord(np.linspace(-0.959, -0.001, 36), units="1", long_name="c") self.eta = AuxCoord( @@ -203,16 +203,17 @@ def test_derived_points(self): # Calculate the actual result. factory = OceanSg1Factory(**self.kwargs) coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) + assert coord == expected_coord -class Test_update(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) +class Test_update: + @pytest.fixture(autouse=True) + def _setup(self): + self.s = Mock(units=Unit("1"), nbounds=0) + self.c = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.kwargs = dict( s=self.s, c=self.c, @@ -223,70 +224,66 @@ def setUp(self): self.factory = OceanSg1Factory(**self.kwargs) def test_s(self): - new_s = mock.Mock(units=Unit("1"), nbounds=0) + new_s = Mock(units=Unit("1"), nbounds=0) self.factory.update(self.s, new_s) - self.assertIs(self.factory.s, new_s) + assert self.factory.s is new_s def test_c(self): - new_c = mock.Mock(units=Unit("1"), nbounds=0) + new_c = Mock(units=Unit("1"), nbounds=0) self.factory.update(self.c, new_c) - self.assertIs(self.factory.c, new_c) + assert self.factory.c is new_c def test_s_too_many_bounds(self): - new_s = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): + new_s = Mock(units=Unit("1"), nbounds=4) + with pytest.raises(ValueError): self.factory.update(self.s, new_s) def test_c_too_many_bounds(self): - new_c = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): + new_c = Mock(units=Unit("1"), nbounds=4) + with pytest.raises(ValueError): self.factory.update(self.c, new_c) def test_s_incompatible_units(self): - new_s = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_s = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.s, new_s) def test_c_incompatible_units(self): - new_c = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_c = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.c, new_c) def test_eta(self): - new_eta = mock.Mock(units=Unit("m"), nbounds=0) + new_eta = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.eta, new_eta) - self.assertIs(self.factory.eta, new_eta) + assert self.factory.eta is new_eta def test_eta_incompatible_units(self): - new_eta = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_eta = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.eta, new_eta) def test_depth(self): - new_depth = mock.Mock(units=Unit("m"), nbounds=0) + new_depth = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.depth, new_depth) - self.assertIs(self.factory.depth, new_depth) + assert self.factory.depth is new_depth def test_depth_incompatible_units(self): - new_depth = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_depth = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.depth, new_depth) def test_depth_c(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) + new_depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.factory.update(self.depth_c, new_depth_c) - self.assertIs(self.factory.depth_c, new_depth_c) + assert self.factory.depth_c is new_depth_c def test_depth_c_non_scalar(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): + new_depth_c = Mock(units=Unit("m"), nbounds=0, shape=(10,)) + with pytest.raises(ValueError): self.factory.update(self.depth_c, new_depth_c) def test_depth_c_incompatible_units(self): - new_depth_c = mock.Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) - with self.assertRaises(ValueError): + new_depth_c = Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) + with pytest.raises(ValueError): self.factory.update(self.depth_c, new_depth_c) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py b/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py index c3b5a3df1b..ecb8593e99 100644 --- a/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py +++ b/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py @@ -7,26 +7,24 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock +from unittest.mock import Mock from cf_units import Unit import numpy as np +import pytest from iris.aux_factory import OceanSg2Factory from iris.coords import AuxCoord, DimCoord -class Test___init__(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) +class Test___init__: + @pytest.fixture(autouse=True) + def _setup(self): + self.s = Mock(units=Unit("1"), nbounds=0) + self.c = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.kwargs = dict( s=self.s, c=self.c, @@ -36,9 +34,9 @@ def setUp(self): ) def test_insufficient_coordinates(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory( s=None, c=self.c, @@ -46,7 +44,7 @@ def test_insufficient_coordinates(self): depth=self.depth, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory( s=self.s, c=None, @@ -54,7 +52,7 @@ def test_insufficient_coordinates(self): depth=self.depth, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory( s=self.s, c=self.c, @@ -62,7 +60,7 @@ def test_insufficient_coordinates(self): depth=self.depth, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory( s=self.s, c=self.c, @@ -70,7 +68,7 @@ def test_insufficient_coordinates(self): depth=None, depth_c=self.depth_c, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory( s=self.s, c=self.c, @@ -81,61 +79,62 @@ def test_insufficient_coordinates(self): def test_s_too_many_bounds(self): self.s.nbounds = 4 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory(**self.kwargs) def test_c_too_many_bounds(self): self.c.nbounds = 4 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory(**self.kwargs) def test_depth_c_non_scalar(self): self.depth_c.shape = (2,) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory(**self.kwargs) def test_s_incompatible_units(self): self.s.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory(**self.kwargs) def test_c_incompatible_units(self): self.c.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory(**self.kwargs) def test_eta_incompatible_units(self): self.eta.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory(**self.kwargs) def test_depth_c_incompatible_units(self): self.depth_c.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory(**self.kwargs) def test_depth_incompatible_units(self): self.depth.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSg2Factory(**self.kwargs) def test_promote_c_and_s_units_unknown_to_dimensionless(self): - c = mock.Mock(units=Unit("unknown"), nbounds=0) - s = mock.Mock(units=Unit("unknown"), nbounds=0) + c = Mock(units=Unit("unknown"), nbounds=0) + s = Mock(units=Unit("unknown"), nbounds=0) self.kwargs["c"] = c self.kwargs["s"] = s factory = OceanSg2Factory(**self.kwargs) - self.assertEqual("1", factory.dependencies["c"].units) - self.assertEqual("1", factory.dependencies["s"].units) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) + assert factory.dependencies["c"].units == "1" + assert factory.dependencies["s"].units == "1" + + +class Test_dependencies: + @pytest.fixture(autouse=True) + def _setup(self): + self.s = Mock(units=Unit("1"), nbounds=0) + self.c = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.kwargs = dict( s=self.s, c=self.c, @@ -146,10 +145,10 @@ def setUp(self): def test_values(self): factory = OceanSg2Factory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) + assert factory.dependencies == self.kwargs -class Test_make_coord(tests.IrisTest): +class Test_make_coord: @staticmethod def coord_dims(coord): mapping = dict(s=(0,), c=(0,), eta=(1, 2), depth=(1, 2), depth_c=()) @@ -169,7 +168,8 @@ def derive(s, c, eta, depth, depth_c, coord=True): ) return result - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.s = DimCoord(np.linspace(-0.985, -0.014, 36), units="1", long_name="s") self.c = DimCoord(np.linspace(-0.959, -0.001, 36), units="1", long_name="c") self.eta = AuxCoord( @@ -203,16 +203,17 @@ def test_derived_points(self): # Calculate the actual result. factory = OceanSg2Factory(**self.kwargs) coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) + assert coord == expected_coord -class Test_update(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) +class Test_update: + @pytest.fixture(autouse=True) + def _setup(self): + self.s = Mock(units=Unit("1"), nbounds=0) + self.c = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.kwargs = dict( s=self.s, c=self.c, @@ -223,70 +224,66 @@ def setUp(self): self.factory = OceanSg2Factory(**self.kwargs) def test_s(self): - new_s = mock.Mock(units=Unit("1"), nbounds=0) + new_s = Mock(units=Unit("1"), nbounds=0) self.factory.update(self.s, new_s) - self.assertIs(self.factory.s, new_s) + assert self.factory.s is new_s def test_c(self): - new_c = mock.Mock(units=Unit("1"), nbounds=0) + new_c = Mock(units=Unit("1"), nbounds=0) self.factory.update(self.c, new_c) - self.assertIs(self.factory.c, new_c) + assert self.factory.c is new_c def test_s_too_many_bounds(self): - new_s = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): + new_s = Mock(units=Unit("1"), nbounds=4) + with pytest.raises(ValueError): self.factory.update(self.s, new_s) def test_c_too_many_bounds(self): - new_c = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): + new_c = Mock(units=Unit("1"), nbounds=4) + with pytest.raises(ValueError): self.factory.update(self.c, new_c) def test_s_incompatible_units(self): - new_s = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_s = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.s, new_s) def test_c_incompatible_units(self): - new_c = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_c = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.c, new_c) def test_eta(self): - new_eta = mock.Mock(units=Unit("m"), nbounds=0) + new_eta = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.eta, new_eta) - self.assertIs(self.factory.eta, new_eta) + assert self.factory.eta is new_eta def test_eta_incompatible_units(self): - new_eta = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_eta = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.eta, new_eta) def test_depth(self): - new_depth = mock.Mock(units=Unit("m"), nbounds=0) + new_depth = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.depth, new_depth) - self.assertIs(self.factory.depth, new_depth) + assert self.factory.depth is new_depth def test_depth_incompatible_units(self): - new_depth = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_depth = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.depth, new_depth) def test_depth_c(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) + new_depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.factory.update(self.depth_c, new_depth_c) - self.assertIs(self.factory.depth_c, new_depth_c) + assert self.factory.depth_c is new_depth_c def test_depth_c_non_scalar(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): + new_depth_c = Mock(units=Unit("m"), nbounds=0, shape=(10,)) + with pytest.raises(ValueError): self.factory.update(self.depth_c, new_depth_c) def test_depth_c_incompatible_units(self): - new_depth_c = mock.Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) - with self.assertRaises(ValueError): + new_depth_c = Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) + with pytest.raises(ValueError): self.factory.update(self.depth_c, new_depth_c) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py b/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py index e8bf322305..910e897590 100644 --- a/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py +++ b/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py @@ -7,76 +7,75 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock +from unittest.mock import Mock from cf_units import Unit import numpy as np +import pytest from iris.aux_factory import OceanSigmaFactory from iris.coords import AuxCoord, DimCoord -class Test___init__(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) +class Test___init__: + @pytest.fixture(autouse=True) + def _setup(self): + self.sigma = Mock(units=Unit("1"), nbounds=0) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) self.kwargs = dict(sigma=self.sigma, eta=self.eta, depth=self.depth) def test_insufficient_coordinates(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaFactory() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaFactory(sigma=None, eta=self.eta, depth=self.depth) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaFactory(sigma=self.sigma, eta=None, depth=self.depth) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaFactory(sigma=self.sigma, eta=self.eta, depth=None) def test_sigma_too_many_bounds(self): self.sigma.nbounds = 4 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaFactory(**self.kwargs) def test_sigma_incompatible_units(self): self.sigma.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaFactory(**self.kwargs) def test_eta_incompatible_units(self): self.eta.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaFactory(**self.kwargs) def test_depth_incompatible_units(self): self.depth.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaFactory(**self.kwargs) def test_promote_sigma_units_unknown_to_dimensionless(self): - sigma = mock.Mock(units=Unit("unknown"), nbounds=0) + sigma = Mock(units=Unit("unknown"), nbounds=0) self.kwargs["sigma"] = sigma factory = OceanSigmaFactory(**self.kwargs) - self.assertEqual("1", factory.dependencies["sigma"].units) + assert factory.dependencies["sigma"].units == "1" -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) +class Test_dependencies: + @pytest.fixture(autouse=True) + def _setup(self): + self.sigma = Mock(units=Unit("1"), nbounds=0) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) self.kwargs = dict(sigma=self.sigma, eta=self.eta, depth=self.depth) def test_values(self): factory = OceanSigmaFactory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) + assert factory.dependencies == self.kwargs -class Test_make_coord(tests.IrisTest): +class Test_make_coord: @staticmethod def coord_dims(coord): mapping = dict(sigma=(0,), eta=(1, 2), depth=(1, 2)) @@ -95,7 +94,8 @@ def derive(sigma, eta, depth, coord=True): ) return result - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.sigma = DimCoord(np.linspace(-0.05, -1, 5), long_name="sigma", units="1") self.eta = AuxCoord( np.arange(-1, 3, dtype=np.float64).reshape(2, 2), @@ -119,52 +119,49 @@ def test_derived_points(self): # Calculate the actual result. factory = OceanSigmaFactory(**self.kwargs) coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) + assert coord == expected_coord -class Test_update(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) +class Test_update: + @pytest.fixture(autouse=True) + def _setup(self): + self.sigma = Mock(units=Unit("1"), nbounds=0) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) self.kwargs = dict(sigma=self.sigma, eta=self.eta, depth=self.depth) self.factory = OceanSigmaFactory(**self.kwargs) def test_sigma(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=0) + new_sigma = Mock(units=Unit("1"), nbounds=0) self.factory.update(self.sigma, new_sigma) - self.assertIs(self.factory.sigma, new_sigma) + assert self.factory.sigma is new_sigma def test_sigma_too_many_bounds(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): + new_sigma = Mock(units=Unit("1"), nbounds=4) + with pytest.raises(ValueError): self.factory.update(self.sigma, new_sigma) def test_sigma_incompatible_units(self): - new_sigma = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_sigma = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.sigma, new_sigma) def test_eta(self): - new_eta = mock.Mock(units=Unit("m"), nbounds=0) + new_eta = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.eta, new_eta) - self.assertIs(self.factory.eta, new_eta) + assert self.factory.eta is new_eta def test_eta_incompatible_units(self): - new_eta = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_eta = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.eta, new_eta) def test_depth(self): - new_depth = mock.Mock(units=Unit("m"), nbounds=0) + new_depth = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.depth, new_depth) - self.assertIs(self.factory.depth, new_depth) + assert self.factory.depth is new_depth def test_depth_incompatible_units(self): - new_depth = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_depth = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.depth, new_depth) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py b/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py index 604daa9419..56991c01d9 100644 --- a/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py +++ b/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py @@ -7,27 +7,25 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock +from unittest.mock import Mock from cf_units import Unit import numpy as np +import pytest from iris.aux_factory import OceanSigmaZFactory from iris.coords import AuxCoord, DimCoord -class Test___init__(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.nsigma = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.zlev = mock.Mock(units=Unit("m"), nbounds=0) +class Test___init__: + @pytest.fixture(autouse=True) + def _setup(self): + self.sigma = Mock(units=Unit("1"), nbounds=0) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) + self.nsigma = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.zlev = Mock(units=Unit("m"), nbounds=0) self.kwargs = dict( sigma=self.sigma, eta=self.eta, @@ -38,9 +36,9 @@ def setUp(self): ) def test_insufficient_coordinates(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory( sigma=self.sigma, eta=self.eta, @@ -49,7 +47,7 @@ def test_insufficient_coordinates(self): nsigma=self.nsigma, zlev=None, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory( sigma=None, eta=None, @@ -58,7 +56,7 @@ def test_insufficient_coordinates(self): nsigma=self.nsigma, zlev=self.zlev, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory( sigma=self.sigma, eta=None, @@ -67,7 +65,7 @@ def test_insufficient_coordinates(self): nsigma=self.nsigma, zlev=self.zlev, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory( sigma=self.sigma, eta=None, @@ -76,7 +74,7 @@ def test_insufficient_coordinates(self): nsigma=self.nsigma, zlev=self.zlev, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory( sigma=self.sigma, eta=self.eta, @@ -88,69 +86,70 @@ def test_insufficient_coordinates(self): def test_sigma_too_many_bounds(self): self.sigma.nbounds = 4 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory(**self.kwargs) def test_zlev_too_many_bounds(self): self.zlev.nbounds = 4 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory(**self.kwargs) def test_sigma_zlev_same_boundedness(self): self.zlev.nbounds = 2 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory(**self.kwargs) def test_depth_c_non_scalar(self): self.depth_c.shape = (2,) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory(**self.kwargs) def test_nsigma_non_scalar(self): self.nsigma.shape = (4,) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory(**self.kwargs) def test_zlev_incompatible_units(self): self.zlev.units = Unit("Pa") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory(**self.kwargs) def test_sigma_incompatible_units(self): self.sigma.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory(**self.kwargs) def test_eta_incompatible_units(self): self.eta.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory(**self.kwargs) def test_depth_c_incompatible_units(self): self.depth_c.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory(**self.kwargs) def test_depth_incompatible_units(self): self.depth.units = Unit("km") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): OceanSigmaZFactory(**self.kwargs) def test_promote_sigma_units_unknown_to_dimensionless(self): - sigma = mock.Mock(units=Unit("unknown"), nbounds=0) + sigma = Mock(units=Unit("unknown"), nbounds=0) self.kwargs["sigma"] = sigma factory = OceanSigmaZFactory(**self.kwargs) - self.assertEqual("1", factory.dependencies["sigma"].units) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.nsigma = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.zlev = mock.Mock(units=Unit("m"), nbounds=0) + assert factory.dependencies["sigma"].units == "1" + + +class Test_dependencies: + @pytest.fixture(autouse=True) + def _setup(self): + self.sigma = Mock(units=Unit("1"), nbounds=0) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) + self.nsigma = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.zlev = Mock(units=Unit("m"), nbounds=0) self.kwargs = dict( sigma=self.sigma, eta=self.eta, @@ -162,10 +161,10 @@ def setUp(self): def test_values(self): factory = OceanSigmaZFactory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) + assert factory.dependencies == self.kwargs -class Test_make_coord(tests.IrisTest): +class Test_make_coord: @staticmethod def coord_dims(coord): mapping = dict( @@ -195,7 +194,8 @@ def derive(sigma, eta, depth, depth_c, nsigma, zlev, coord=True): ) return result - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.sigma = DimCoord( np.arange(5, dtype=np.float64) * 10, long_name="sigma", units="1" ) @@ -236,7 +236,7 @@ def test_derived_points(self): # Calculate the actual result. factory = OceanSigmaZFactory(**self.kwargs) coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) + assert coord == expected_coord def test_derived_points_with_bounds(self): self.sigma.guess_bounds() @@ -263,7 +263,7 @@ def test_derived_points_with_bounds(self): # Calculate the actual result. factory = OceanSigmaZFactory(**self.kwargs) coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) + assert coord == expected_coord def test_no_eta(self): # Broadcast expected points given the known dimensional mapping. @@ -279,7 +279,7 @@ def test_no_eta(self): self.kwargs["eta"] = None factory = OceanSigmaZFactory(**self.kwargs) coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) + assert coord == expected_coord def test_no_sigma(self): # Broadcast expected points given the known dimensional mapping. @@ -295,7 +295,7 @@ def test_no_sigma(self): self.kwargs["sigma"] = None factory = OceanSigmaZFactory(**self.kwargs) coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) + assert coord == expected_coord def test_no_depth_c(self): # Broadcast expected points given the known dimensional mapping. @@ -311,7 +311,7 @@ def test_no_depth_c(self): self.kwargs["depth_c"] = None factory = OceanSigmaZFactory(**self.kwargs) coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) + assert coord == expected_coord def test_no_depth(self): # Broadcast expected points given the known dimensional mapping. @@ -327,17 +327,18 @@ def test_no_depth(self): self.kwargs["depth"] = None factory = OceanSigmaZFactory(**self.kwargs) coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - -class Test_update(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.nsigma = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.zlev = mock.Mock(units=Unit("m"), nbounds=0) + assert coord == expected_coord + + +class Test_update: + @pytest.fixture(autouse=True) + def _setup(self): + self.sigma = Mock(units=Unit("1"), nbounds=0) + self.eta = Mock(units=Unit("m"), nbounds=0) + self.depth = Mock(units=Unit("m"), nbounds=0) + self.depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) + self.nsigma = Mock(units=Unit("1"), nbounds=0, shape=(1,)) + self.zlev = Mock(units=Unit("m"), nbounds=0) self.kwargs = dict( sigma=self.sigma, eta=self.eta, @@ -349,98 +350,94 @@ def setUp(self): self.factory = OceanSigmaZFactory(**self.kwargs) def test_sigma(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=0) + new_sigma = Mock(units=Unit("1"), nbounds=0) self.factory.update(self.sigma, new_sigma) - self.assertIs(self.factory.sigma, new_sigma) + assert self.factory.sigma is new_sigma def test_sigma_too_many_bounds(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): + new_sigma = Mock(units=Unit("1"), nbounds=4) + with pytest.raises(ValueError): self.factory.update(self.sigma, new_sigma) def test_sigma_zlev_same_boundedness(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=2) - with self.assertRaises(ValueError): + new_sigma = Mock(units=Unit("1"), nbounds=2) + with pytest.raises(ValueError): self.factory.update(self.sigma, new_sigma) def test_sigma_incompatible_units(self): - new_sigma = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_sigma = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.sigma, new_sigma) def test_eta(self): - new_eta = mock.Mock(units=Unit("m"), nbounds=0) + new_eta = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.eta, new_eta) - self.assertIs(self.factory.eta, new_eta) + assert self.factory.eta is new_eta def test_eta_incompatible_units(self): - new_eta = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_eta = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.eta, new_eta) def test_depth(self): - new_depth = mock.Mock(units=Unit("m"), nbounds=0) + new_depth = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.depth, new_depth) - self.assertIs(self.factory.depth, new_depth) + assert self.factory.depth is new_depth def test_depth_incompatible_units(self): - new_depth = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_depth = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.depth, new_depth) def test_depth_c(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) + new_depth_c = Mock(units=Unit("m"), nbounds=0, shape=(1,)) self.factory.update(self.depth_c, new_depth_c) - self.assertIs(self.factory.depth_c, new_depth_c) + assert self.factory.depth_c is new_depth_c def test_depth_c_non_scalar(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): + new_depth_c = Mock(units=Unit("m"), nbounds=0, shape=(10,)) + with pytest.raises(ValueError): self.factory.update(self.depth_c, new_depth_c) def test_depth_c_incompatible_units(self): - new_depth_c = mock.Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) - with self.assertRaises(ValueError): + new_depth_c = Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) + with pytest.raises(ValueError): self.factory.update(self.depth_c, new_depth_c) def test_nsigma(self): - new_nsigma = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) + new_nsigma = Mock(units=Unit("1"), nbounds=0, shape=(1,)) self.factory.update(self.nsigma, new_nsigma) - self.assertIs(self.factory.nsigma, new_nsigma) + assert self.factory.nsigma is new_nsigma def test_nsigma_missing(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.factory.update(self.nsigma, None) def test_nsigma_non_scalar(self): - new_nsigma = mock.Mock(units=Unit("1"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): + new_nsigma = Mock(units=Unit("1"), nbounds=0, shape=(10,)) + with pytest.raises(ValueError): self.factory.update(self.nsigma, new_nsigma) def test_zlev(self): - new_zlev = mock.Mock(units=Unit("m"), nbounds=0) + new_zlev = Mock(units=Unit("m"), nbounds=0) self.factory.update(self.zlev, new_zlev) - self.assertIs(self.factory.zlev, new_zlev) + assert self.factory.zlev is new_zlev def test_zlev_missing(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.factory.update(self.zlev, None) def test_zlev_too_many_bounds(self): - new_zlev = mock.Mock(units=Unit("m"), nbounds=4) - with self.assertRaises(ValueError): + new_zlev = Mock(units=Unit("m"), nbounds=4) + with pytest.raises(ValueError): self.factory.update(self.zlev, new_zlev) def test_zlev_same_boundedness(self): - new_zlev = mock.Mock(units=Unit("m"), nbounds=2) - with self.assertRaises(ValueError): + new_zlev = Mock(units=Unit("m"), nbounds=2) + with pytest.raises(ValueError): self.factory.update(self.zlev, new_zlev) def test_zlev_incompatible_units(self): - new_zlev = new_zlev = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): + new_zlev = new_zlev = Mock(units=Unit("Pa"), nbounds=0) + with pytest.raises(ValueError): self.factory.update(self.zlev, new_zlev) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py index 375a745ce8..cbc1c8fe1f 100644 --- a/lib/iris/tests/unit/common/lenient/test_Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test_Lenient.py @@ -4,179 +4,166 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.common.lenient.Lenient`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip +import pytest -from unittest.mock import sentinel +from iris.common.lenient import _LENIENT, _LENIENT_PROTECTED, Lenient -from iris.common.lenient import _LENIENT, Lenient +@pytest.fixture() +def lenient(): + # setup + state = {key: _LENIENT.__dict__[key] for key in _LENIENT_PROTECTED} + # call + yield Lenient() + # teardown + for key, value in state.items(): + _LENIENT.__dict__[key] = value -class Test___init__(tests.IrisTest): - def test_default(self): - lenient = Lenient() + +class Test___init__: + def test_default(self, lenient): expected = dict(maths=True) - self.assertEqual(expected, lenient.__dict__) + assert lenient.__dict__ == expected - def test_kwargs(self): - lenient = Lenient(maths=False) + def test_kwargs(self, lenient): + actual = Lenient(maths=False) expected = dict(maths=False) - self.assertEqual(expected, lenient.__dict__) + assert actual.__dict__ == expected - def test_kwargs_invalid(self): + def test_kwargs_invalid(self, lenient): emsg = "Invalid .* option, got 'merge'." - with self.assertRaisesRegex(KeyError, emsg): + with pytest.raises(KeyError, match=emsg): _ = Lenient(merge=True) -class Test___contains__(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() - - def test_in(self): - self.assertIn("maths", self.lenient) +class Test___contains__: + def test_in(self, lenient): + assert "maths" in lenient - def test_not_in(self): - self.assertNotIn("concatenate", self.lenient) + def test_not_in(self, lenient): + assert "concatenate" not in lenient -class Test___getitem__(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() +class Test___getitem__: + def test_in(self, lenient): + assert bool(lenient["maths"]) is True - def test_in(self): - self.assertTrue(self.lenient["maths"]) - - def test_not_in(self): + def test_not_in(self, lenient): emsg = "Invalid .* option, got 'MATHS'." - with self.assertRaisesRegex(KeyError, emsg): - _ = self.lenient["MATHS"] - + with pytest.raises(KeyError, match=emsg): + _ = lenient["MATHS"] -class Test___repr__(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() - def test(self): +class Test___repr__: + def test(self, lenient): expected = "Lenient(maths=True)" - self.assertEqual(expected, repr(self.lenient)) + assert repr(lenient) == expected -class Test___setitem__(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() - - def test_key_invalid(self): +class Test___setitem__: + def test_key_invalid(self, lenient): emsg = "Invalid .* option, got 'MATHS." - with self.assertRaisesRegex(KeyError, emsg): - self.lenient["MATHS"] = False + with pytest.raises(KeyError, match=emsg): + lenient["MATHS"] = False - def test_maths_value_invalid(self): - value = sentinel.value + def test_maths_value_invalid(self, mocker, lenient): + value = mocker.sentinel.value emsg = f"Invalid .* option 'maths' value, got {value!r}." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient["maths"] = value + with pytest.raises(ValueError, match=emsg): + lenient["maths"] = value - def test_maths_disable__lenient_enable_true(self): - self.assertTrue(_LENIENT.enable) - self.lenient["maths"] = False - self.assertFalse(self.lenient.__dict__["maths"]) - self.assertFalse(_LENIENT.enable) + def test_maths_disable__lenient_enable_true(self, lenient): + assert bool(_LENIENT.enable) is True + lenient["maths"] = False + assert bool(lenient.__dict__["maths"]) is False + assert bool(_LENIENT.enable) is False - def test_maths_disable__lenient_enable_false(self): + def test_maths_disable__lenient_enable_false(self, lenient): _LENIENT.__dict__["enable"] = False - self.assertFalse(_LENIENT.enable) - self.lenient["maths"] = False - self.assertFalse(self.lenient.__dict__["maths"]) - self.assertFalse(_LENIENT.enable) - - def test_maths_enable__lenient_enable_true(self): - self.assertTrue(_LENIENT.enable) - self.lenient["maths"] = True - self.assertTrue(self.lenient.__dict__["maths"]) - self.assertTrue(_LENIENT.enable) - - def test_maths_enable__lenient_enable_false(self): + assert bool(_LENIENT.enable) is False + lenient["maths"] = False + assert bool(lenient.__dict__["maths"]) is False + assert bool(_LENIENT.enable) is False + + def test_maths_enable__lenient_enable_true(self, lenient): + assert bool(_LENIENT.enable) is True + lenient["maths"] = True + assert bool(lenient.__dict__["maths"]) is True + assert bool(_LENIENT.enable) is True + + def test_maths_enable__lenient_enable_false(self, lenient): _LENIENT.__dict__["enable"] = False - self.assertFalse(_LENIENT.enable) - self.lenient["maths"] = True - self.assertTrue(self.lenient.__dict__["maths"]) - self.assertTrue(_LENIENT.enable) - + assert bool(_LENIENT.enable) is False + lenient["maths"] = True + assert bool(lenient.__dict__["maths"]) is True + assert bool(_LENIENT.enable) is True -class Test_context(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() - def test_nop(self): - self.assertTrue(self.lenient["maths"]) +class Test_context: + def test_nop(self, lenient): + assert bool(lenient["maths"]) is True - with self.lenient.context(): - self.assertTrue(self.lenient["maths"]) + with lenient.context(): + assert bool(lenient["maths"]) is True - self.assertTrue(self.lenient["maths"]) + assert bool(lenient["maths"]) is True - def test_maths_disable__lenient_true(self): + def test_maths_disable__lenient_true(self, lenient): # synchronised - self.assertTrue(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) + assert bool(_LENIENT.enable) is True + assert bool(lenient["maths"]) is True - with self.lenient.context(maths=False): + with lenient.context(maths=False): # still synchronised - self.assertFalse(_LENIENT.enable) - self.assertFalse(self.lenient["maths"]) + assert bool(_LENIENT.enable) is False + assert bool(lenient["maths"]) is False # still synchronised - self.assertTrue(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) + assert bool(_LENIENT.enable) is True + assert bool(lenient["maths"]) is True - def test_maths_disable__lenient_false(self): + def test_maths_disable__lenient_false(self, lenient): # not synchronised _LENIENT.__dict__["enable"] = False - self.assertFalse(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) + assert bool(_LENIENT.enable) is False + assert bool(lenient["maths"]) is True - with self.lenient.context(maths=False): + with lenient.context(maths=False): # now synchronised - self.assertFalse(_LENIENT.enable) - self.assertFalse(self.lenient["maths"]) + assert bool(_LENIENT.enable) is False + assert bool(lenient["maths"]) is False # still synchronised - self.assertTrue(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) + assert bool(_LENIENT.enable) is True + assert bool(lenient["maths"]) is True - def test_maths_enable__lenient_true(self): + def test_maths_enable__lenient_true(self, lenient): # not synchronised - self.assertTrue(_LENIENT.enable) - self.lenient.__dict__["maths"] = False - self.assertFalse(self.lenient["maths"]) + assert bool(_LENIENT.enable) is True + lenient.__dict__["maths"] = False + assert bool(lenient["maths"]) is False - with self.lenient.context(maths=True): + with lenient.context(maths=True): # now synchronised - self.assertTrue(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) + assert bool(_LENIENT.enable) is True + assert bool(lenient["maths"]) is True # still synchronised - self.assertFalse(_LENIENT.enable) - self.assertFalse(self.lenient["maths"]) + assert bool(_LENIENT.enable) is False + assert bool(lenient["maths"]) is False - def test_maths_enable__lenient_false(self): + def test_maths_enable__lenient_false(self, lenient): # synchronised _LENIENT.__dict__["enable"] = False - self.assertFalse(_LENIENT.enable) - self.lenient.__dict__["maths"] = False - self.assertFalse(self.lenient["maths"]) + assert bool(_LENIENT.enable) is False + lenient.__dict__["maths"] = False + assert bool(lenient["maths"]) is False - with self.lenient.context(maths=True): + with lenient.context(maths=True): # still synchronised - self.assertTrue(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) + assert bool(_LENIENT.enable) is True + assert bool(lenient["maths"]) is True # still synchronised - self.assertFalse(_LENIENT.enable) - self.assertFalse(self.lenient["maths"]) - - -if __name__ == "__main__": - tests.main() + assert bool(_LENIENT.enable) is False + assert bool(lenient["maths"]) is False diff --git a/lib/iris/tests/unit/common/lenient/test__Lenient.py b/lib/iris/tests/unit/common/lenient/test__Lenient.py index 814359fbaf..bd19c3922e 100644 --- a/lib/iris/tests/unit/common/lenient/test__Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test__Lenient.py @@ -4,12 +4,10 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.common.lenient._Lenient`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from collections.abc import Iterable +import pytest + from iris.common.lenient import ( _LENIENT_ENABLE_DEFAULT, _LENIENT_PROTECTED, @@ -18,25 +16,30 @@ ) -class Test___init__(tests.IrisTest): - def setUp(self): +@pytest.fixture() +def lenient(): + return _Lenient() + + +class Test___init__: + @pytest.fixture(autouse=True) + def _setup(self): self.expected = dict(active=None, enable=_LENIENT_ENABLE_DEFAULT) - def test_default(self): - lenient = _Lenient() - self.assertEqual(self.expected, lenient.__dict__) + def test_default(self, lenient): + assert lenient.__dict__ == self.expected def test_args_service_str(self): service = "service1" lenient = _Lenient(service) self.expected.update(dict(service1=True)) - self.assertEqual(self.expected, lenient.__dict__) + assert lenient.__dict__ == self.expected def test_args_services_str(self): services = ("service1", "service2") lenient = _Lenient(*services) self.expected.update(dict(service1=True, service2=True)) - self.assertEqual(self.expected, lenient.__dict__) + assert lenient.__dict__ == self.expected def test_args_services_callable(self): def service1(): @@ -48,19 +51,19 @@ def service2(): services = (service1, service2) lenient = _Lenient(*services) self.expected.update({_qualname(service1): True, _qualname(service2): True}) - self.assertEqual(self.expected, lenient.__dict__) + assert lenient.__dict__ == self.expected def test_kwargs_client_str(self): client = dict(client1="service1") lenient = _Lenient(**client) self.expected.update(dict(client1=("service1",))) - self.assertEqual(self.expected, lenient.__dict__) + assert lenient.__dict__ == self.expected def test_kwargs_clients_str(self): clients = dict(client1="service1", client2="service2") lenient = _Lenient(**clients) self.expected.update(dict(client1=("service1",), client2=("service2",))) - self.assertEqual(self.expected, lenient.__dict__) + assert lenient.__dict__ == self.expected def test_kwargs_clients_callable(self): def client1(): @@ -88,27 +91,28 @@ def service2(): _qualname(client2): (_qualname(service1), _qualname(service2)), } ) - self.assertEqual(self.expected, lenient.__dict__) + assert lenient.__dict__ == self.expected -class Test___call__(tests.IrisTest): - def setUp(self): +class Test___call__: + @pytest.fixture(autouse=True) + def _setup(self): self.client = "myclient" self.lenient = _Lenient() def test_missing_service_str(self): - self.assertFalse(self.lenient("myservice")) + assert not self.lenient("myservice") def test_missing_service_callable(self): def myservice(): pass - self.assertFalse(self.lenient(myservice)) + assert not self.lenient(myservice) def test_disabled_service_str(self): service = "myservice" self.lenient.__dict__[service] = False - self.assertFalse(self.lenient(service)) + assert not self.lenient(service) def test_disable_service_callable(self): def myservice(): @@ -116,12 +120,12 @@ def myservice(): qualname_service = _qualname(myservice) self.lenient.__dict__[qualname_service] = False - self.assertFalse(self.lenient(myservice)) + assert not self.lenient(myservice) def test_service_str_with_no_active_client(self): service = "myservice" self.lenient.__dict__[service] = True - self.assertFalse(self.lenient(service)) + assert not self.lenient(service) def test_service_callable_with_no_active_client(self): def myservice(): @@ -129,13 +133,13 @@ def myservice(): qualname_service = _qualname(myservice) self.lenient.__dict__[qualname_service] = True - self.assertFalse(self.lenient(myservice)) + assert not self.lenient(myservice) def test_service_str_with_active_client_with_no_registered_services(self): service = "myservice" self.lenient.__dict__[service] = True self.lenient.__dict__["active"] = self.client - self.assertFalse(self.lenient(service)) + assert not self.lenient(service) def test_service_callable_with_active_client_with_no_registered_services( self, @@ -149,16 +153,14 @@ def myclient(): qualname_service = _qualname(myservice) self.lenient.__dict__[qualname_service] = True self.lenient.__dict__["active"] = _qualname(myclient) - self.assertFalse(self.lenient(myservice)) + assert not self.lenient(myservice) - def test_service_str_with_active_client_with_unmatched_registered_services( - self, - ): + def test_service_str_with_active_client_with_unmatched_registered_services(self): service = "myservice" self.lenient.__dict__[service] = True self.lenient.__dict__["active"] = self.client self.lenient.__dict__[self.client] = ("service1", "service2") - self.assertFalse(self.lenient(service)) + assert not self.lenient(service) def test_service_callable_with_active_client_with_unmatched_registered_services( self, @@ -174,18 +176,16 @@ def myclient(): self.lenient.__dict__[qualname_service] = True self.lenient.__dict__["active"] = qualname_client self.lenient.__dict__[qualname_client] = ("service1", "service2") - self.assertFalse(self.lenient(myservice)) + assert not self.lenient(myservice) def test_service_str_with_active_client_with_registered_services(self): service = "myservice" self.lenient.__dict__[service] = True self.lenient.__dict__["active"] = self.client self.lenient.__dict__[self.client] = ("service1", "service2", service) - self.assertTrue(self.lenient(service)) + assert self.lenient(service) - def test_service_callable_with_active_client_with_registered_services( - self, - ): + def test_service_callable_with_active_client_with_registered_services(self): def myservice(): pass @@ -201,7 +201,7 @@ def myclient(): "service2", qualname_service, ) - self.assertTrue(self.lenient(myservice)) + assert self.lenient(myservice) def test_service_str_with_active_client_with_unmatched_registered_service_str( self, @@ -210,7 +210,7 @@ def test_service_str_with_active_client_with_unmatched_registered_service_str( self.lenient.__dict__[service] = True self.lenient.__dict__["active"] = self.client self.lenient.__dict__[self.client] = "serviceXXX" - self.assertFalse(self.lenient(service)) + assert not self.lenient(service) def test_service_callable_with_active_client_with_unmatched_registered_service_str( self, @@ -226,14 +226,14 @@ def myclient(): self.lenient.__dict__[qualname_service] = True self.lenient.__dict__["active"] = qualname_client self.lenient.__dict__[qualname_client] = f"{qualname_service}XXX" - self.assertFalse(self.lenient(myservice)) + assert not self.lenient(myservice) def test_service_str_with_active_client_with_registered_service_str(self): service = "myservice" self.lenient.__dict__[service] = True self.lenient.__dict__["active"] = self.client self.lenient.__dict__[self.client] = service - self.assertTrue(self.lenient(service)) + assert self.lenient(service) def test_service_callable_with_active_client_with_registered_service_str( self, @@ -249,119 +249,106 @@ def myclient(): self.lenient.__dict__[qualname_service] = True self.lenient.__dict__["active"] = qualname_client self.lenient.__dict__[qualname_client] = qualname_service - self.assertTrue(self.lenient(myservice)) + assert self.lenient(myservice) def test_enable(self): service = "myservice" self.lenient.__dict__[service] = True self.lenient.__dict__["active"] = self.client self.lenient.__dict__[self.client] = service - self.assertTrue(self.lenient(service)) + assert self.lenient(service) self.lenient.__dict__["enable"] = False - self.assertFalse(self.lenient(service)) + assert not self.lenient(service) -class Test___contains__(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() +class Test___contains__: + def test_in(self, lenient): + assert "active" in lenient - def test_in(self): - self.assertIn("active", self.lenient) + def test_not_in(self, lenient): + assert "ACTIVATE" not in lenient - def test_not_in(self): - self.assertNotIn("ACTIVATE", self.lenient) - - def test_in_qualname(self): + def test_in_qualname(self, lenient): def func(): pass qualname_func = _qualname(func) - lenient = _Lenient() lenient.__dict__[qualname_func] = None - self.assertIn(func, lenient) - self.assertIn(qualname_func, lenient) - + assert func in lenient + assert qualname_func in lenient -class Test___getattr__(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - def test_in(self): - self.assertIsNone(self.lenient.active) +class Test___getattr__: + def test_in(self, lenient): + assert lenient.active is None - def test_not_in(self): + def test_not_in(self, lenient): emsg = "Invalid .* option, got 'wibble'." - with self.assertRaisesRegex(AttributeError, emsg): - _ = self.lenient.wibble - + with pytest.raises(AttributeError, match=emsg): + _ = lenient.wibble -class Test__getitem__(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - def test_in(self): - self.assertIsNone(self.lenient["active"]) +class Test__getitem__: + def test_in(self, lenient): + assert lenient["active"] is None - def test_in_callable(self): + def test_in_callable(self, lenient): def service(): pass qualname_service = _qualname(service) - self.lenient.__dict__[qualname_service] = True - self.assertTrue(self.lenient[service]) + lenient.__dict__[qualname_service] = True + assert lenient[service] - def test_not_in(self): + def test_not_in(self, lenient): emsg = "Invalid .* option, got 'wibble'." - with self.assertRaisesRegex(KeyError, emsg): - _ = self.lenient["wibble"] + with pytest.raises(KeyError, match=emsg): + _ = lenient["wibble"] - def test_not_in_callable(self): + def test_not_in_callable(self, lenient): def service(): pass qualname_service = _qualname(service) emsg = f"Invalid .* option, got '{qualname_service}'." - with self.assertRaisesRegex(KeyError, emsg): - _ = self.lenient[service] + with pytest.raises(KeyError, match=emsg): + _ = lenient[service] -class Test___setitem__(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_not_in(self): +class Test___setitem__: + def test_not_in(self, lenient): emsg = "Invalid .* option, got 'wibble'." - with self.assertRaisesRegex(KeyError, emsg): - self.lenient["wibble"] = None + with pytest.raises(KeyError, match=emsg): + lenient["wibble"] = None - def test_in_value_str(self): + def test_in_value_str(self, lenient): client = "client" service = "service" - self.lenient.__dict__[client] = None - self.lenient[client] = service - self.assertEqual(self.lenient.__dict__[client], (service,)) + lenient.__dict__[client] = None + lenient[client] = service + assert lenient.__dict__[client] == (service,) - def test_callable_in_value_str(self): + def test_callable_in_value_str(self, lenient): def client(): pass service = "service" qualname_client = _qualname(client) - self.lenient.__dict__[qualname_client] = None - self.lenient[client] = service - self.assertEqual(self.lenient.__dict__[qualname_client], (service,)) + lenient.__dict__[qualname_client] = None + lenient[client] = service + assert lenient.__dict__[qualname_client] == (service,) - def test_in_value_callable(self): + def test_in_value_callable(self, lenient): def service(): pass client = "client" qualname_service = _qualname(service) - self.lenient.__dict__[client] = None - self.lenient[client] = service - self.assertEqual(self.lenient.__dict__[client], (qualname_service,)) + lenient.__dict__[client] = None + lenient[client] = service + assert lenient.__dict__[client] == (qualname_service,) - def test_callable_in_value_callable(self): + def test_callable_in_value_callable(self, lenient): def client(): pass @@ -370,45 +357,45 @@ def service(): qualname_client = _qualname(client) qualname_service = _qualname(service) - self.lenient.__dict__[qualname_client] = None - self.lenient[client] = service - self.assertEqual(self.lenient.__dict__[qualname_client], (qualname_service,)) + lenient.__dict__[qualname_client] = None + lenient[client] = service + assert lenient.__dict__[qualname_client] == (qualname_service,) - def test_in_value_bool(self): + def test_in_value_bool(self, lenient): client = "client" - self.lenient.__dict__[client] = None - self.lenient[client] = True - self.assertTrue(self.lenient.__dict__[client]) - self.assertFalse(isinstance(self.lenient.__dict__[client], Iterable)) + lenient.__dict__[client] = None + lenient[client] = True + assert lenient.__dict__[client] + assert not isinstance(lenient.__dict__[client], Iterable) - def test_callable_in_value_bool(self): + def test_callable_in_value_bool(self, lenient): def client(): pass qualname_client = _qualname(client) - self.lenient.__dict__[qualname_client] = None - self.lenient[client] = True - self.assertTrue(self.lenient.__dict__[qualname_client]) - self.assertFalse(isinstance(self.lenient.__dict__[qualname_client], Iterable)) + lenient.__dict__[qualname_client] = None + lenient[client] = True + assert lenient.__dict__[qualname_client] + assert not isinstance(lenient.__dict__[qualname_client], Iterable) - def test_in_value_iterable(self): + def test_in_value_iterable(self, lenient): client = "client" services = ("service1", "service2") - self.lenient.__dict__[client] = None - self.lenient[client] = services - self.assertEqual(self.lenient.__dict__[client], services) + lenient.__dict__[client] = None + lenient[client] = services + assert lenient.__dict__[client] == services - def test_callable_in_value_iterable(self): + def test_callable_in_value_iterable(self, lenient): def client(): pass qualname_client = _qualname(client) services = ("service1", "service2") - self.lenient.__dict__[qualname_client] = None - self.lenient[client] = services - self.assertEqual(self.lenient.__dict__[qualname_client], services) + lenient.__dict__[qualname_client] = None + lenient[client] = services + assert lenient.__dict__[qualname_client] == services - def test_in_value_iterable_callable(self): + def test_in_value_iterable_callable(self, lenient): def service1(): pass @@ -416,12 +403,12 @@ def service2(): pass client = "client" - self.lenient.__dict__[client] = None + lenient.__dict__[client] = None qualname_services = (_qualname(service1), _qualname(service2)) - self.lenient[client] = (service1, service2) - self.assertEqual(self.lenient.__dict__[client], qualname_services) + lenient[client] = (service1, service2) + assert lenient.__dict__[client] == qualname_services - def test_callable_in_value_iterable_callable(self): + def test_callable_in_value_iterable_callable(self, lenient): def client(): pass @@ -432,51 +419,53 @@ def service2(): pass qualname_client = _qualname(client) - self.lenient.__dict__[qualname_client] = None + lenient.__dict__[qualname_client] = None qualname_services = (_qualname(service1), _qualname(service2)) - self.lenient[client] = (service1, service2) - self.assertEqual(self.lenient.__dict__[qualname_client], qualname_services) + lenient[client] = (service1, service2) + assert lenient.__dict__[qualname_client] == qualname_services - def test_active_iterable(self): + def test_active_iterable(self, lenient): active = "active" - self.assertIsNone(self.lenient.__dict__[active]) + assert lenient.__dict__[active] is None + emsg = "Invalid .* option 'active'" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient[active] = (None,) + with pytest.raises(ValueError, match=emsg): + lenient[active] = (None,) - def test_active_str(self): + def test_active_str(self, lenient): active = "active" client = "client1" - self.assertIsNone(self.lenient.__dict__[active]) - self.lenient[active] = client - self.assertEqual(self.lenient.__dict__[active], client) + assert lenient.__dict__[active] is None + lenient[active] = client + assert lenient.__dict__[active] == client - def test_active_callable(self): + def test_active_callable(self, lenient): def client(): pass active = "active" qualname_client = _qualname(client) - self.assertIsNone(self.lenient.__dict__[active]) - self.lenient[active] = client - self.assertEqual(self.lenient.__dict__[active], qualname_client) + assert lenient.__dict__[active] is None + lenient[active] = client + assert lenient.__dict__[active] == qualname_client - def test_enable(self): + def test_enable(self, lenient): enable = "enable" - self.assertEqual(self.lenient.__dict__[enable], _LENIENT_ENABLE_DEFAULT) - self.lenient[enable] = True - self.assertTrue(self.lenient.__dict__[enable]) - self.lenient[enable] = False - self.assertFalse(self.lenient.__dict__[enable]) + assert lenient.__dict__[enable] == _LENIENT_ENABLE_DEFAULT + lenient[enable] = True + assert lenient.__dict__[enable] + lenient[enable] = False + assert not lenient.__dict__[enable] - def test_enable_invalid(self): + def test_enable_invalid(self, lenient): emsg = "Invalid .* option 'enable'" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient["enable"] = None + with pytest.raises(ValueError, match=emsg): + lenient["enable"] = None -class Test_context(tests.IrisTest): - def setUp(self): +class Test_context: + @pytest.fixture(autouse=True) + def _setup(self): self.lenient = _Lenient() self.default = dict(active=None, enable=_LENIENT_ENABLE_DEFAULT) @@ -488,9 +477,9 @@ def test_nop(self): with self.lenient.context(): context = self.copy() post = self.copy() - self.assertEqual(pre, self.default) - self.assertEqual(context, self.default) - self.assertEqual(post, self.default) + assert pre == self.default + assert context == self.default + assert post == self.default def test_active_str(self): client = "client" @@ -498,11 +487,11 @@ def test_active_str(self): with self.lenient.context(active=client): context = self.copy() post = self.copy() - self.assertEqual(pre, self.default) + assert pre == self.default expected = self.default.copy() expected.update(dict(active=client)) - self.assertEqual(context, expected) - self.assertEqual(post, self.default) + assert context == expected + assert post == self.default def test_active_callable(self): def client(): @@ -513,11 +502,11 @@ def client(): context = self.copy() post = self.copy() qualname_client = _qualname(client) - self.assertEqual(pre, self.default) + assert pre == self.default expected = self.default.copy() expected.update(dict(active=qualname_client)) - self.assertEqual(context, expected) - self.assertEqual(post, self.default) + assert context == expected + assert post == self.default def test_kwargs(self): client = "client" @@ -528,11 +517,11 @@ def test_kwargs(self): context = self.copy() post = self.copy() self.default.update(dict(service1=False, service2=False)) - self.assertEqual(pre, self.default) + assert pre == self.default expected = self.default.copy() expected.update(dict(active=client, service1=True, service2=True)) - self.assertEqual(context, expected) - self.assertEqual(post, self.default) + assert context == expected + assert post == self.default def test_args_str(self): client = "client" @@ -541,12 +530,12 @@ def test_args_str(self): with self.lenient.context(*services, active=client): context = self.copy() post = self.copy() - self.assertEqual(pre, self.default) + assert pre == self.default expected = self.default.copy() expected.update(dict(active=client, client=services)) - self.assertEqual(context["active"], expected["active"]) - self.assertEqual(set(context["client"]), set(expected["client"])) - self.assertEqual(post, self.default) + assert context["active"] == expected["active"] + assert set(context["client"]) == set(expected["client"]) + assert post == self.default def test_args_callable(self): def service1(): @@ -562,12 +551,12 @@ def service2(): context = self.copy() post = self.copy() qualname_services = tuple([_qualname(service) for service in services]) - self.assertEqual(pre, self.default) + assert pre == self.default expected = self.default.copy() expected.update(dict(active=client, client=qualname_services)) - self.assertEqual(context["active"], expected["active"]) - self.assertEqual(set(context["client"]), set(expected["client"])) - self.assertEqual(post, self.default) + assert context["active"] == expected["active"] + assert set(context["client"]) == set(expected["client"]) + assert post == self.default def test_context_runtime(self): services = ("service1", "service2") @@ -575,56 +564,54 @@ def test_context_runtime(self): with self.lenient.context(*services): context = self.copy() post = self.copy() - self.assertEqual(pre, self.default) + assert pre == self.default expected = self.default.copy() expected.update(dict(active="__context", __context=services)) - self.assertEqual(context, expected) - self.assertEqual(post, self.default) + assert context == expected + assert post == self.default -class Test_enable(tests.IrisTest): - def setUp(self): +class Test_enable: + @pytest.fixture(autouse=True) + def _setup(self): self.lenient = _Lenient() def test_getter(self): - self.assertEqual(self.lenient.enable, _LENIENT_ENABLE_DEFAULT) + assert self.lenient.enable == _LENIENT_ENABLE_DEFAULT def test_setter_invalid(self): emsg = "Invalid .* option 'enable'" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): self.lenient.enable = 0 def test_setter(self): - self.assertEqual(self.lenient.enable, _LENIENT_ENABLE_DEFAULT) + assert self.lenient.enable == _LENIENT_ENABLE_DEFAULT self.lenient.enable = False - self.assertFalse(self.lenient.enable) + assert not self.lenient.enable -class Test_register_client(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_not_protected(self): +class Test_register_client: + def test_not_protected(self, lenient): emsg = "Cannot register .* client" for protected in _LENIENT_PROTECTED: - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.register_client(protected, "service") + with pytest.raises(ValueError, match=emsg): + lenient.register_client(protected, "service") - def test_str_service_str(self): + def test_str_service_str(self, lenient): client = "client" services = "service" - self.lenient.register_client(client, services) - self.assertIn(client, self.lenient.__dict__) - self.assertEqual(self.lenient.__dict__[client], (services,)) + lenient.register_client(client, services) + assert client in lenient.__dict__ + assert lenient.__dict__[client] == (services,) - def test_str_services_str(self): + def test_str_services_str(self, lenient): client = "client" services = ("service1", "service2") - self.lenient.register_client(client, services) - self.assertIn(client, self.lenient.__dict__) - self.assertEqual(self.lenient.__dict__[client], services) + lenient.register_client(client, services) + assert client in lenient.__dict__ + assert lenient.__dict__[client] == services - def test_callable_service_callable(self): + def test_callable_service_callable(self, lenient): def client(): pass @@ -633,11 +620,11 @@ def service(): qualname_client = _qualname(client) qualname_service = _qualname(service) - self.lenient.register_client(client, service) - self.assertIn(qualname_client, self.lenient.__dict__) - self.assertEqual(self.lenient.__dict__[qualname_client], (qualname_service,)) + lenient.register_client(client, service) + assert qualname_client in lenient.__dict__ + assert lenient.__dict__[qualname_client] == (qualname_service,) - def test_callable_services_callable(self): + def test_callable_services_callable(self, lenient): def client(): pass @@ -649,163 +636,150 @@ def service2(): qualname_client = _qualname(client) qualname_services = (_qualname(service1), _qualname(service2)) - self.lenient.register_client(client, (service1, service2)) - self.assertIn(qualname_client, self.lenient.__dict__) - self.assertEqual(self.lenient.__dict__[qualname_client], qualname_services) + lenient.register_client(client, (service1, service2)) + assert qualname_client in lenient.__dict__ + assert lenient.__dict__[qualname_client] == qualname_services - def test_services_empty(self): + def test_services_empty(self, lenient): emsg = "Require at least one .* client service." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.register_client("client", ()) + with pytest.raises(ValueError, match=emsg): + lenient.register_client("client", ()) - def test_services_overwrite(self): + def test_services_overwrite(self, lenient): client = "client" services = ("service1", "service2") - self.lenient.__dict__[client] = services - self.assertEqual(self.lenient[client], services) + lenient.__dict__[client] = services + assert lenient[client] == services new_services = ("service3", "service4") - self.lenient.register_client(client, services=new_services) - self.assertEqual(self.lenient[client], new_services) + lenient.register_client(client, services=new_services) + assert lenient[client] == new_services - def test_services_append(self): + def test_services_append(self, lenient): client = "client" services = ("service1", "service2") - self.lenient.__dict__[client] = services - self.assertEqual(self.lenient[client], services) + lenient.__dict__[client] = services + assert lenient[client] == services new_services = ("service3", "service4") - self.lenient.register_client(client, services=new_services, append=True) + lenient.register_client(client, services=new_services, append=True) expected = set(services + new_services) - self.assertEqual(set(self.lenient[client]), expected) + assert set(lenient[client]) == expected -class Test_register_service(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_str(self): +class Test_register_service: + def test_str(self, lenient): service = "service" - self.assertNotIn(service, self.lenient.__dict__) - self.lenient.register_service(service) - self.assertIn(service, self.lenient.__dict__) - self.assertFalse(isinstance(self.lenient.__dict__[service], Iterable)) - self.assertTrue(self.lenient.__dict__[service]) + assert service not in lenient.__dict__ + lenient.register_service(service) + assert service in lenient.__dict__ + assert not isinstance(lenient.__dict__[service], Iterable) + assert lenient.__dict__[service] - def test_callable(self): + def test_callable(self, lenient): def service(): pass qualname_service = _qualname(service) - self.assertNotIn(qualname_service, self.lenient.__dict__) - self.lenient.register_service(service) - self.assertIn(qualname_service, self.lenient.__dict__) - self.assertFalse(isinstance(self.lenient.__dict__[qualname_service], Iterable)) - self.assertTrue(self.lenient.__dict__[qualname_service]) + assert qualname_service not in lenient.__dict__ + lenient.register_service(service) + assert qualname_service in lenient.__dict__ + assert not isinstance(lenient.__dict__[qualname_service], Iterable) + assert lenient.__dict__[qualname_service] - def test_not_protected(self): + def test_not_protected(self, lenient): emsg = "Cannot register .* service" for protected in _LENIENT_PROTECTED: - self.lenient.__dict__[protected] = None - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.register_service("active") + lenient.__dict__[protected] = None + with pytest.raises(ValueError, match=emsg): + lenient.register_service("active") -class Test_unregister_client(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_not_protected(self): +class Test_unregister_client: + def test_not_protected(self, lenient): emsg = "Cannot unregister .* client, as .* is a protected .* option." for protected in _LENIENT_PROTECTED: - self.lenient.__dict__[protected] = None - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_client(protected) + lenient.__dict__[protected] = None + with pytest.raises(ValueError, match=emsg): + lenient.unregister_client(protected) - def test_not_in(self): + def test_not_in(self, lenient): emsg = "Cannot unregister unknown .* client" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_client("client") + with pytest.raises(ValueError, match=emsg): + lenient.unregister_client("client") - def test_not_client(self): + def test_not_client(self, lenient): client = "client" - self.lenient.__dict__[client] = True + lenient.__dict__[client] = True emsg = "Cannot unregister .* client, as .* is not a valid .* client." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_client(client) + with pytest.raises(ValueError, match=emsg): + lenient.unregister_client(client) - def test_not_client_callable(self): + def test_not_client_callable(self, lenient): def client(): pass qualname_client = _qualname(client) - self.lenient.__dict__[qualname_client] = True + lenient.__dict__[qualname_client] = True emsg = "Cannot unregister .* client, as .* is not a valid .* client." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_client(client) + with pytest.raises(ValueError, match=emsg): + lenient.unregister_client(client) - def test_str(self): + def test_str(self, lenient): client = "client" - self.lenient.__dict__[client] = (None,) - self.lenient.unregister_client(client) - self.assertNotIn(client, self.lenient.__dict__) + lenient.__dict__[client] = (None,) + lenient.unregister_client(client) + assert client not in lenient.__dict__ - def test_callable(self): + def test_callable(self, lenient): def client(): pass qualname_client = _qualname(client) - self.lenient.__dict__[qualname_client] = (None,) - self.lenient.unregister_client(client) - self.assertNotIn(qualname_client, self.lenient.__dict__) + lenient.__dict__[qualname_client] = (None,) + lenient.unregister_client(client) + assert qualname_client not in lenient.__dict__ -class Test_unregister_service(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_not_protected(self): +class Test_unregister_service: + def test_not_protected(self, lenient): emsg = "Cannot unregister .* service, as .* is a protected .* option." for protected in _LENIENT_PROTECTED: - self.lenient.__dict__[protected] = None - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_service(protected) + lenient.__dict__[protected] = None + with pytest.raises(ValueError, match=emsg): + lenient.unregister_service(protected) - def test_not_in(self): + def test_not_in(self, lenient): emsg = "Cannot unregister unknown .* service" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_service("service") + with pytest.raises(ValueError, match=emsg): + lenient.unregister_service("service") - def test_not_service(self): + def test_not_service(self, lenient): service = "service" - self.lenient.__dict__[service] = (None,) + lenient.__dict__[service] = (None,) emsg = "Cannot unregister .* service, as .* is not a valid .* service." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_service(service) + with pytest.raises(ValueError, match=emsg): + lenient.unregister_service(service) - def test_not_service_callable(self): + def test_not_service_callable(self, lenient): def service(): pass qualname_service = _qualname(service) - self.lenient.__dict__[qualname_service] = (None,) + lenient.__dict__[qualname_service] = (None,) emsg = "Cannot unregister .* service, as .* is not a valid .* service." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_service(service) + with pytest.raises(ValueError, match=emsg): + lenient.unregister_service(service) - def test_str(self): + def test_str(self, lenient): service = "service" - self.lenient.__dict__[service] = True - self.lenient.unregister_service(service) - self.assertNotIn(service, self.lenient.__dict__) + lenient.__dict__[service] = True + lenient.unregister_service(service) + assert service not in lenient.__dict__ - def test_callable(self): + def test_callable(self, lenient): def service(): pass qualname_service = _qualname(service) - self.lenient.__dict__[qualname_service] = True - self.lenient.unregister_service(service) - self.assertNotIn(qualname_service, self.lenient.__dict__) - - -if __name__ == "__main__": - tests.main() + lenient.__dict__[qualname_service] = True + lenient.unregister_service(service) + assert qualname_service not in lenient.__dict__ diff --git a/lib/iris/tests/unit/common/lenient/test__lenient_client.py b/lib/iris/tests/unit/common/lenient/test__lenient_client.py index 509b183003..01e1853007 100644 --- a/lib/iris/tests/unit/common/lenient/test__lenient_client.py +++ b/lib/iris/tests/unit/common/lenient/test__lenient_client.py @@ -4,42 +4,42 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :func:`iris.common.lenient._lenient_client`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from inspect import getmodule -from unittest.mock import sentinel + +import pytest from iris.common.lenient import _LENIENT, _lenient_client -class Test(tests.IrisTest): - def setUp(self): +class Test: + @pytest.fixture(autouse=True) + def _setup(self, mocker): module_name = getmodule(self).__name__ self.client = f"{module_name}" + ".Test.{}..myclient" self.service = f"{module_name}" + ".Test.{}..myservice" self.active = "active" - self.args_in = sentinel.arg1, sentinel.arg2 - self.kwargs_in = dict(kwarg1=sentinel.kwarg1, kwarg2=sentinel.kwarg2) + self.args_in = mocker.sentinel.arg1, mocker.sentinel.arg2 + self.kwargs_in = dict( + kwarg1=mocker.sentinel.kwarg1, kwarg2=mocker.sentinel.kwarg2 + ) def test_args_too_many(self): emsg = "Invalid lenient client arguments, expecting 1" - with self.assertRaisesRegex(AssertionError, emsg): - _lenient_client(None, None) + with pytest.raises(AssertionError, match=emsg): + _ = _lenient_client(None, None) def test_args_not_callable(self): emsg = "Invalid lenient client argument, expecting a callable" - with self.assertRaisesRegex(AssertionError, emsg): - _lenient_client(None) + with pytest.raises(AssertionError, match=emsg): + _ = _lenient_client(None) def test_args_and_kwargs(self): def func(): pass emsg = "Invalid lenient client, got both arguments and keyword arguments" - with self.assertRaisesRegex(AssertionError, emsg): - _lenient_client(func, services=func) + with pytest.raises(AssertionError, match=emsg): + _ = _lenient_client(func, services=func) def test_call_naked(self): @_lenient_client @@ -47,20 +47,20 @@ def myclient(): return _LENIENT.__dict__.copy() result = myclient() - self.assertIn(self.active, result) + assert self.active in result qualname_client = self.client.format("test_call_naked") - self.assertEqual(result[self.active], qualname_client) - self.assertNotIn(qualname_client, result) + assert result[self.active] == qualname_client + assert qualname_client not in result def test_call_naked_alternative(self): def myclient(): return _LENIENT.__dict__.copy() result = _lenient_client(myclient)() - self.assertIn(self.active, result) + assert self.active in result qualname_client = self.client.format("test_call_naked_alternative") - self.assertEqual(result[self.active], qualname_client) - self.assertNotIn(qualname_client, result) + assert result[self.active] == qualname_client + assert qualname_client not in result def test_call_naked_client_args_kwargs(self): @_lenient_client @@ -68,15 +68,15 @@ def myclient(*args, **kwargs): return args, kwargs args_out, kwargs_out = myclient(*self.args_in, **self.kwargs_in) - self.assertEqual(args_out, self.args_in) - self.assertEqual(kwargs_out, self.kwargs_in) + assert args_out == self.args_in + assert kwargs_out == self.kwargs_in def test_call_naked_doc(self): @_lenient_client def myclient(): """Myclient doc-string.""" - self.assertEqual(myclient.__doc__, "Myclient doc-string.") + assert myclient.__doc__ == "Myclient doc-string." def test_call_no_kwargs(self): @_lenient_client() @@ -84,20 +84,20 @@ def myclient(): return _LENIENT.__dict__.copy() result = myclient() - self.assertIn(self.active, result) + assert self.active in result qualname_client = self.client.format("test_call_no_kwargs") - self.assertEqual(result[self.active], qualname_client) - self.assertNotIn(qualname_client, result) + assert result[self.active] == qualname_client + assert qualname_client not in result def test_call_no_kwargs_alternative(self): def myclient(): return _LENIENT.__dict__.copy() result = (_lenient_client())(myclient)() - self.assertIn(self.active, result) + assert self.active in result qualname_client = self.client.format("test_call_no_kwargs_alternative") - self.assertEqual(result[self.active], qualname_client) - self.assertNotIn(qualname_client, result) + assert result[self.active] == qualname_client + assert qualname_client not in result def test_call_kwargs_none(self): @_lenient_client(services=None) @@ -105,24 +105,24 @@ def myclient(): return _LENIENT.__dict__.copy() result = myclient() - self.assertIn(self.active, result) + assert self.active in result qualname_client = self.client.format("test_call_kwargs_none") - self.assertEqual(result[self.active], qualname_client) - self.assertNotIn(qualname_client, result) + assert result[self.active] == qualname_client + assert qualname_client not in result - def test_call_kwargs_single(self): - service = sentinel.service + def test_call_kwargs_single(self, mocker): + service = mocker.sentinel.service @_lenient_client(services=service) def myclient(): return _LENIENT.__dict__.copy() result = myclient() - self.assertIn(self.active, result) + assert self.active in result qualname_client = self.client.format("test_call_kwargs_single") - self.assertEqual(result[self.active], qualname_client) - self.assertIn(qualname_client, result) - self.assertEqual(result[qualname_client], (service,)) + assert result[self.active] == qualname_client + assert qualname_client in result + assert result[qualname_client] == (service,) def test_call_kwargs_single_callable(self): def myservice(): @@ -134,26 +134,26 @@ def myclient(): test_name = "test_call_kwargs_single_callable" result = myclient() - self.assertIn(self.active, result) + assert self.active in result qualname_client = self.client.format(test_name) - self.assertEqual(result[self.active], qualname_client) - self.assertIn(qualname_client, result) + assert result[self.active] == qualname_client + assert qualname_client in result qualname_services = (self.service.format(test_name),) - self.assertEqual(result[qualname_client], qualname_services) + assert result[qualname_client] == qualname_services - def test_call_kwargs_iterable(self): - services = (sentinel.service1, sentinel.service2) + def test_call_kwargs_iterable(self, mocker): + services = (mocker.sentinel.service1, mocker.sentinel.service2) @_lenient_client(services=services) def myclient(): return _LENIENT.__dict__.copy() result = myclient() - self.assertIn(self.active, result) + assert self.active in result qualname_client = self.client.format("test_call_kwargs_iterable") - self.assertEqual(result[self.active], qualname_client) - self.assertIn(qualname_client, result) - self.assertEqual(set(result[qualname_client]), set(services)) + assert result[self.active] == qualname_client + assert qualname_client in result + assert set(result[qualname_client]) == set(services) def test_call_client_args_kwargs(self): @_lenient_client() @@ -161,16 +161,12 @@ def myclient(*args, **kwargs): return args, kwargs args_out, kwargs_out = myclient(*self.args_in, **self.kwargs_in) - self.assertEqual(args_out, self.args_in) - self.assertEqual(kwargs_out, self.kwargs_in) + assert args_out == self.args_in + assert kwargs_out == self.kwargs_in def test_call_doc(self): @_lenient_client() def myclient(): """Myclient doc-string.""" - self.assertEqual(myclient.__doc__, "Myclient doc-string.") - - -if __name__ == "__main__": - tests.main() + assert myclient.__doc__ == "Myclient doc-string." diff --git a/lib/iris/tests/unit/common/lenient/test__lenient_service.py b/lib/iris/tests/unit/common/lenient/test__lenient_service.py index c0ed8df403..d89bbc977b 100644 --- a/lib/iris/tests/unit/common/lenient/test__lenient_service.py +++ b/lib/iris/tests/unit/common/lenient/test__lenient_service.py @@ -4,31 +4,31 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :func:`iris.common.lenient._lenient_service`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from inspect import getmodule -from unittest.mock import sentinel + +import pytest from iris.common.lenient import _LENIENT, _lenient_service -class Test(tests.IrisTest): - def setUp(self): +class Test: + @pytest.fixture(autouse=True) + def _setup(self, mocker): module_name = getmodule(self).__name__ self.service = f"{module_name}" + ".Test.{}..myservice" - self.args_in = sentinel.arg1, sentinel.arg2 - self.kwargs_in = dict(kwarg1=sentinel.kwarg1, kwarg2=sentinel.kwarg2) + self.args_in = mocker.sentinel.arg1, mocker.sentinel.arg2 + self.kwargs_in = dict( + kwarg1=mocker.sentinel.kwarg1, kwarg2=mocker.sentinel.kwarg2 + ) def test_args_too_many(self): emsg = "Invalid lenient service arguments, expecting 1" - with self.assertRaisesRegex(AssertionError, emsg): + with pytest.raises(AssertionError, match=emsg): _lenient_service(None, None) def test_args_not_callable(self): emsg = "Invalid lenient service argument, expecting a callable" - with self.assertRaisesRegex(AssertionError, emsg): + with pytest.raises(AssertionError, match=emsg): _lenient_service(None) def test_call_naked(self): @@ -38,11 +38,11 @@ def myservice(): qualname_service = self.service.format("test_call_naked") state = _LENIENT.__dict__ - self.assertIn(qualname_service, state) - self.assertTrue(state[qualname_service]) + assert qualname_service in state + assert state[qualname_service] result = myservice() - self.assertIn(qualname_service, result) - self.assertTrue(result[qualname_service]) + assert qualname_service in result + assert result[qualname_service] def test_call_naked_alternative(self): def myservice(): @@ -50,8 +50,8 @@ def myservice(): qualname_service = self.service.format("test_call_naked_alternative") result = _lenient_service(myservice)() - self.assertIn(qualname_service, result) - self.assertTrue(result[qualname_service]) + assert qualname_service in result + assert result[qualname_service] def test_call_naked_service_args_kwargs(self): @_lenient_service @@ -59,15 +59,15 @@ def myservice(*args, **kwargs): return args, kwargs args_out, kwargs_out = myservice(*self.args_in, **self.kwargs_in) - self.assertEqual(args_out, self.args_in) - self.assertEqual(kwargs_out, self.kwargs_in) + assert args_out == self.args_in + assert kwargs_out == self.kwargs_in def test_call_naked_doc(self): @_lenient_service def myservice(): """Myservice doc-string.""" - self.assertEqual(myservice.__doc__, "Myservice doc-string.") + assert myservice.__doc__ == "Myservice doc-string." def test_call(self): @_lenient_service() @@ -76,11 +76,11 @@ def myservice(): qualname_service = self.service.format("test_call") state = _LENIENT.__dict__ - self.assertIn(qualname_service, state) - self.assertTrue(state[qualname_service]) + assert qualname_service in state + assert state[qualname_service] result = myservice() - self.assertIn(qualname_service, result) - self.assertTrue(result[qualname_service]) + assert qualname_service in result + assert result[qualname_service] def test_call_alternative(self): def myservice(): @@ -88,8 +88,8 @@ def myservice(): qualname_service = self.service.format("test_call_alternative") result = (_lenient_service())(myservice)() - self.assertIn(qualname_service, result) - self.assertTrue(result[qualname_service]) + assert qualname_service in result + assert result[qualname_service] def test_call_service_args_kwargs(self): @_lenient_service() @@ -97,16 +97,12 @@ def myservice(*args, **kwargs): return args, kwargs args_out, kwargs_out = myservice(*self.args_in, **self.kwargs_in) - self.assertEqual(args_out, self.args_in) - self.assertEqual(kwargs_out, self.kwargs_in) + assert args_out == self.args_in + assert kwargs_out == self.kwargs_in def test_call_doc(self): @_lenient_service() def myservice(): """Myservice doc-string.""" - self.assertEqual(myservice.__doc__, "Myservice doc-string.") - - -if __name__ == "__main__": - tests.main() + assert myservice.__doc__ == "Myservice doc-string." diff --git a/lib/iris/tests/unit/common/lenient/test__qualname.py b/lib/iris/tests/unit/common/lenient/test__qualname.py index 49576814d4..69d2d229e1 100644 --- a/lib/iris/tests/unit/common/lenient/test__qualname.py +++ b/lib/iris/tests/unit/common/lenient/test__qualname.py @@ -4,25 +4,23 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :func:`iris.common.lenient._qualname`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from inspect import getmodule -from unittest.mock import sentinel + +import pytest from iris.common.lenient import _qualname -class Test(tests.IrisTest): - def setUp(self): +class Test: + @pytest.fixture(autouse=True) + def _setup(self): module_name = getmodule(self).__name__ self.locals = f"{module_name}" + ".Test.{}..{}" - def test_pass_thru_non_callable(self): - func = sentinel.func + def test_pass_thru_non_callable(self, mocker): + func = mocker.sentinel.func result = _qualname(func) - self.assertEqual(result, func) + assert result == func def test_callable_function_local(self): def myfunc(): @@ -30,13 +28,13 @@ def myfunc(): qualname_func = self.locals.format("test_callable_function_local", "myfunc") result = _qualname(myfunc) - self.assertEqual(result, qualname_func) + assert result == qualname_func def test_callable_function(self): import iris result = _qualname(iris.load) - self.assertEqual(result, "iris.load") + assert result == "iris.load" def test_callable_method_local(self): class MyClass: @@ -47,14 +45,10 @@ def mymethod(self): "test_callable_method_local", "MyClass.mymethod" ) result = _qualname(MyClass.mymethod) - self.assertEqual(result, qualname_method) + assert result == qualname_method def test_callable_method(self): import iris result = _qualname(iris.cube.Cube.add_ancillary_variable) - self.assertEqual(result, "iris.cube.Cube.add_ancillary_variable") - - -if __name__ == "__main__": - tests.main() + assert result == "iris.cube.Cube.add_ancillary_variable" diff --git a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py index b7304f4301..a12af28242 100644 --- a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py @@ -4,25 +4,22 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.common.metadata.AncillaryVariableMetadata`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel + +import pytest from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import AncillaryVariableMetadata, BaseMetadata -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes +class Test: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.standard_name = mocker.sentinel.standard_name + self.long_name = mocker.sentinel.long_name + self.var_name = mocker.sentinel.var_name + self.units = mocker.sentinel.units + self.attributes = mocker.sentinel.attributes self.cls = AncillaryVariableMetadata def test_repr(self): @@ -44,7 +41,7 @@ def test_repr(self): self.units, self.attributes, ) - self.assertEqual(expected, repr(metadata)) + assert repr(metadata) == expected def test__fields(self): expected = ( @@ -54,107 +51,108 @@ def test__fields(self): "units", "attributes", ) - self.assertEqual(self.cls._fields, expected) + assert self.cls._fields == expected def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) + assert issubclass(self.cls, BaseMetadata) -class Test___eq__(tests.IrisTest): - def setUp(self): +class Test___eq__: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, ) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = AncillaryVariableMetadata def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__) + assert self.cls.__eq__.__doc__ == BaseMetadata.__eq__.__doc__ def test_lenient_service(self): qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) + assert qualname___eq__ in _LENIENT + assert _LENIENT[qualname___eq__] + assert _LENIENT[self.cls.__eq__] - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value + def test_call(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_op_lenient_same(self): + + patcher = mocker.patch.object(BaseMetadata, "__eq__", return_value=return_value) + result = metadata.__eq__(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == {} + + def test_op_lenient_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.__eq__(rmetadata) + assert rmetadata.__eq__(lmetadata) - def test_op_lenient_same_none(self): + def test_op_lenient_same_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.__eq__(rmetadata) + assert rmetadata.__eq__(lmetadata) - def test_op_lenient_different(self): + def test_op_lenient_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - def test_op_strict_same(self): + def test_op_strict_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.__eq__(rmetadata) + assert rmetadata.__eq__(lmetadata) - def test_op_strict_different(self): + def test_op_strict_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - def test_op_strict_different_none(self): + def test_op_strict_different_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) -class Test___lt__(tests.IrisTest): - def setUp(self): +class Test___lt__: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = AncillaryVariableMetadata self.one = self.cls(1, 1, 1, 1, 1) self.two = self.cls(1, 1, 1, 2, 1) @@ -163,99 +161,100 @@ def setUp(self): def test__ascending_lt(self): result = self.one < self.two - self.assertTrue(result) + assert result def test__descending_lt(self): result = self.two < self.one - self.assertFalse(result) + assert not result def test__none_rhs_operand(self): result = self.one < self.none - self.assertFalse(result) + assert not result def test__none_lhs_operand(self): result = self.none < self.one - self.assertTrue(result) + assert result def test__ignore_attributes(self): result = self.one < self.attributes - self.assertFalse(result) + assert not result result = self.attributes < self.one - self.assertFalse(result) + assert not result -class Test_combine(tests.IrisTest): - def setUp(self): +class Test_combine: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, ) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = AncillaryVariableMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.combine.__doc__, self.cls.combine.__doc__) + assert self.cls.combine.__doc__ == BaseMetadata.combine.__doc__ def test_lenient_service(self): qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( + assert qualname_combine in _LENIENT + assert _LENIENT[qualname_combine] + assert _LENIENT[self.cls.combine] + + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( + ) + result = self.none.combine(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=None) + + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) + ) + result = self.none.combine(other, lenient=lenient) - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=lenient) - def test_op_lenient_same(self): + def test_op_lenient_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) expected = self.values - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_lenient_same_none(self): + def test_op_lenient_same_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) expected = self.values - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_lenient_different(self): + def test_op_lenient_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy @@ -263,20 +262,20 @@ def test_op_lenient_different(self): expected = self.values.copy() expected["units"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_strict_same(self): + def test_op_strict_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) expected = self.values.copy() - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_strict_different(self): + def test_op_strict_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy @@ -284,11 +283,11 @@ def test_op_strict_different(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_strict_different_none(self): + def test_op_strict_different_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None @@ -296,81 +295,82 @@ def test_op_strict_different_none(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected -class Test_difference(tests.IrisTest): - def setUp(self): +class Test_difference: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, ) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = AncillaryVariableMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.difference.__doc__, self.cls.difference.__doc__) + assert self.cls.difference.__doc__ == BaseMetadata.difference.__doc__ def test_lenient_service(self): qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( + assert qualname_difference in _LENIENT + assert _LENIENT[qualname_difference] + assert _LENIENT[self.cls.difference] + + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( + ) + result = self.none.difference(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=None) + + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) + ) + result = self.none.difference(other, lenient=lenient) - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=lenient) - def test_op_lenient_same(self): + def test_op_lenient_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None - def test_op_lenient_same_none(self): + def test_op_lenient_same_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None - def test_op_lenient_different(self): + def test_op_lenient_different(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -381,19 +381,19 @@ def test_op_lenient_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["units"] = lexpected["units"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - def test_op_strict_same(self): + def test_op_strict_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None - def test_op_strict_different(self): + def test_op_strict_different(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -404,11 +404,11 @@ def test_op_strict_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - def test_op_strict_different_none(self): + def test_op_strict_different_none(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -419,54 +419,47 @@ def test_op_strict_different_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected -class Test_equal(tests.IrisTest): - def setUp(self): +class Test_equal: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = AncillaryVariableMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) + assert self.cls.equal.__doc__ == BaseMetadata.equal.__doc__ def test_lenient_service(self): qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - -if __name__ == "__main__": - tests.main() + assert qualname_equal in _LENIENT + assert _LENIENT[qualname_equal] + assert _LENIENT[self.cls.equal] + + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object(BaseMetadata, "equal", return_value=return_value) + result = self.none.equal(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=None) + + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object(BaseMetadata, "equal", return_value=return_value) + result = self.none.equal(other, lenient=lenient) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=lenient) diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index 73886882de..1944ecd6be 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -4,28 +4,25 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.common.metadata.BaseMetadata`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from collections import OrderedDict -import unittest.mock as mock -from unittest.mock import sentinel import numpy as np import numpy.ma as ma +import pytest from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata, CubeMetadata +from iris.tests._shared_utils import assert_dict_equal -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes +class Test: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.standard_name = mocker.sentinel.standard_name + self.long_name = mocker.sentinel.long_name + self.var_name = mocker.sentinel.var_name + self.units = mocker.sentinel.units + self.attributes = mocker.sentinel.attributes self.cls = BaseMetadata def test_repr(self): @@ -47,7 +44,7 @@ def test_repr(self): self.units, self.attributes, ) - self.assertEqual(expected, repr(metadata)) + assert repr(metadata) == expected def test_str(self): metadata = self.cls( @@ -58,7 +55,7 @@ def test_str(self): attributes={}, ) expected = f"BaseMetadata(var_name={self.var_name!r}, units={self.units!r})" - self.assertEqual(expected, str(metadata)) + assert str(metadata) == expected def test__fields(self): expected = ( @@ -68,70 +65,72 @@ def test__fields(self): "units", "attributes", ) - self.assertEqual(expected, self.cls._fields) + assert self.cls._fields == expected -class Test___eq__(tests.IrisTest): - def setUp(self): +class Test___eq__: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.kwargs = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, ) self.cls = BaseMetadata self.metadata = self.cls(**self.kwargs) def test_lenient_service(self): qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) + assert qualname___eq__ in _LENIENT + assert _LENIENT[qualname___eq__] + assert _LENIENT[self.cls.__eq__] def test_cannot_compare_non_class(self): result = self.metadata.__eq__(None) - self.assertIs(NotImplemented, result) + assert result is NotImplemented def test_cannot_compare_different_class(self): other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) result = self.metadata.__eq__(other) - self.assertIs(NotImplemented, result) - - def test_lenient(self): - return_value = sentinel.return_value - with mock.patch("iris.common.metadata._LENIENT", return_value=True) as mlenient: - with mock.patch.object( - self.cls, "_compare_lenient", return_value=return_value - ) as mcompare: - result = self.metadata.__eq__(self.metadata) - - self.assertEqual(return_value, result) - self.assertEqual(1, mcompare.call_count) + assert result is NotImplemented + + def test_lenient(self, mocker): + return_value = mocker.sentinel.return_value + mlenient = mocker.patch("iris.common.metadata._LENIENT", return_value=True) + mcompare = mocker.patch.object( + self.cls, "_compare_lenient", return_value=return_value + ) + result = self.metadata.__eq__(self.metadata) + + assert result == return_value + assert mcompare.call_count == 1 (arg,), kwargs = mcompare.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) + assert arg is self.metadata + assert kwargs == {} - self.assertEqual(1, mlenient.call_count) + assert mlenient.call_count == 1 (arg,), kwargs = mlenient.call_args - self.assertEqual(_qualname(self.cls.__eq__), _qualname(arg)) - self.assertEqual(dict(), kwargs) + assert _qualname(arg) == _qualname(self.cls.__eq__) + assert kwargs == {} def test_strict_same(self): - self.assertTrue(self.metadata.__eq__(self.metadata)) + assert self.metadata.__eq__(self.metadata) other = self.cls(**self.kwargs) - self.assertTrue(self.metadata.__eq__(other)) - self.assertTrue(other.__eq__(self.metadata)) + assert self.metadata.__eq__(other) + assert other.__eq__(self.metadata) def test_strict_different(self): self.kwargs["var_name"] = None other = self.cls(**self.kwargs) - self.assertFalse(self.metadata.__eq__(other)) - self.assertFalse(other.__eq__(self.metadata)) + assert not self.metadata.__eq__(other) + assert not other.__eq__(self.metadata) -class Test___lt__(tests.IrisTest): - def setUp(self): +class Test___lt__: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = BaseMetadata self.one = self.cls(1, 1, 1, 1, 1) self.two = self.cls(1, 1, 1, 2, 1) @@ -140,122 +139,125 @@ def setUp(self): def test__ascending_lt(self): result = self.one < self.two - self.assertTrue(result) + assert result def test__descending_lt(self): result = self.two < self.one - self.assertFalse(result) + assert not result def test__none_rhs_operand(self): result = self.one < self.none - self.assertFalse(result) + assert not result def test__none_lhs_operand(self): result = self.none < self.one - self.assertTrue(result) + assert result def test__ignore_attributes(self): result = self.one < self.attributes - self.assertFalse(result) + assert not result result = self.attributes < self.one - self.assertFalse(result) + assert not result -class Test___ne__(tests.IrisTest): - def setUp(self): +class Test___ne__: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.cls = BaseMetadata self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.other = sentinel.other + self.other = mocker.sentinel.other - def test_notimplemented(self): + def test_notimplemented(self, mocker): return_value = NotImplemented - with mock.patch.object(self.cls, "__eq__", return_value=return_value) as mocker: - result = self.metadata.__ne__(self.other) + patcher = mocker.patch.object(self.cls, "__eq__", return_value=return_value) + result = self.metadata.__ne__(self.other) - self.assertIs(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(self.other, arg) - self.assertEqual(dict(), kwargs) + assert result is return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == self.other + assert kwargs == {} - def test_negate_true(self): + def test_negate_true(self, mocker): return_value = True - with mock.patch.object(self.cls, "__eq__", return_value=return_value) as mocker: - result = self.metadata.__ne__(self.other) + patcher = mocker.patch.object(self.cls, "__eq__", return_value=return_value) + result = self.metadata.__ne__(self.other) - self.assertFalse(result) - (arg,), kwargs = mocker.call_args - self.assertEqual(self.other, arg) - self.assertEqual(dict(), kwargs) + assert not result + (arg,), kwargs = patcher.call_args + assert arg == self.other + assert kwargs == {} - def test_negate_false(self): + def test_negate_false(self, mocker): return_value = False - with mock.patch.object(self.cls, "__eq__", return_value=return_value) as mocker: - result = self.metadata.__ne__(self.other) + patcher = mocker.patch.object(self.cls, "__eq__", return_value=return_value) + result = self.metadata.__ne__(self.other) - self.assertTrue(result) - (arg,), kwargs = mocker.call_args - self.assertEqual(self.other, arg) - self.assertEqual(dict(), kwargs) + assert result + (arg,), kwargs = patcher.call_args + assert arg == self.other + assert kwargs == {} -class Test__combine(tests.IrisTest): - def setUp(self): +class Test__combine: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.kwargs = dict( standard_name="standard_name", long_name="long_name", var_name="var_name", units="units", - attributes=dict(one=sentinel.one, two=sentinel.two), + attributes=dict(one=mocker.sentinel.one, two=mocker.sentinel.two), ) self.cls = BaseMetadata self.metadata = self.cls(**self.kwargs) - def test_lenient(self): - return_value = sentinel._combine_lenient - other = sentinel.other - with mock.patch("iris.common.metadata._LENIENT", return_value=True) as mlenient: - with mock.patch.object( - self.cls, "_combine_lenient", return_value=return_value - ) as mcombine: - result = self.metadata._combine(other) - - self.assertEqual(1, mlenient.call_count) - (arg,), kwargs = mlenient.call_args - self.assertEqual(self.metadata.combine, arg) - self.assertEqual(dict(), kwargs) - - self.assertEqual(return_value, result) - self.assertEqual(1, mcombine.call_count) - (arg,), kwargs = mcombine.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_strict(self): - dummy = sentinel.dummy + def test_lenient(self, mocker): + return_value = mocker.sentinel._combine_lenient + other = mocker.sentinel.other + mlenient = mocker.patch("iris.common.metadata._LENIENT", return_value=True) + mcombine = mocker.patch.object( + self.cls, "_combine_lenient", return_value=return_value + ) + result = self.metadata._combine(other) + + assert mlenient.call_count == 1 + (arg,), kwargs = mlenient.call_args + assert arg == self.metadata.combine + assert kwargs == {} + + assert result == return_value + assert mcombine.call_count == 1 + (arg,), kwargs = mcombine.call_args + assert arg == other + assert kwargs == {} + + def test_strict(self, mocker): + dummy = mocker.sentinel.dummy values = self.kwargs.copy() values["standard_name"] = dummy values["var_name"] = dummy values["attributes"] = dummy other = self.cls(**values) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - result = self.metadata._combine(other) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + result = self.metadata._combine(other) expected = [ None if values[field] == dummy else values[field] for field in self.cls._fields ] - self.assertEqual(expected, result) + assert result == expected -class Test__combine_lenient(tests.IrisTest): - def setUp(self): +class Test__combine_lenient: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.cls = BaseMetadata self.none = self.cls(*(None,) * len(self.cls._fields))._asdict() self.names = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, ) def test_strict_units(self): @@ -266,8 +268,8 @@ def test_strict_units(self): rmetadata = self.cls(**right) expected = list(left.values()) - self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) + assert lmetadata._combine_lenient(rmetadata) == expected + assert rmetadata._combine_lenient(lmetadata) == expected def test_strict_units_different(self): left = self.none.copy() @@ -279,9 +281,9 @@ def test_strict_units_different(self): result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.values()) - self.assertEqual(expected, result) + assert result == expected result = rmetadata._combine_lenient(lmetadata) - self.assertEqual(expected, result) + assert result == expected def test_strict_units_different_none(self): left = self.none.copy() @@ -292,57 +294,57 @@ def test_strict_units_different_none(self): result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.values()) - self.assertEqual(expected, result) + assert result == expected result = rmetadata._combine_lenient(lmetadata) - self.assertEqual(expected, result) + assert result == expected - def test_attributes(self): + def test_attributes(self, mocker): left = self.none.copy() right = self.none.copy() - ldict = dict(item=sentinel.left) - rdict = dict(item=sentinel.right) + ldict = dict(item=mocker.sentinel.left) + rdict = dict(item=mocker.sentinel.right) left["attributes"] = ldict right["attributes"] = rdict rmetadata = self.cls(**right) - return_value = sentinel.return_value - with mock.patch.object( + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( self.cls, "_combine_lenient_attributes", return_value=return_value, - ) as mocker: - lmetadata = self.cls(**left) - result = lmetadata._combine_lenient(rmetadata) + ) + lmetadata = self.cls(**left) + result = lmetadata._combine_lenient(rmetadata) expected = self.none.copy() expected["attributes"] = return_value expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected - self.assertEqual(1, mocker.call_count) - args, kwargs = mocker.call_args + assert patcher.call_count == 1 + args, kwargs = patcher.call_args expected = (ldict, rdict) - self.assertEqual(expected, args) - self.assertEqual(dict(), kwargs) + assert args == expected + assert kwargs == {} - def test_attributes_non_mapping_different(self): + def test_attributes_non_mapping_different(self, mocker): left = self.none.copy() right = self.none.copy() - ldict = dict(item=sentinel.left) - rdict = sentinel.right + ldict = dict(item=mocker.sentinel.left) + rdict = mocker.sentinel.right left["attributes"] = ldict right["attributes"] = rdict lmetadata = self.cls(**left) rmetadata = self.cls(**right) expected = list(self.none.copy().values()) - self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) + assert lmetadata._combine_lenient(rmetadata) == expected + assert rmetadata._combine_lenient(lmetadata) == expected - def test_attributes_non_mapping_different_none(self): + def test_attributes_non_mapping_different_none(self, mocker): left = self.none.copy() right = self.none.copy() - ldict = dict(item=sentinel.left) + ldict = dict(item=mocker.sentinel.left) left["attributes"] = ldict lmetadata = self.cls(**left) rmetadata = self.cls(**right) @@ -351,10 +353,10 @@ def test_attributes_non_mapping_different_none(self): expected = self.none.copy() expected["attributes"] = ldict expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected result = rmetadata._combine_lenient(lmetadata) - self.assertEqual(expected, result) + assert result == expected def test_names(self): left = self.none.copy() @@ -364,11 +366,11 @@ def test_names(self): rmetadata = self.cls(**right) expected = list(left.values()) - self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) + assert lmetadata._combine_lenient(rmetadata) == expected + assert rmetadata._combine_lenient(lmetadata) == expected - def test_names_different(self): - dummy = sentinel.dummy + def test_names_different(self, mocker): + dummy = mocker.sentinel.dummy left = self.none.copy() right = self.none.copy() left.update(self.names) @@ -379,8 +381,8 @@ def test_names_different(self): rmetadata = self.cls(**right) expected = list(self.none.copy().values()) - self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) + assert lmetadata._combine_lenient(rmetadata) == expected + assert rmetadata._combine_lenient(lmetadata) == expected def test_names_different_none(self): left = self.none.copy() @@ -391,14 +393,15 @@ def test_names_different_none(self): result = lmetadata._combine_lenient(rmetadata) expected = list(left.values()) - self.assertEqual(expected, result) + assert result == expected result = rmetadata._combine_lenient(lmetadata) - self.assertEqual(expected, result) + assert result == expected -class Test__combine_lenient_attributes(tests.IrisTest): - def setUp(self): +class Test__combine_lenient_attributes: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = OrderedDict( one="one", two="two", @@ -408,7 +411,7 @@ def setUp(self): ) self.cls = BaseMetadata self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy def test_same(self): left = self.values.copy() @@ -416,10 +419,10 @@ def test_same(self): result = self.metadata._combine_lenient_attributes(left, right) expected = left - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) result = self.metadata._combine_lenient_attributes(right, left) - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) def test_different(self): left = self.values.copy() @@ -430,10 +433,10 @@ def test_different(self): expected = self.values.copy() for key in ["two", "four"]: del expected[key] - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) result = self.metadata._combine_lenient_attributes(right, left) - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) def test_different_none(self): left = self.values.copy() @@ -444,10 +447,10 @@ def test_different_none(self): expected = self.values.copy() for key in ["one", "three", "five"]: del expected[key] - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) result = self.metadata._combine_lenient_attributes(right, left) - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) def test_extra(self): left = self.values.copy() @@ -459,14 +462,15 @@ def test_extra(self): expected = self.values.copy() expected["extra_left"] = left["extra_left"] expected["extra_right"] = right["extra_right"] - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) result = self.metadata._combine_lenient_attributes(right, left) - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) -class Test__combine_strict_attributes(tests.IrisTest): - def setUp(self): +class Test__combine_strict_attributes: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = OrderedDict( one="one", two="two", @@ -476,7 +480,7 @@ def setUp(self): ) self.cls = BaseMetadata self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy def test_same(self): left = self.values.copy() @@ -484,10 +488,10 @@ def test_same(self): result = self.metadata._combine_strict_attributes(left, right) expected = left - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) result = self.metadata._combine_strict_attributes(right, left) - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) def test_different(self): left = self.values.copy() @@ -498,10 +502,10 @@ def test_different(self): expected = self.values.copy() for key in ["one", "three"]: del expected[key] - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) result = self.metadata._combine_strict_attributes(right, left) - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) def test_different_none(self): left = self.values.copy() @@ -512,10 +516,10 @@ def test_different_none(self): expected = self.values.copy() for key in ["one", "three", "five"]: del expected[key] - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) result = self.metadata._combine_strict_attributes(right, left) - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) def test_extra(self): left = self.values.copy() @@ -525,76 +529,71 @@ def test_extra(self): result = self.metadata._combine_strict_attributes(left, right) expected = self.values.copy() - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) result = self.metadata._combine_strict_attributes(right, left) - self.assertDictEqual(expected, result) + assert_dict_equal(result, expected) -class Test__compare_lenient(tests.IrisTest): - def setUp(self): +class Test__compare_lenient: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.cls = BaseMetadata self.none = self.cls(*(None,) * len(self.cls._fields))._asdict() self.names = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, ) - def test_name_same(self): + def test_name_same(self, mocker): left = self.none.copy() left.update(self.names) right = left.copy() lmetadata = self.cls(**left) rmetadata = self.cls(**right) - with mock.patch.object( - self.cls, "_is_attributes", return_value=False - ) as mocker: - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._compare_lenient(lmetadata)) + patcher = mocker.patch.object(self.cls, "_is_attributes", return_value=False) + assert lmetadata._compare_lenient(rmetadata) + assert rmetadata._compare_lenient(lmetadata) # mocker not called for "units" nor "var_name" members. expected = (len(self.cls._fields) - 2) * 2 - self.assertEqual(expected, mocker.call_count) + assert patcher.call_count == expected - def test_name_same_lenient_false__long_name_different(self): + def test_name_same_lenient_false__long_name_different(self, mocker): left = self.none.copy() left.update(self.names) right = left.copy() - right["long_name"] = sentinel.dummy + right["long_name"] = mocker.sentinel.dummy lmetadata = self.cls(**left) rmetadata = self.cls(**right) - with mock.patch.object( - self.cls, "_is_attributes", return_value=False - ) as mocker: - self.assertFalse(lmetadata._compare_lenient(rmetadata)) - self.assertFalse(rmetadata._compare_lenient(lmetadata)) + patcher = mocker.patch.object(self.cls, "_is_attributes", return_value=False) + assert not lmetadata._compare_lenient(rmetadata) + assert not rmetadata._compare_lenient(lmetadata) # mocker not called for "units" nor "var_name" members. expected = (len(self.cls._fields) - 2) * 2 - self.assertEqual(expected, mocker.call_count) + assert patcher.call_count == expected - def test_name_same_lenient_true__var_name_different(self): + def test_name_same_lenient_true__var_name_different(self, mocker): left = self.none.copy() left.update(self.names) right = left.copy() - right["var_name"] = sentinel.dummy + right["var_name"] = mocker.sentinel.dummy lmetadata = self.cls(**left) rmetadata = self.cls(**right) - with mock.patch.object( - self.cls, "_is_attributes", return_value=False - ) as mocker: - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._compare_lenient(lmetadata)) + patcher = mocker.patch.object(self.cls, "_is_attributes", return_value=False) + assert lmetadata._compare_lenient(rmetadata) + assert rmetadata._compare_lenient(lmetadata) # mocker not called for "units" nor "var_name" members. expected = (len(self.cls._fields) - 2) * 2 - self.assertEqual(expected, mocker.call_count) + assert patcher.call_count == expected - def test_name_different(self): + def test_name_different(self, mocker): left = self.none.copy() left.update(self.names) right = left.copy() @@ -602,13 +601,13 @@ def test_name_different(self): lmetadata = self.cls(**left) rmetadata = self.cls(**right) - with mock.patch.object(self.cls, "_is_attributes") as mocker: - self.assertFalse(lmetadata._compare_lenient(rmetadata)) - self.assertFalse(rmetadata._compare_lenient(lmetadata)) + patcher = mocker.patch.object(self.cls, "_is_attributes") + assert not lmetadata._compare_lenient(rmetadata) + assert not rmetadata._compare_lenient(lmetadata) - self.assertEqual(0, mocker.call_count) + assert patcher.call_count == 0 - def test_strict_units(self): + def test_strict_units(self, mocker): left = self.none.copy() left.update(self.names) left["units"] = "K" @@ -616,17 +615,15 @@ def test_strict_units(self): lmetadata = self.cls(**left) rmetadata = self.cls(**right) - with mock.patch.object( - self.cls, "_is_attributes", return_value=False - ) as mocker: - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._compare_lenient(lmetadata)) + patcher = mocker.patch.object(self.cls, "_is_attributes", return_value=False) + assert lmetadata._compare_lenient(rmetadata) + assert rmetadata._compare_lenient(lmetadata) # mocker not called for "units" nor "var_name" members. expected = (len(self.cls._fields) - 2) * 2 - self.assertEqual(expected, mocker.call_count) + assert patcher.call_count == expected - def test_strict_units_different(self): + def test_strict_units_different(self, mocker): left = self.none.copy() left.update(self.names) left["units"] = "K" @@ -635,63 +632,61 @@ def test_strict_units_different(self): lmetadata = self.cls(**left) rmetadata = self.cls(**right) - with mock.patch.object( - self.cls, "_is_attributes", return_value=False - ) as mocker: - self.assertFalse(lmetadata._compare_lenient(rmetadata)) - self.assertFalse(rmetadata._compare_lenient(lmetadata)) + patcher = mocker.patch.object(self.cls, "_is_attributes", return_value=False) + assert not lmetadata._compare_lenient(rmetadata) + assert not rmetadata._compare_lenient(lmetadata) # mocker not called for "units" nor "var_name" members. expected = (len(self.cls._fields) - 2) * 2 - self.assertEqual(expected, mocker.call_count) + assert patcher.call_count == expected - def test_attributes(self): + def test_attributes(self, mocker): left = self.none.copy() left.update(self.names) right = left.copy() - ldict = dict(item=sentinel.left) - rdict = dict(item=sentinel.right) + ldict = dict(item=mocker.sentinel.left) + rdict = dict(item=mocker.sentinel.right) left["attributes"] = ldict right["attributes"] = rdict rmetadata = self.cls(**right) - with mock.patch.object( + patcher = mocker.patch.object( self.cls, "_compare_lenient_attributes", return_value=True, - ) as mocker: - lmetadata = self.cls(**left) - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._compare_lenient(lmetadata)) + ) + lmetadata = self.cls(**left) + assert lmetadata._compare_lenient(rmetadata) + assert rmetadata._compare_lenient(lmetadata) - self.assertEqual(2, mocker.call_count) + assert patcher.call_count == 2 expected = [((ldict, rdict),), ((rdict, ldict),)] - self.assertEqual(expected, mocker.call_args_list) + assert patcher.call_args_list == expected - def test_attributes_non_mapping_different(self): + def test_attributes_non_mapping_different(self, mocker): left = self.none.copy() left.update(self.names) right = left.copy() - ldict = dict(item=sentinel.left) - rdict = sentinel.right + ldict = dict(item=mocker.sentinel.left) + rdict = mocker.sentinel.right left["attributes"] = ldict right["attributes"] = rdict lmetadata = self.cls(**left) rmetadata = self.cls(**right) - self.assertFalse(lmetadata._compare_lenient(rmetadata)) - self.assertFalse(rmetadata._compare_lenient(lmetadata)) + assert not lmetadata._compare_lenient(rmetadata) + assert not rmetadata._compare_lenient(lmetadata) - def test_attributes_non_mapping_different_none(self): + def test_attributes_non_mapping_different_none(self, mocker): left = self.none.copy() left.update(self.names) right = left.copy() - ldict = dict(item=sentinel.left) + ldict = dict(item=mocker.sentinel.left) left["attributes"] = ldict lmetadata = self.cls(**left) rmetadata = self.cls(**right) - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._compare_lenient(lmetadata)) + assert lmetadata._compare_lenient(rmetadata) + assert rmetadata._compare_lenient(lmetadata) def test_names(self): left = self.none.copy() @@ -702,179 +697,183 @@ def test_names(self): lmetadata = self.cls(**left) rmetadata = self.cls(**right) - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._combine_lenient(lmetadata)) + assert lmetadata._compare_lenient(rmetadata) + assert rmetadata._compare_lenient(lmetadata) -class Test__compare_lenient_attributes(tests.IrisTest): - def setUp(self): +class Test__compare_lenient_attributes: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = OrderedDict( - one=sentinel.one, - two=sentinel.two, + one=mocker.sentinel.one, + two=mocker.sentinel.two, three=np.int16(123), four=np.arange(10), five=ma.arange(5), ) self.cls = BaseMetadata self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy def test_same(self): left = self.values.copy() right = self.values.copy() - self.assertTrue(self.metadata._compare_lenient_attributes(left, right)) - self.assertTrue(self.metadata._compare_lenient_attributes(right, left)) + assert self.metadata._compare_lenient_attributes(left, right) + assert self.metadata._compare_lenient_attributes(right, left) def test_different(self): left = self.values.copy() right = self.values.copy() left["two"] = left["four"] = self.dummy - self.assertFalse(self.metadata._compare_lenient_attributes(left, right)) - self.assertFalse(self.metadata._compare_lenient_attributes(right, left)) + assert not self.metadata._compare_lenient_attributes(left, right) + assert not self.metadata._compare_lenient_attributes(right, left) def test_different_none(self): left = self.values.copy() right = self.values.copy() left["one"] = left["three"] = left["five"] = None - self.assertFalse(self.metadata._compare_lenient_attributes(left, right)) - self.assertFalse(self.metadata._compare_lenient_attributes(right, left)) + assert not self.metadata._compare_lenient_attributes(left, right) + assert not self.metadata._compare_lenient_attributes(right, left) - def test_extra(self): + def test_extra(self, mocker): left = self.values.copy() right = self.values.copy() - left["extra_left"] = sentinel.extra_left - right["extra_right"] = sentinel.extra_right + left["extra_left"] = mocker.sentinel.extra_left + right["extra_right"] = mocker.sentinel.extra_right - self.assertTrue(self.metadata._compare_lenient_attributes(left, right)) - self.assertTrue(self.metadata._compare_lenient_attributes(right, left)) + assert self.metadata._compare_lenient_attributes(left, right) + assert self.metadata._compare_lenient_attributes(right, left) -class Test__compare_strict_attributes(tests.IrisTest): - def setUp(self): +class Test__compare_strict_attributes: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = OrderedDict( - one=sentinel.one, - two=sentinel.two, + one=mocker.sentinel.one, + two=mocker.sentinel.two, three=np.int16(123), four=np.arange(10), five=ma.arange(5), ) self.cls = BaseMetadata self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy def test_same(self): left = self.values.copy() right = self.values.copy() - self.assertTrue(self.metadata._compare_strict_attributes(left, right)) - self.assertTrue(self.metadata._compare_strict_attributes(right, left)) + assert self.metadata._compare_strict_attributes(left, right) + assert self.metadata._compare_strict_attributes(right, left) def test_different(self): left = self.values.copy() right = self.values.copy() left["two"] = left["four"] = self.dummy - self.assertFalse(self.metadata._compare_strict_attributes(left, right)) - self.assertFalse(self.metadata._compare_strict_attributes(right, left)) + assert not self.metadata._compare_strict_attributes(left, right) + assert not self.metadata._compare_strict_attributes(right, left) def test_different_none(self): left = self.values.copy() right = self.values.copy() left["one"] = left["three"] = left["five"] = None - self.assertFalse(self.metadata._compare_strict_attributes(left, right)) - self.assertFalse(self.metadata._compare_strict_attributes(right, left)) + assert not self.metadata._compare_strict_attributes(left, right) + assert not self.metadata._compare_strict_attributes(right, left) - def test_extra(self): + def test_extra(self, mocker): left = self.values.copy() right = self.values.copy() - left["extra_left"] = sentinel.extra_left - right["extra_right"] = sentinel.extra_right + left["extra_left"] = mocker.sentinel.extra_left + right["extra_right"] = mocker.sentinel.extra_right - self.assertFalse(self.metadata._compare_strict_attributes(left, right)) - self.assertFalse(self.metadata._compare_strict_attributes(right, left)) + assert not self.metadata._compare_strict_attributes(left, right) + assert not self.metadata._compare_strict_attributes(right, left) -class Test__difference(tests.IrisTest): - def setUp(self): +class Test__difference: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.kwargs = dict( standard_name="standard_name", long_name="long_name", var_name="var_name", units="units", - attributes=dict(one=sentinel.one, two=sentinel.two), + attributes=dict(one=mocker.sentinel.one, two=mocker.sentinel.two), ) self.cls = BaseMetadata self.metadata = self.cls(**self.kwargs) - def test_lenient(self): - return_value = sentinel._difference_lenient - other = sentinel.other - with mock.patch("iris.common.metadata._LENIENT", return_value=True) as mlenient: - with mock.patch.object( - self.cls, "_difference_lenient", return_value=return_value - ) as mdifference: - result = self.metadata._difference(other) - - self.assertEqual(1, mlenient.call_count) - (arg,), kwargs = mlenient.call_args - self.assertEqual(self.metadata.difference, arg) - self.assertEqual(dict(), kwargs) - - self.assertEqual(return_value, result) - self.assertEqual(1, mdifference.call_count) - (arg,), kwargs = mdifference.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_strict(self): - dummy = sentinel.dummy + def test_lenient(self, mocker): + return_value = mocker.sentinel._difference_lenient + other = mocker.sentinel.other + mlenient = mocker.patch("iris.common.metadata._LENIENT", return_value=True) + mdifference = mocker.patch.object( + self.cls, "_difference_lenient", return_value=return_value + ) + result = self.metadata._difference(other) + + assert mlenient.call_count == 1 + (arg,), kwargs = mlenient.call_args + assert arg == self.metadata.difference + assert kwargs == {} + + assert result == return_value + assert mdifference.call_count == 1 + (arg,), kwargs = mdifference.call_args + assert arg == other + assert kwargs == {} + + def test_strict(self, mocker): + dummy = mocker.sentinel.dummy values = self.kwargs.copy() values["long_name"] = dummy values["units"] = dummy other = self.cls(**values) method = "_difference_strict_attributes" - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - with mock.patch.object(self.cls, method, return_value=None) as mdifference: - result = self.metadata._difference(other) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + mdifference = mocker.patch.object(self.cls, method, return_value=None) + result = self.metadata._difference(other) expected = [ (self.kwargs[field], dummy) if values[field] == dummy else None for field in self.cls._fields ] - self.assertEqual(expected, result) - self.assertEqual(1, mdifference.call_count) + assert result == expected + assert mdifference.call_count == 1 args, kwargs = mdifference.call_args expected = (self.kwargs["attributes"], values["attributes"]) - self.assertEqual(expected, args) - self.assertEqual(dict(), kwargs) + assert args == expected + assert kwargs == {} - with mock.patch.object(self.cls, method, return_value=None) as mdifference: - result = other._difference(self.metadata) + mdifference = mocker.patch.object(self.cls, method, return_value=None) + result = other._difference(self.metadata) expected = [ (dummy, self.kwargs[field]) if values[field] == dummy else None for field in self.cls._fields ] - self.assertEqual(expected, result) - self.assertEqual(1, mdifference.call_count) + assert result == expected + assert mdifference.call_count == 1 args, kwargs = mdifference.call_args expected = (self.kwargs["attributes"], values["attributes"]) - self.assertEqual(expected, args) - self.assertEqual(dict(), kwargs) + assert args == expected + assert kwargs == {} -class Test__difference_lenient(tests.IrisTest): - def setUp(self): +class Test__difference_lenient: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.cls = BaseMetadata self.none = self.cls(*(None,) * len(self.cls._fields))._asdict() self.names = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, ) def test_strict_units(self): @@ -884,8 +883,8 @@ def test_strict_units(self): lmetadata = self.cls(**left) rmetadata = self.cls(**right) expected = list(self.none.values()) - self.assertEqual(expected, lmetadata._difference_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._difference_lenient(lmetadata)) + assert lmetadata._difference_lenient(rmetadata) == expected + assert rmetadata._difference_lenient(lmetadata) == expected def test_strict_units_different(self): left = self.none.copy() @@ -900,13 +899,13 @@ def test_strict_units_different(self): expected = self.none.copy() expected["units"] = (lunits, runits) expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected result = rmetadata._difference_lenient(lmetadata) expected = self.none.copy() expected["units"] = (runits, lunits) expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected def test_strict_units_different_none(self): left = self.none.copy() @@ -921,46 +920,46 @@ def test_strict_units_different_none(self): expected["units"] = (lunits, runits) expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected result = rmetadata._difference_lenient(lmetadata) expected = self.none.copy() expected["units"] = (runits, lunits) expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected - def test_attributes(self): + def test_attributes(self, mocker): left = self.none.copy() right = self.none.copy() - ldict = dict(item=sentinel.left) - rdict = dict(item=sentinel.right) + ldict = dict(item=mocker.sentinel.left) + rdict = dict(item=mocker.sentinel.right) left["attributes"] = ldict right["attributes"] = rdict rmetadata = self.cls(**right) - return_value = sentinel.return_value - with mock.patch.object( + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( self.cls, "_difference_lenient_attributes", return_value=return_value, - ) as mocker: - lmetadata = self.cls(**left) - result = lmetadata._difference_lenient(rmetadata) + ) + lmetadata = self.cls(**left) + result = lmetadata._difference_lenient(rmetadata) expected = self.none.copy() expected["attributes"] = return_value expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected - self.assertEqual(1, mocker.call_count) - args, kwargs = mocker.call_args + assert patcher.call_count == 1 + args, kwargs = patcher.call_args expected = (ldict, rdict) - self.assertEqual(expected, args) - self.assertEqual(dict(), kwargs) + assert args == expected + assert kwargs == {} - def test_attributes_non_mapping_different(self): + def test_attributes_non_mapping_different(self, mocker): left = self.none.copy() right = self.none.copy() - ldict = dict(item=sentinel.left) - rdict = sentinel.right + ldict = dict(item=mocker.sentinel.left) + rdict = mocker.sentinel.right left["attributes"] = ldict right["attributes"] = rdict lmetadata = self.cls(**left) @@ -970,28 +969,28 @@ def test_attributes_non_mapping_different(self): expected = self.none.copy() expected["attributes"] = (ldict, rdict) expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected result = rmetadata._difference_lenient(lmetadata) expected = self.none.copy() expected["attributes"] = (rdict, ldict) expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected - def test_attributes_non_mapping_different_none(self): + def test_attributes_non_mapping_different_none(self, mocker): left = self.none.copy() right = self.none.copy() - ldict = dict(item=sentinel.left) + ldict = dict(item=mocker.sentinel.left) left["attributes"] = ldict lmetadata = self.cls(**left) rmetadata = self.cls(**right) result = lmetadata._difference_lenient(rmetadata) expected = list(self.none.copy().values()) - self.assertEqual(expected, result) + assert result == expected result = rmetadata._difference_lenient(lmetadata) - self.assertEqual(expected, result) + assert result == expected def test_names(self): left = self.none.copy() @@ -1001,11 +1000,11 @@ def test_names(self): rmetadata = self.cls(**right) expected = list(self.none.values()) - self.assertEqual(expected, lmetadata._difference_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._difference_lenient(lmetadata)) + assert lmetadata._difference_lenient(rmetadata) == expected + assert rmetadata._difference_lenient(lmetadata) == expected - def test_names_different(self): - dummy = sentinel.dummy + def test_names_different(self, mocker): + dummy = mocker.sentinel.dummy left = self.none.copy() right = self.none.copy() left.update(self.names) @@ -1024,7 +1023,7 @@ def test_names_different(self): expected["long_name"] = (left["long_name"], right["long_name"]) expected["var_name"] = (left["var_name"], right["var_name"]) expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected result = rmetadata._difference_lenient(lmetadata) expected = self.none.copy() @@ -1035,7 +1034,7 @@ def test_names_different(self): expected["long_name"] = (right["long_name"], left["long_name"]) expected["var_name"] = (right["var_name"], left["var_name"]) expected = list(expected.values()) - self.assertEqual(expected, result) + assert result == expected def test_names_different_none(self): left = self.none.copy() @@ -1046,34 +1045,35 @@ def test_names_different_none(self): result = lmetadata._difference_lenient(rmetadata) expected = list(self.none.values()) - self.assertEqual(expected, result) + assert result == expected result = rmetadata._difference_lenient(lmetadata) - self.assertEqual(expected, result) + assert result == expected -class Test__difference_lenient_attributes(tests.IrisTest): - def setUp(self): +class Test__difference_lenient_attributes: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = OrderedDict( - one=sentinel.one, - two=sentinel.two, + one=mocker.sentinel.one, + two=mocker.sentinel.two, three=np.float64(3.14), four=np.arange(10, dtype=np.float64), five=ma.arange(10, dtype=np.int16), ) self.cls = BaseMetadata self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy def test_same(self): left = self.values.copy() right = self.values.copy() result = self.metadata._difference_lenient_attributes(left, right) - self.assertIsNone(result) + assert result is None result = self.metadata._difference_lenient_attributes(right, left) - self.assertIsNone(result) + assert result is None def test_different(self): left = self.values.copy() @@ -1086,13 +1086,13 @@ def test_different(self): del right[key] expected_left, expected_right = (left, right) result_left, result_right = result - self.assertDictEqual(expected_left, result_left) - self.assertDictEqual(expected_right, result_right) + assert_dict_equal(result_left, expected_left) + assert_dict_equal(result_right, expected_right) result = self.metadata._difference_lenient_attributes(right, left) result_left, result_right = result - self.assertDictEqual(expected_right, result_left) - self.assertDictEqual(expected_left, result_right) + assert_dict_equal(result_left, expected_right) + assert_dict_equal(result_right, expected_left) def test_different_none(self): left = self.values.copy() @@ -1105,47 +1105,48 @@ def test_different_none(self): del right[key] expected_left, expected_right = (left, right) result_left, result_right = result - self.assertDictEqual(expected_left, result_left) - self.assertDictEqual(expected_right, result_right) + assert_dict_equal(result_left, expected_left) + assert_dict_equal(result_right, expected_right) result = self.metadata._difference_lenient_attributes(right, left) result_left, result_right = result - self.assertDictEqual(expected_right, result_left) - self.assertDictEqual(expected_left, result_right) + assert_dict_equal(result_left, expected_right) + assert_dict_equal(result_right, expected_left) - def test_extra(self): + def test_extra(self, mocker): left = self.values.copy() right = self.values.copy() - left["extra_left"] = sentinel.extra_left - right["extra_right"] = sentinel.extra_right + left["extra_left"] = mocker.sentinel.extra_left + right["extra_right"] = mocker.sentinel.extra_right result = self.metadata._difference_lenient_attributes(left, right) - self.assertIsNone(result) + assert result is None result = self.metadata._difference_lenient_attributes(right, left) - self.assertIsNone(result) + assert result is None -class Test__difference_strict_attributes(tests.IrisTest): - def setUp(self): +class Test__difference_strict_attributes: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = OrderedDict( - one=sentinel.one, - two=sentinel.two, + one=mocker.sentinel.one, + two=mocker.sentinel.two, three=np.int32(123), four=np.arange(10), five=ma.arange(10), ) self.cls = BaseMetadata self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy def test_same(self): left = self.values.copy() right = self.values.copy() result = self.metadata._difference_strict_attributes(left, right) - self.assertIsNone(result) + assert result is None result = self.metadata._difference_strict_attributes(right, left) - self.assertIsNone(result) + assert result is None def test_different(self): left = self.values.copy() @@ -1159,13 +1160,13 @@ def test_different(self): del expected_left[key] del expected_right[key] result_left, result_right = result - self.assertDictEqual(expected_left, result_left) - self.assertDictEqual(expected_right, result_right) + assert_dict_equal(result_left, expected_left) + assert_dict_equal(result_right, expected_right) result = self.metadata._difference_strict_attributes(right, left) result_left, result_right = result - self.assertDictEqual(expected_right, result_left) - self.assertDictEqual(expected_left, result_right) + assert_dict_equal(result_left, expected_right) + assert_dict_equal(result_right, expected_left) def test_different_none(self): left = self.values.copy() @@ -1179,54 +1180,56 @@ def test_different_none(self): del expected_left[key] del expected_right[key] result_left, result_right = result - self.assertDictEqual(expected_left, result_left) - self.assertDictEqual(expected_right, result_right) + assert_dict_equal(result_left, expected_left) + assert_dict_equal(result_right, expected_right) result = self.metadata._difference_strict_attributes(right, left) result_left, result_right = result - self.assertDictEqual(expected_right, result_left) - self.assertDictEqual(expected_left, result_right) + assert_dict_equal(result_left, expected_right) + assert_dict_equal(result_right, expected_left) - def test_extra(self): + def test_extra(self, mocker): left = self.values.copy() right = self.values.copy() - left["extra_left"] = sentinel.extra_left - right["extra_right"] = sentinel.extra_right + left["extra_left"] = mocker.sentinel.extra_left + right["extra_right"] = mocker.sentinel.extra_right result = self.metadata._difference_strict_attributes(left, right) expected_left = dict(extra_left=left["extra_left"]) expected_right = dict(extra_right=right["extra_right"]) result_left, result_right = result - self.assertDictEqual(expected_left, result_left) - self.assertDictEqual(expected_right, result_right) + assert_dict_equal(result_left, expected_left) + assert_dict_equal(result_right, expected_right) result = self.metadata._difference_strict_attributes(right, left) result_left, result_right = result - self.assertDictEqual(expected_right, result_left) - self.assertDictEqual(expected_left, result_right) + assert_dict_equal(result_left, expected_right) + assert_dict_equal(result_right, expected_left) -class Test__is_attributes(tests.IrisTest): - def setUp(self): +class Test__is_attributes: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = BaseMetadata self.metadata = self.cls(*(None,) * len(self.cls._fields)) self.field = "attributes" def test_field(self): - self.assertTrue(self.metadata._is_attributes(self.field, {}, {})) + assert self.metadata._is_attributes(self.field, {}, {}) def test_field_not_attributes(self): - self.assertFalse(self.metadata._is_attributes(None, {}, {})) + assert not self.metadata._is_attributes(None, {}, {}) def test_left_not_mapping(self): - self.assertFalse(self.metadata._is_attributes(self.field, None, {})) + assert not self.metadata._is_attributes(self.field, None, {}) def test_right_not_mapping(self): - self.assertFalse(self.metadata._is_attributes(self.field, {}, None)) + assert not self.metadata._is_attributes(self.field, {}, None) -class Test_combine(tests.IrisTest): - def setUp(self): +class Test_combine: + @pytest.fixture(autouse=True) + def _setup(self, mocker): kwargs = dict( standard_name="standard_name", long_name="long_name", @@ -1237,84 +1240,79 @@ def setUp(self): self.cls = BaseMetadata self.metadata = self.cls(**kwargs) self.mock_kwargs = OrderedDict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, ) def test_lenient_service(self): qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) + assert qualname_combine in _LENIENT + assert _LENIENT[qualname_combine] + assert _LENIENT[self.cls.combine] def test_cannot_combine_non_class(self): emsg = "Cannot combine" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.combine(None) + with pytest.raises(TypeError, match=emsg): + _ = self.metadata.combine(None) def test_cannot_combine_different_class(self): other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) emsg = "Cannot combine" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.combine(other) + with pytest.raises(TypeError, match=emsg): + _ = self.metadata.combine(other) - def test_lenient_default(self): + def test_lenient_default(self, mocker): return_value = self.mock_kwargs.values() - with mock.patch.object( - self.cls, "_combine", return_value=return_value - ) as mocker: - result = self.metadata.combine(self.metadata) - - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - def test_lenient_true(self): + patcher = mocker.patch.object(self.cls, "_combine", return_value=return_value) + result = self.metadata.combine(self.metadata) + + assert result._asdict() == self.mock_kwargs + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg is self.metadata + assert kwargs == {} + + def test_lenient_true(self, mocker): return_value = self.mock_kwargs.values() - with mock.patch.object( - self.cls, "_combine", return_value=return_value - ) as mcombine: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.combine(self.metadata, lenient=True) + mcombine = mocker.patch.object(self.cls, "_combine", return_value=return_value) + mcontext = mocker.patch.object(_LENIENT, "context") + result = self.metadata.combine(self.metadata, lenient=True) - self.assertEqual(1, mcontext.call_count) + assert mcontext.call_count == 1 (arg,), kwargs = mcontext.call_args - self.assertEqual(_qualname(self.cls.combine), arg) - self.assertEqual(dict(), kwargs) + assert arg == _qualname(self.cls.combine) + assert kwargs == {} - self.assertEqual(result._asdict(), self.mock_kwargs) - self.assertEqual(1, mcombine.call_count) + assert result._asdict() == self.mock_kwargs + assert mcombine.call_count == 1 (arg,), kwargs = mcombine.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) + assert arg is self.metadata + assert kwargs == {} - def test_lenient_false(self): + def test_lenient_false(self, mocker): return_value = self.mock_kwargs.values() - with mock.patch.object( - self.cls, "_combine", return_value=return_value - ) as mcombine: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.combine(self.metadata, lenient=False) + mcombine = mocker.patch.object(self.cls, "_combine", return_value=return_value) + mcontext = mocker.patch.object(_LENIENT, "context") + result = self.metadata.combine(self.metadata, lenient=False) - self.assertEqual(1, mcontext.call_count) + assert mcontext.call_count == 1 args, kwargs = mcontext.call_args - self.assertEqual((), args) - self.assertEqual({_qualname(self.cls.combine): False}, kwargs) + assert args == () + assert kwargs == {_qualname(self.cls.combine): False} - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mcombine.call_count) + assert result._asdict() == self.mock_kwargs + assert mcombine.call_count == 1 (arg,), kwargs = mcombine.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) + assert arg is self.metadata + assert kwargs == {} -class Test_difference(tests.IrisTest): - def setUp(self): +class Test_difference: + @pytest.fixture(autouse=True) + def _setup(self, mocker): kwargs = dict( standard_name="standard_name", long_name="long_name", @@ -1325,163 +1323,161 @@ def setUp(self): self.cls = BaseMetadata self.metadata = self.cls(**kwargs) self.mock_kwargs = OrderedDict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, ) def test_lenient_service(self): qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) + assert qualname_difference in _LENIENT + assert _LENIENT[qualname_difference] + assert _LENIENT[self.cls.difference] def test_cannot_differ_non_class(self): emsg = "Cannot differ" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.difference(None) + with pytest.raises(TypeError, match=emsg): + _ = self.metadata.difference(None) def test_cannot_differ_different_class(self): other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) emsg = "Cannot differ" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.difference(other) + with pytest.raises(TypeError, match=emsg): + _ = self.metadata.difference(other) - def test_lenient_default(self): + def test_lenient_default(self, mocker): return_value = self.mock_kwargs.values() - with mock.patch.object( + patcher = mocker.patch.object( self.cls, "_difference", return_value=return_value - ) as mocker: - result = self.metadata.difference(self.metadata) + ) + result = self.metadata.difference(self.metadata) - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) + assert result._asdict() == self.mock_kwargs + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg is self.metadata + assert kwargs == {} - def test_lenient_true(self): + def test_lenient_true(self, mocker): return_value = self.mock_kwargs.values() - with mock.patch.object( + mdifference = mocker.patch.object( self.cls, "_difference", return_value=return_value - ) as mdifference: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.difference(self.metadata, lenient=True) + ) + mcontext = mocker.patch.object(_LENIENT, "context") + result = self.metadata.difference(self.metadata, lenient=True) - self.assertEqual(1, mcontext.call_count) + assert mcontext.call_count == 1 (arg,), kwargs = mcontext.call_args - self.assertEqual(_qualname(self.cls.difference), arg) - self.assertEqual(dict(), kwargs) + assert arg == _qualname(self.cls.difference) + assert kwargs == {} - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mdifference.call_count) + assert result._asdict() == self.mock_kwargs + assert mdifference.call_count == 1 (arg,), kwargs = mdifference.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) + assert arg is self.metadata + assert kwargs == {} - def test_lenient_false(self): + def test_lenient_false(self, mocker): return_value = self.mock_kwargs.values() - with mock.patch.object( + mdifference = mocker.patch.object( self.cls, "_difference", return_value=return_value - ) as mdifference: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.difference(self.metadata, lenient=False) + ) + mcontext = mocker.patch.object(_LENIENT, "context") + result = self.metadata.difference(self.metadata, lenient=False) - self.assertEqual(mcontext.call_count, 1) + assert mcontext.call_count == 1 args, kwargs = mcontext.call_args - self.assertEqual((), args) - self.assertEqual({_qualname(self.cls.difference): False}, kwargs) + assert args == () + assert kwargs == {_qualname(self.cls.difference): False} - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mdifference.call_count) + assert result._asdict() == self.mock_kwargs + assert mdifference.call_count == 1 (arg,), kwargs = mdifference.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) + assert arg is self.metadata + assert kwargs == {} -class Test_equal(tests.IrisTest): - def setUp(self): +class Test_equal: + @pytest.fixture(autouse=True) + def _setup(self, mocker): kwargs = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, ) self.cls = BaseMetadata self.metadata = self.cls(**kwargs) def test_lenient_service(self): qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue((_LENIENT[self.cls.equal])) + assert qualname_equal in _LENIENT + assert _LENIENT[qualname_equal] + assert _LENIENT[self.cls.equal] def test_cannot_compare_non_class(self): emsg = "Cannot compare" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.equal(None) + with pytest.raises(TypeError, match=emsg): + _ = self.metadata.equal(None) def test_cannot_compare_different_class(self): other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) emsg = "Cannot compare" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.equal(other) - - def test_lenient_default(self): - return_value = sentinel.return_value - with mock.patch.object(self.cls, "__eq__", return_value=return_value) as mocker: - result = self.metadata.equal(self.metadata) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - def test_lenient_true(self): - return_value = sentinel.return_value - with mock.patch.object( - self.cls, "__eq__", return_value=return_value - ) as m__eq__: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.equal(self.metadata, lenient=True) - - self.assertEqual(return_value, result) - self.assertEqual(1, mcontext.call_count) + with pytest.raises(TypeError, match=emsg): + _ = self.metadata.equal(other) + + def test_lenient_default(self, mocker): + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object(self.cls, "__eq__", return_value=return_value) + result = self.metadata.equal(self.metadata) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg is self.metadata + assert kwargs == {} + + def test_lenient_true(self, mocker): + return_value = mocker.sentinel.return_value + m__eq__ = mocker.patch.object(self.cls, "__eq__", return_value=return_value) + mcontext = mocker.patch.object(_LENIENT, "context") + result = self.metadata.equal(self.metadata, lenient=True) + + assert result == return_value + assert mcontext.call_count == 1 (arg,), kwargs = mcontext.call_args - self.assertEqual(_qualname(self.cls.equal), arg) - self.assertEqual(dict(), kwargs) + assert arg == _qualname(self.cls.equal) + assert kwargs == {} - self.assertEqual(1, m__eq__.call_count) + assert m__eq__.call_count == 1 (arg,), kwargs = m__eq__.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - def test_lenient_false(self): - return_value = sentinel.return_value - with mock.patch.object( - self.cls, "__eq__", return_value=return_value - ) as m__eq__: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.equal(self.metadata, lenient=False) - - self.assertEqual(1, mcontext.call_count) + assert arg is self.metadata + assert kwargs == {} + + def test_lenient_false(self, mocker): + return_value = mocker.sentinel.return_value + m__eq__ = mocker.patch.object(self.cls, "__eq__", return_value=return_value) + mcontext = mocker.patch.object(_LENIENT, "context") + result = self.metadata.equal(self.metadata, lenient=False) + + assert mcontext.call_count == 1 args, kwargs = mcontext.call_args - self.assertEqual((), args) - self.assertEqual({_qualname(self.cls.equal): False}, kwargs) + assert args == () + assert kwargs == {_qualname(self.cls.equal): False} - self.assertEqual(return_value, result) - self.assertEqual(1, m__eq__.call_count) + assert result == return_value + assert m__eq__.call_count == 1 (arg,), kwargs = m__eq__.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) + assert arg is self.metadata + assert kwargs == {} -class Test_name(tests.IrisTest): - def setUp(self): +class Test_name: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = BaseMetadata self.default = self.cls.DEFAULT_NAME @@ -1500,122 +1496,119 @@ def test_standard_name(self): metadata = self._make(standard_name=token) result = metadata.name() - self.assertEqual(token, result) + assert result == token result = metadata.name(token=True) - self.assertEqual(token, result) + assert result == token def test_standard_name__invalid_token(self): token = "nope nope" metadata = self._make(standard_name=token) result = metadata.name() - self.assertEqual(token, result) + assert result == token result = metadata.name(token=True) - self.assertEqual(self.default, result) + assert result == self.default def test_long_name(self): token = "long_name" metadata = self._make(long_name=token) result = metadata.name() - self.assertEqual(token, result) + assert result == token result = metadata.name(token=True) - self.assertEqual(token, result) + assert result == token def test_long_name__invalid_token(self): token = "nope nope" metadata = self._make(long_name=token) result = metadata.name() - self.assertEqual(token, result) + assert result == token result = metadata.name(token=True) - self.assertEqual(self.default, result) + assert result == self.default def test_var_name(self): token = "var_name" metadata = self._make(var_name=token) result = metadata.name() - self.assertEqual(token, result) + assert result == token result = metadata.name(token=True) - self.assertEqual(token, result) + assert result == token def test_var_name__invalid_token(self): token = "nope nope" metadata = self._make(var_name=token) result = metadata.name() - self.assertEqual(token, result) + assert result == token result = metadata.name(token=True) - self.assertEqual(self.default, result) + assert result == self.default def test_default(self): metadata = self._make() result = metadata.name() - self.assertEqual(self.default, result) + assert result == self.default result = metadata.name(token=True) - self.assertEqual(self.default, result) + assert result == self.default def test_default__invalid_token(self): token = "nope nope" metadata = self._make() result = metadata.name(default=token) - self.assertEqual(token, result) + assert result == token emsg = "Cannot retrieve a valid name token" - with self.assertRaisesRegex(ValueError, emsg): - metadata.name(default=token, token=True) + with pytest.raises(ValueError, match=emsg): + _ = metadata.name(default=token, token=True) -class Test_token(tests.IrisTest): - def setUp(self): +class Test_token: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = BaseMetadata - def test_passthru_None(self): + def test_passthru_none(self): result = self.cls.token(None) - self.assertIsNone(result) + assert result is None def test_fail_leading_underscore(self): result = self.cls.token("_nope") - self.assertIsNone(result) + assert result is None def test_fail_leading_dot(self): result = self.cls.token(".nope") - self.assertIsNone(result) + assert result is None def test_fail_leading_plus(self): result = self.cls.token("+nope") - self.assertIsNone(result) + assert result is None def test_fail_leading_at(self): result = self.cls.token("@nope") - self.assertIsNone(result) + assert result is None def test_fail_space(self): result = self.cls.token("nope nope") - self.assertIsNone(result) + assert result is None def test_fail_colon(self): result = self.cls.token("nope:") - self.assertIsNone(result) + assert result is None def test_pass_simple(self): token = "simple" result = self.cls.token(token) - self.assertEqual(token, result) + assert result == token def test_pass_leading_digit(self): token = "123simple" result = self.cls.token(token) - self.assertEqual(token, result) + assert result == token def test_pass_mixture(self): token = "S.imple@one+two_3" result = self.cls.token(token) - self.assertEqual(token, result) - - -if __name__ == "__main__": - tests.main() + assert result == token diff --git a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py index 3618d2ace5..9cabd21d01 100644 --- a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py @@ -4,26 +4,23 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.common.metadata.CellMeasureMetadata`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel + +import pytest from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata, CellMeasureMetadata -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.measure = mock.sentinel.measure +class Test: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.standard_name = mocker.sentinel.standard_name + self.long_name = mocker.sentinel.long_name + self.var_name = mocker.sentinel.var_name + self.units = mocker.sentinel.units + self.attributes = mocker.sentinel.attributes + self.measure = mocker.sentinel.measure self.cls = CellMeasureMetadata def test_repr(self): @@ -47,7 +44,7 @@ def test_repr(self): self.attributes, self.measure, ) - self.assertEqual(expected, repr(metadata)) + assert repr(metadata) == expected def test__fields(self): expected = ( @@ -58,148 +55,148 @@ def test__fields(self): "attributes", "measure", ) - self.assertEqual(self.cls._fields, expected) + assert self.cls._fields == expected def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) + assert issubclass(self.cls, BaseMetadata) -class Test___eq__(tests.IrisTest): - def setUp(self): +class Test___eq__: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - measure=sentinel.measure, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, + measure=mocker.sentinel.measure, ) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = CellMeasureMetadata def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__) + assert self.cls.__eq__.__doc__ == BaseMetadata.__eq__.__doc__ def test_lenient_service(self): qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) + assert qualname___eq__ in _LENIENT + assert _LENIENT[qualname___eq__] + assert _LENIENT[self.cls.__eq__] - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value + def test_call(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_op_lenient_same(self): + patcher = mocker.patch.object(BaseMetadata, "__eq__", return_value=return_value) + result = metadata.__eq__(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == {} + + def test_op_lenient_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.__eq__(rmetadata) + assert rmetadata.__eq__(lmetadata) - def test_op_lenient_same_none(self): + def test_op_lenient_same_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.__eq__(rmetadata) + assert rmetadata.__eq__(lmetadata) - def test_op_lenient_same_measure_none(self): + def test_op_lenient_same_measure_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - def test_op_lenient_different(self): + def test_op_lenient_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - def test_op_lenient_different_measure(self): + def test_op_lenient_different_measure(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - def test_op_strict_same(self): + def test_op_strict_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.__eq__(rmetadata) + assert rmetadata.__eq__(lmetadata) - def test_op_strict_different(self): + def test_op_strict_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - def test_op_strict_different_measure(self): + def test_op_strict_different_measure(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - def test_op_strict_different_none(self): + def test_op_strict_different_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - def test_op_strict_different_measure_none(self): + def test_op_strict_different_measure_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) -class Test___lt__(tests.IrisTest): - def setUp(self): +class Test___lt__: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = CellMeasureMetadata self.one = self.cls(1, 1, 1, 1, 1, 1) self.two = self.cls(1, 1, 1, 2, 1, 1) @@ -208,111 +205,112 @@ def setUp(self): def test__ascending_lt(self): result = self.one < self.two - self.assertTrue(result) + assert result def test__descending_lt(self): result = self.two < self.one - self.assertFalse(result) + assert not result def test__none_rhs_operand(self): result = self.one < self.none - self.assertFalse(result) + assert not result def test__none_lhs_operand(self): result = self.none < self.one - self.assertTrue(result) + assert result def test__ignore_attributes(self): result = self.one < self.attributes - self.assertFalse(result) + assert not result result = self.attributes < self.one - self.assertFalse(result) + assert not result -class Test_combine(tests.IrisTest): - def setUp(self): +class Test_combine: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - measure=sentinel.measure, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, + measure=mocker.sentinel.measure, ) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = CellMeasureMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.combine.__doc__, self.cls.combine.__doc__) + assert self.cls.combine.__doc__ == BaseMetadata.combine.__doc__ def test_lenient_service(self): qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( + assert qualname_combine in _LENIENT + assert _LENIENT[qualname_combine] + assert _LENIENT[self.cls.combine] + + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( + ) + result = self.none.combine(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=None) + + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) + ) + result = self.none.combine(other, lenient=lenient) - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=lenient) - def test_op_lenient_same(self): + def test_op_lenient_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) expected = self.values - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_lenient_same_none(self): + def test_op_lenient_same_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) expected = self.values - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_lenient_same_measure_none(self): + def test_op_lenient_same_measure_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = None rmetadata = self.cls(**right) expected = right.copy() - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_lenient_different(self): + def test_op_lenient_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy @@ -320,11 +318,11 @@ def test_op_lenient_different(self): expected = self.values.copy() expected["units"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_lenient_different_measure(self): + def test_op_lenient_different_measure(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = self.dummy @@ -332,20 +330,20 @@ def test_op_lenient_different_measure(self): expected = self.values.copy() expected["measure"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_strict_same(self): + def test_op_strict_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) expected = self.values.copy() - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_strict_different(self): + def test_op_strict_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy @@ -353,11 +351,11 @@ def test_op_strict_different(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_strict_different_measure(self): + def test_op_strict_different_measure(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = self.dummy @@ -365,11 +363,11 @@ def test_op_strict_different_measure(self): expected = self.values.copy() expected["measure"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_strict_different_none(self): + def test_op_strict_different_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None @@ -377,11 +375,11 @@ def test_op_strict_different_none(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_strict_different_measure_none(self): + def test_op_strict_different_measure_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = None @@ -389,96 +387,97 @@ def test_op_strict_different_measure_none(self): expected = self.values.copy() expected["measure"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected -class Test_difference(tests.IrisTest): - def setUp(self): +class Test_difference: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - measure=sentinel.measure, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, + measure=mocker.sentinel.measure, ) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = CellMeasureMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.difference.__doc__, self.cls.difference.__doc__) + assert self.cls.difference.__doc__ == BaseMetadata.difference.__doc__ def test_lenient_service(self): qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( + assert qualname_difference in _LENIENT + assert _LENIENT[qualname_difference] + assert _LENIENT[self.cls.difference] + + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( + ) + result = self.none.difference(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=None) + + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) + ) + result = self.none.difference(other, lenient=lenient) - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=lenient) - def test_op_lenient_same(self): + def test_op_lenient_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None - def test_op_lenient_same_none(self): + def test_op_lenient_same_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None - def test_op_lenient_same_measure_none(self): + def test_op_lenient_same_measure_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = None rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() - lexpected["measure"] = (sentinel.measure, None) + lexpected["measure"] = (mocker.sentinel.measure, None) rexpected = deepcopy(self.none)._asdict() - rexpected["measure"] = (None, sentinel.measure) + rexpected["measure"] = (None, mocker.sentinel.measure) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - def test_op_lenient_different(self): + def test_op_lenient_different(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -489,11 +488,11 @@ def test_op_lenient_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["units"] = lexpected["units"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - def test_op_lenient_different_measure(self): + def test_op_lenient_different_measure(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -504,19 +503,19 @@ def test_op_lenient_different_measure(self): rexpected = deepcopy(self.none)._asdict() rexpected["measure"] = lexpected["measure"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - def test_op_strict_same(self): + def test_op_strict_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None - def test_op_strict_different(self): + def test_op_strict_different(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -527,11 +526,11 @@ def test_op_strict_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - def test_op_strict_different_measure(self): + def test_op_strict_different_measure(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -542,11 +541,11 @@ def test_op_strict_different_measure(self): rexpected = deepcopy(self.none)._asdict() rexpected["measure"] = lexpected["measure"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - def test_op_strict_different_none(self): + def test_op_strict_different_none(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -557,11 +556,11 @@ def test_op_strict_different_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - def test_op_strict_different_measure_none(self): + def test_op_strict_different_measure_none(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -572,54 +571,47 @@ def test_op_strict_different_measure_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["measure"] = lexpected["measure"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected -class Test_equal(tests.IrisTest): - def setUp(self): +class Test_equal: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = CellMeasureMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) + assert self.cls.equal.__doc__ == BaseMetadata.equal.__doc__ def test_lenient_service(self): qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - -if __name__ == "__main__": - tests.main() + assert qualname_equal in _LENIENT + assert _LENIENT[qualname_equal] + assert _LENIENT[self.cls.equal] + + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object(BaseMetadata, "equal", return_value=return_value) + result = self.none.equal(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=None) + + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object(BaseMetadata, "equal", return_value=return_value) + result = self.none.equal(other, lenient=lenient) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=lenient) diff --git a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py index 010838b7fc..bb6b2115d2 100644 --- a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py @@ -4,27 +4,24 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.common.metadata.CoordMetadata`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel + +import pytest from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata, CoordMetadata -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.coord_system = mock.sentinel.coord_system - self.climatological = mock.sentinel.climatological +class Test: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.standard_name = mocker.sentinel.standard_name + self.long_name = mocker.sentinel.long_name + self.var_name = mocker.sentinel.var_name + self.units = mocker.sentinel.units + self.attributes = mocker.sentinel.attributes + self.coord_system = mocker.sentinel.coord_system + self.climatological = mocker.sentinel.climatological self.cls = CoordMetadata def test_repr(self): @@ -51,7 +48,7 @@ def test_repr(self): self.coord_system, self.climatological, ) - self.assertEqual(expected, repr(metadata)) + assert repr(metadata) == expected def test__fields(self): expected = ( @@ -63,153 +60,153 @@ def test__fields(self): "coord_system", "climatological", ) - self.assertEqual(self.cls._fields, expected) + assert self.cls._fields == expected def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) + assert issubclass(self.cls, BaseMetadata) -class Test___eq__(tests.IrisTest): - def setUp(self): +class Test___eq__: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - coord_system=sentinel.coord_system, - climatological=sentinel.climatological, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, + coord_system=mocker.sentinel.coord_system, + climatological=mocker.sentinel.climatological, ) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = CoordMetadata def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__) + assert self.cls.__eq__.__doc__ == BaseMetadata.__eq__.__doc__ def test_lenient_service(self): qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) + assert qualname___eq__ in _LENIENT + assert _LENIENT[qualname___eq__] + assert _LENIENT[self.cls.__eq__] - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value + def test_call(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_op_lenient_same(self): + patcher = mocker.patch.object(BaseMetadata, "__eq__", return_value=return_value) + result = metadata.__eq__(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == {} + + def test_op_lenient_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.__eq__(rmetadata) + assert rmetadata.__eq__(lmetadata) - def test_op_lenient_same_none(self): + def test_op_lenient_same_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.__eq__(rmetadata) + assert rmetadata.__eq__(lmetadata) + + def test_op_lenient_same_members_none(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=True) - def test_op_lenient_same_members_none(self): for member in self.cls._members: lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = None rmetadata = self.cls(**right) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different(self): + def test_op_lenient_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) + + def test_op_lenient_different_members(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=True) - def test_op_lenient_different_members(self): for member in self.cls._members: lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = self.dummy rmetadata = self.cls(**right) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_same(self): + def test_op_strict_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.__eq__(rmetadata) + assert rmetadata.__eq__(lmetadata) - def test_op_strict_different(self): + def test_op_strict_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) + + def test_op_strict_different_members(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=False) - def test_op_strict_different_members(self): for member in self.cls._members: lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = self.dummy rmetadata = self.cls(**right) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_none(self): + def test_op_strict_different_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) + + def test_op_strict_different_members_none(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=False) - def test_op_strict_different_members_none(self): for member in self.cls._members: lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = None rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) -class Test___lt__(tests.IrisTest): - def setUp(self): +class Test___lt__: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = CoordMetadata self.one = self.cls(1, 1, 1, 1, 1, 1, 1) self.two = self.cls(1, 1, 1, 2, 1, 1, 1) @@ -218,113 +215,114 @@ def setUp(self): def test__ascending_lt(self): result = self.one < self.two - self.assertTrue(result) + assert result def test__descending_lt(self): result = self.two < self.one - self.assertFalse(result) + assert not result def test__none_rhs_operand(self): result = self.one < self.none - self.assertFalse(result) + assert not result def test__none_lhs_operand(self): result = self.none < self.one - self.assertTrue(result) + assert result def test__ignore_attributes_coord_system(self): result = self.one < self.attributes_cs - self.assertFalse(result) + assert not result result = self.attributes_cs < self.one - self.assertFalse(result) + assert not result -class Test_combine(tests.IrisTest): - def setUp(self): +class Test_combine: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - coord_system=sentinel.coord_system, - climatological=sentinel.climatological, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, + coord_system=mocker.sentinel.coord_system, + climatological=mocker.sentinel.climatological, ) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = CoordMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.combine.__doc__, self.cls.combine.__doc__) + assert self.cls.combine.__doc__ == BaseMetadata.combine.__doc__ def test_lenient_service(self): qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( + assert qualname_combine in _LENIENT + assert _LENIENT[qualname_combine] + assert _LENIENT[self.cls.combine] + + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( + ) + result = self.none.combine(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=None) + + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) + ) + result = self.none.combine(other, lenient=lenient) - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=lenient) - def test_op_lenient_same(self): + def test_op_lenient_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) expected = self.values - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_lenient_same_none(self): + def test_op_lenient_same_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) expected = self.values - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected + + def test_op_lenient_same_members_none(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=True) - def test_op_lenient_same_members_none(self): for member in self.cls._members: lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = None rmetadata = self.cls(**right) expected = right.copy() + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertTrue(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_different(self): + def test_op_lenient_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy @@ -332,11 +330,13 @@ def test_op_lenient_different(self): expected = self.values.copy() expected["units"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected + + def test_op_lenient_different_members(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=True) - def test_op_lenient_different_members(self): for member in self.cls._members: lmetadata = self.cls(**self.values) right = self.values.copy() @@ -344,21 +344,19 @@ def test_op_lenient_different_members(self): rmetadata = self.cls(**right) expected = self.values.copy() expected[member] = None + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_same(self): + def test_op_strict_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) expected = self.values.copy() - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_strict_different(self): + def test_op_strict_different(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy @@ -366,11 +364,13 @@ def test_op_strict_different(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected + + def test_op_strict_different_members(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=False) - def test_op_strict_different_members(self): for member in self.cls._members: lmetadata = self.cls(**self.values) right = self.values.copy() @@ -378,12 +378,10 @@ def test_op_strict_different_members(self): rmetadata = self.cls(**right) expected = self.values.copy() expected[member] = None + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_none(self): + def test_op_strict_different_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None @@ -391,11 +389,13 @@ def test_op_strict_different_none(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected + + def test_op_strict_different_members_none(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=False) - def test_op_strict_different_members_none(self): for member in self.cls._members: lmetadata = self.cls(**self.values) right = self.values.copy() @@ -403,84 +403,85 @@ def test_op_strict_different_members_none(self): rmetadata = self.cls(**right) expected = self.values.copy() expected[member] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected -class Test_difference(tests.IrisTest): - def setUp(self): +class Test_difference: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - coord_system=sentinel.coord_system, - climatological=sentinel.climatological, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, + coord_system=mocker.sentinel.coord_system, + climatological=mocker.sentinel.climatological, ) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = CoordMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.difference.__doc__, self.cls.difference.__doc__) + assert self.cls.difference.__doc__ == BaseMetadata.difference.__doc__ def test_lenient_service(self): qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( + assert qualname_difference in _LENIENT + assert _LENIENT[qualname_difference] + assert _LENIENT[self.cls.difference] + + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( + ) + result = self.none.difference(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=None) + + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) + ) + result = self.none.difference(other, lenient=lenient) - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=lenient) - def test_op_lenient_same(self): + def test_op_lenient_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None - def test_op_lenient_same_none(self): + def test_op_lenient_same_none(self, mocker): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None + + def test_op_lenient_same_members_none(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=True) - def test_op_lenient_same_members_none(self): for member in self.cls._members: lmetadata = self.cls(**self.values) member_value = getattr(lmetadata, member) @@ -491,12 +492,10 @@ def test_op_lenient_same_members_none(self): lexpected[member] = (member_value, None) rexpected = deepcopy(self.none)._asdict() rexpected[member] = (None, member_value) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) - - def test_op_lenient_different(self): + def test_op_lenient_different(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -507,11 +506,13 @@ def test_op_lenient_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["units"] = lexpected["units"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=True) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected + + def test_op_lenient_different_members(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=True) - def test_op_lenient_different_members(self): for member in self.cls._members: left = self.values.copy() lmetadata = self.cls(**left) @@ -522,20 +523,18 @@ def test_op_lenient_different_members(self): lexpected[member] = (left[member], right[member]) rexpected = deepcopy(self.none)._asdict() rexpected[member] = lexpected[member][::-1] + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) - - def test_op_strict_same(self): + def test_op_strict_same(self, mocker): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None - def test_op_strict_different(self): + def test_op_strict_different(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -546,11 +545,13 @@ def test_op_strict_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected + + def test_op_strict_different_members(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=False) - def test_op_strict_different_members(self): for member in self.cls._members: left = self.values.copy() lmetadata = self.cls(**left) @@ -561,12 +562,10 @@ def test_op_strict_different_members(self): lexpected[member] = (left[member], right[member]) rexpected = deepcopy(self.none)._asdict() rexpected[member] = lexpected[member][::-1] + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) - - def test_op_strict_different_none(self): + def test_op_strict_different_none(self, mocker): left = self.values.copy() lmetadata = self.cls(**left) right = self.values.copy() @@ -577,11 +576,13 @@ def test_op_strict_different_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + mocker.patch("iris.common.metadata._LENIENT", return_value=False) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected + + def test_op_strict_different_members_none(self, mocker): + mocker.patch("iris.common.metadata._LENIENT", return_value=False) - def test_op_strict_different_members_none(self): for member in self.cls._members: left = self.values.copy() lmetadata = self.cls(**left) @@ -592,55 +593,46 @@ def test_op_strict_different_members_none(self): lexpected[member] = (left[member], right[member]) rexpected = deepcopy(self.none)._asdict() rexpected[member] = lexpected[member][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(lexpected, lmetadata.difference(rmetadata)._asdict()) - self.assertEqual(rexpected, rmetadata.difference(lmetadata)._asdict()) + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected -class Test_equal(tests.IrisTest): - def setUp(self): +class Test_equal: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = CoordMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) + assert self.cls.equal.__doc__ == BaseMetadata.equal.__doc__ def test_lenient_service(self): qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - -if __name__ == "__main__": - tests.main() + assert qualname_equal in _LENIENT + assert _LENIENT[qualname_equal] + assert _LENIENT[self.cls.equal] + + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object(BaseMetadata, "equal", return_value=return_value) + result = self.none.equal(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=None) + + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object(BaseMetadata, "equal", return_value=return_value) + result = self.none.equal(other, lenient=lenient) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=lenient) diff --git a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py index 7d51cbfb37..0b4725da42 100644 --- a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py @@ -6,13 +6,8 @@ # Import iris.tests first so that some things can be initialised before # importing anything else. -from typing import Any, ClassVar - -import iris.tests as tests # isort:skip - from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel +from typing import Any, ClassVar import pytest @@ -44,14 +39,15 @@ def _make_metadata( ) -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.cell_methods = mock.sentinel.cell_methods +class Test: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.standard_name = mocker.sentinel.standard_name + self.long_name = mocker.sentinel.long_name + self.var_name = mocker.sentinel.var_name + self.units = mocker.sentinel.units + self.attributes = mocker.sentinel.attributes + self.cell_methods = mocker.sentinel.cell_methods self.cls = CubeMetadata def test_repr(self): @@ -75,7 +71,7 @@ def test_repr(self): self.attributes, self.cell_methods, ) - self.assertEqual(expected, repr(metadata)) + assert repr(metadata) == expected def test__fields(self): expected = ( @@ -86,10 +82,10 @@ def test__fields(self): "attributes", "cell_methods", ) - self.assertEqual(self.cls._fields, expected) + assert self.cls._fields == expected def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) + assert issubclass(self.cls, BaseMetadata) @pytest.fixture(params=CubeMetadata._fields) # type: ignore[attr-defined] @@ -99,7 +95,7 @@ def fieldname(request): @pytest.fixture(params=["strict", "lenient"]) -def op_leniency(request): +def leniency(request): """Parametrize testing over strict or lenient operation.""" return request.param @@ -355,7 +351,7 @@ class MixinSplitattrsMatrixTests: def test_splitattrs_cases( self, - op_leniency, + leniency, primary_values, primary_is_global_not_local, order_reversed, @@ -370,7 +366,7 @@ def test_splitattrs_cases( * left-to-right or right-to-left operation order. """ primary_inputs = primary_values[-2:] - check_is_lenient = {"strict": False, "lenient": True}[op_leniency] + check_is_lenient = {"strict": False, "lenient": True}[leniency] check_splitattrs_testcase( operation_name=self.operation_name, check_is_lenient=check_is_lenient, @@ -393,7 +389,7 @@ def test_splitattrs_cases( ) def test_splitattrs_global_local_independence( self, - op_leniency, + leniency, primary_values, secondary_values, ): @@ -414,7 +410,7 @@ def test_splitattrs_global_local_independence( """ primary_inputs = primary_values[-2:] secondary_inputs = secondary_values[-2:] - check_is_lenient = {"strict": False, "lenient": True}[op_leniency] + check_is_lenient = {"strict": False, "lenient": True}[leniency] check_splitattrs_testcase( operation_name=self.operation_name, check_is_lenient=check_is_lenient, @@ -429,19 +425,19 @@ class Test___eq__(MixinSplitattrsMatrixTests): operation_name = "equal" @pytest.fixture(autouse=True) - def setup(self): + def _setup(self, mocker): self.lvalues = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, # Must be a mapping. attributes=dict(), - cell_methods=sentinel.cell_methods, + cell_methods=mocker.sentinel.cell_methods, ) # Setup another values tuple with all-distinct content objects. self.rvalues = deepcopy(self.lvalues) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = CubeMetadata def test_wraps_docstring(self): @@ -453,37 +449,35 @@ def test_lenient_service(self): assert _LENIENT[qualname___eq__] assert _LENIENT[self.cls.__eq__] - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value + def test_call(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) + patcher = mocker.patch.object(BaseMetadata, "__eq__", return_value=return_value) + result = metadata.__eq__(other) - assert return_value == result - assert mocker.call_args_list == [mock.call(other)] + assert result == return_value + assert patcher.call_args_list == [mocker.call(other)] - def test_op_same(self, op_leniency): + def test_op_same(self, leniency, mocker): # Check op all-same content, but all-new data. # NOTE: test for both strict/lenient, should both work the same. - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" lmetadata = self.cls(**self.lvalues) rmetadata = self.cls(**self.rvalues) - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # Check equality both l==r and r==l. - assert lmetadata.__eq__(rmetadata) - assert rmetadata.__eq__(lmetadata) + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # Check equality both l==r and r==l. + assert lmetadata.__eq__(rmetadata) + assert rmetadata.__eq__(lmetadata) - def test_op_different__none(self, fieldname, op_leniency): + def test_op_different__none(self, fieldname, leniency, mocker): # One side has field=value, and the other field=None, both strict + lenient. if fieldname == "attributes": # Must be a dict, cannot be None. pytest.skip() else: - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" lmetadata = self.cls(**self.lvalues) self.rvalues.update({fieldname: None}) rmetadata = self.cls(**self.rvalues) @@ -497,18 +491,18 @@ def test_op_different__none(self, fieldname, op_leniency): # Ensure we are handling all the different field cases raise ValueError(f"{self.__name__} unhandled fieldname : {fieldname}") - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # Check equality both l==r and r==l. - assert lmetadata.__eq__(rmetadata) == expect_success - assert rmetadata.__eq__(lmetadata) == expect_success + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # Check equality both l==r and r==l. + assert lmetadata.__eq__(rmetadata) == expect_success + assert rmetadata.__eq__(lmetadata) == expect_success - def test_op_different__value(self, fieldname, op_leniency): + def test_op_different__value(self, fieldname, leniency, mocker): # Compare when a given field value is changed, both strict + lenient. if fieldname == "attributes": # Dicts have more possibilities: handled separately. pytest.skip() else: - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" lmetadata = self.cls(**self.lvalues) self.rvalues.update({fieldname: self.dummy}) rmetadata = self.cls(**self.rvalues) @@ -527,39 +521,40 @@ def test_op_different__value(self, fieldname, op_leniency): # Ensure we are handling all the different field cases raise ValueError(f"{self.__name__} unhandled fieldname : {fieldname}") - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # Check equality both l==r and r==l. - assert lmetadata.__eq__(rmetadata) == expect_success - assert rmetadata.__eq__(lmetadata) == expect_success + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # Check equality both l==r and r==l. + assert lmetadata.__eq__(rmetadata) == expect_success + assert rmetadata.__eq__(lmetadata) == expect_success - def test_op_different__attribute_extra(self, op_leniency): + def test_op_different__attribute_extra(self, leniency, mocker): # Check when one set of attributes has an extra entry. - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" lmetadata = self.cls(**self.lvalues) self.rvalues["attributes"]["_extra_"] = 1 rmetadata = self.cls(**self.rvalues) # This counts as equal *only* in the lenient case. expect_success = is_lenient - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # Check equality both l==r and r==l. - assert lmetadata.__eq__(rmetadata) == expect_success - assert rmetadata.__eq__(lmetadata) == expect_success + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # Check equality both l==r and r==l. + assert lmetadata.__eq__(rmetadata) == expect_success + assert rmetadata.__eq__(lmetadata) == expect_success - def test_op_different__attribute_value(self, op_leniency): + def test_op_different__attribute_value(self, leniency, mocker): # lhs and rhs have different values for an attribute, both strict + lenient. - is_lenient = op_leniency == "lenient" - self.lvalues["attributes"]["_extra_"] = mock.sentinel.value1 - self.rvalues["attributes"]["_extra_"] = mock.sentinel.value2 + is_lenient = leniency == "lenient" + self.lvalues["attributes"]["_extra_"] = mocker.sentinel.value1 + self.rvalues["attributes"]["_extra_"] = mocker.sentinel.value2 lmetadata = self.cls(**self.lvalues) rmetadata = self.cls(**self.rvalues) - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # This should ALWAYS fail. - assert not lmetadata.__eq__(rmetadata) - assert not rmetadata.__eq__(lmetadata) + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # This should ALWAYS fail. + assert not lmetadata.__eq__(rmetadata) + assert not rmetadata.__eq__(lmetadata) -class Test___lt__(tests.IrisTest): - def setUp(self): +class Test___lt__: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = CubeMetadata self.one = self.cls(1, 1, 1, 1, 1, 1) self.two = self.cls(1, 1, 1, 2, 1, 1) @@ -568,43 +563,43 @@ def setUp(self): def test__ascending_lt(self): result = self.one < self.two - self.assertTrue(result) + assert result def test__descending_lt(self): result = self.two < self.one - self.assertFalse(result) + assert not result def test__none_rhs_operand(self): result = self.one < self.none - self.assertFalse(result) + assert not result def test__none_lhs_operand(self): result = self.none < self.one - self.assertTrue(result) + assert result def test__ignore_attributes_cell_methods(self): result = self.one < self.attributes_cm - self.assertFalse(result) + assert not result result = self.attributes_cm < self.one - self.assertFalse(result) + assert not result class Test_combine(MixinSplitattrsMatrixTests): operation_name = "combine" @pytest.fixture(autouse=True) - def setup(self): + def _setup(self, mocker): self.lvalues = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - cell_methods=sentinel.cell_methods, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, + cell_methods=mocker.sentinel.cell_methods, ) # Get a second copy with all-new objects. self.rvalues = deepcopy(self.lvalues) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = CubeMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) @@ -617,48 +612,48 @@ def test_lenient_service(self): assert _LENIENT[qualname_combine] assert _LENIENT[self.cls.combine] - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) + ) + result = self.none.combine(other) - assert return_value == result - assert mocker.call_args_list == [mock.call(other, lenient=None)] + assert result == return_value + assert patcher.call_args_list == [mocker.call(other, lenient=None)] - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) + ) + result = self.none.combine(other, lenient=lenient) - assert return_value == result - assert mocker.call_args_list == [mock.call(other, lenient=lenient)] + assert result == return_value + assert patcher.call_args_list == [mocker.call(other, lenient=lenient)] - def test_op_same(self, op_leniency): + def test_op_same(self, leniency, mocker): # Result is same as either input, both strict + lenient. - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" lmetadata = self.cls(**self.lvalues) rmetadata = self.cls(**self.rvalues) expected = self.lvalues - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # Check both l+r and r+l - assert lmetadata.combine(rmetadata)._asdict() == expected - assert rmetadata.combine(lmetadata)._asdict() == expected + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # Check both l+r and r+l + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_different__none(self, fieldname, op_leniency): + def test_op_different__none(self, fieldname, leniency, mocker): # One side has field=value, and the other field=None, both strict + lenient. if fieldname == "attributes": # Can't be None : Tested separately pytest.skip() - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" lmetadata = self.cls(**self.lvalues) # Cancel one setting in the rhs argument. @@ -682,21 +677,21 @@ def test_op_different__none(self, fieldname, op_leniency): # also include those which only 1 has expected = self.lvalues - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # Check both l+r and r+l - assert lmetadata.combine(rmetadata)._asdict() == expected - assert rmetadata.combine(lmetadata)._asdict() == expected + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # Check both l+r and r+l + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_different__value(self, fieldname, op_leniency): + def test_op_different__value(self, fieldname, leniency, mocker): # One field has different value for lhs/rhs, both strict + lenient. if fieldname == "attributes": # Attribute behaviours are tested separately pytest.skip() - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" - self.lvalues[fieldname] = mock.sentinel.value1 - self.rvalues[fieldname] = mock.sentinel.value2 + self.lvalues[fieldname] = mocker.sentinel.value1 + self.rvalues[fieldname] = mocker.sentinel.value2 lmetadata = self.cls(**self.lvalues) rmetadata = self.cls(**self.rvalues) @@ -704,18 +699,18 @@ def test_op_different__value(self, fieldname, op_leniency): expected = self.lvalues.copy() expected[fieldname] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # Check both l+r and r+l - assert lmetadata.combine(rmetadata)._asdict() == expected - assert rmetadata.combine(lmetadata)._asdict() == expected + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # Check both l+r and r+l + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_different__attribute_extra(self, op_leniency): + def test_op_different__attribute_extra(self, leniency, mocker): # One field has an extra attribute, both strict + lenient. - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" - self.lvalues["attributes"] = {"_a_common_": mock.sentinel.dummy} + self.lvalues["attributes"] = {"_a_common_": mocker.sentinel.dummy} self.rvalues["attributes"] = self.lvalues["attributes"].copy() - self.rvalues["attributes"]["_extra_"] = mock.sentinel.testvalue + self.rvalues["attributes"]["_extra_"] = mocker.sentinel.testvalue lmetadata = self.cls(**self.lvalues) rmetadata = self.cls(**self.rvalues) @@ -726,22 +721,22 @@ def test_op_different__attribute_extra(self, op_leniency): # .. it should not expected = self.lvalues - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # Check both l+r and r+l - assert lmetadata.combine(rmetadata)._asdict() == expected - assert rmetadata.combine(lmetadata)._asdict() == expected + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # Check both l+r and r+l + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected - def test_op_different__attribute_value(self, op_leniency): + def test_op_different__attribute_value(self, leniency, mocker): # lhs and rhs have different values for an attribute, both strict + lenient. - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" self.lvalues["attributes"] = { "_a_common_": self.dummy, - "_b_common_": mock.sentinel.value1, + "_b_common_": mocker.sentinel.value1, } self.lvalues["attributes"] = { "_a_common_": self.dummy, - "_b_common_": mock.sentinel.value2, + "_b_common_": mocker.sentinel.value2, } lmetadata = self.cls(**self.lvalues) rmetadata = self.cls(**self.rvalues) @@ -751,28 +746,28 @@ def test_op_different__attribute_value(self, op_leniency): expected = self.lvalues.copy() expected["attributes"] = None - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # Check both l+r and r+l - assert lmetadata.combine(rmetadata)._asdict() == expected - assert rmetadata.combine(lmetadata)._asdict() == expected + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # Check both l+r and r+l + assert lmetadata.combine(rmetadata)._asdict() == expected + assert rmetadata.combine(lmetadata)._asdict() == expected class Test_difference(MixinSplitattrsMatrixTests): operation_name = "difference" @pytest.fixture(autouse=True) - def setup(self): + def _setup(self, mocker): self.lvalues = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, attributes=dict(), # MUST be a dict - cell_methods=sentinel.cell_methods, + cell_methods=mocker.sentinel.cell_methods, ) # Make a copy with all-different objects in it. self.rvalues = deepcopy(self.lvalues) - self.dummy = sentinel.dummy + self.dummy = mocker.sentinel.dummy self.cls = CubeMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) @@ -785,45 +780,45 @@ def test_lenient_service(self): assert _LENIENT[qualname_difference] assert _LENIENT[self.cls.difference] - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) + ) + result = self.none.difference(other) - assert return_value == result - assert mocker.call_args_list == [mock.call(other, lenient=None)] + assert result == return_value + assert patcher.call_args_list == [mocker.call(other, lenient=None)] - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object( BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) + ) + result = self.none.difference(other, lenient=lenient) - assert return_value == result - assert mocker.call_args_list == [mock.call(other, lenient=lenient)] + assert result == return_value + assert patcher.call_args_list == [mocker.call(other, lenient=lenient)] - def test_op_same(self, op_leniency): - is_lenient = op_leniency == "lenient" + def test_op_same(self, leniency, mocker): + is_lenient = leniency == "lenient" lmetadata = self.cls(**self.lvalues) rmetadata = self.cls(**self.rvalues) - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - assert lmetadata.difference(rmetadata) is None - assert rmetadata.difference(lmetadata) is None + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None - def test_op_different__none(self, fieldname, op_leniency): + def test_op_different__none(self, fieldname, leniency, mocker): # One side has field=value, and the other field=None, both strict + lenient. if fieldname in ("attributes",): # These cannot properly be set to 'None'. Tested elsewhere. pytest.skip() - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" lmetadata = self.cls(**self.lvalues) self.rvalues[fieldname] = None @@ -848,28 +843,28 @@ def test_op_different__none(self, fieldname, op_leniency): rexpected = lexpected.copy() rexpected[fieldname] = diffentry[::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - if strict_result: - assert lmetadata.difference(rmetadata)._asdict() == lexpected - assert rmetadata.difference(lmetadata)._asdict() == rexpected - else: - # Expect NO differences - assert lmetadata.difference(rmetadata) is None - assert rmetadata.difference(lmetadata) is None + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + if strict_result: + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected + else: + # Expect NO differences + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None - def test_op_different__value(self, fieldname, op_leniency): + def test_op_different__value(self, fieldname, leniency, mocker): # One field has different value for lhs/rhs, both strict + lenient. if fieldname == "attributes": # Attribute behaviours are tested separately pytest.skip() - self.lvalues[fieldname] = mock.sentinel.value1 - self.rvalues[fieldname] = mock.sentinel.value2 + self.lvalues[fieldname] = mocker.sentinel.value1 + self.rvalues[fieldname] = mocker.sentinel.value2 lmetadata = self.cls(**self.lvalues) rmetadata = self.cls(**self.rvalues) # In all cases, this field should show a difference : leniency has no effect - ldiff_values = (mock.sentinel.value1, mock.sentinel.value2) + ldiff_values = (mocker.sentinel.value1, mocker.sentinel.value2) ldiff_metadata = self.none._asdict() ldiff_metadata[fieldname] = ldiff_values rdiff_metadata = self.none._asdict() @@ -879,52 +874,52 @@ def test_op_different__value(self, fieldname, op_leniency): assert lmetadata.difference(rmetadata)._asdict() == ldiff_metadata assert rmetadata.difference(lmetadata)._asdict() == rdiff_metadata - def test_op_different__attribute_extra(self, op_leniency): + def test_op_different__attribute_extra(self, leniency, mocker): # One field has an extra attribute, both strict + lenient. - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" self.lvalues["attributes"] = {"_a_common_": self.dummy} lmetadata = self.cls(**self.lvalues) rvalues = deepcopy(self.lvalues) - rvalues["attributes"]["_b_extra_"] = mock.sentinel.extra + rvalues["attributes"]["_b_extra_"] = mocker.sentinel.extra rmetadata = self.cls(**rvalues) if not is_lenient: # In this case, attributes returns a "difference dictionary" - diffentry = tuple([{}, {"_b_extra_": mock.sentinel.extra}]) + diffentry = tuple([{}, {"_b_extra_": mocker.sentinel.extra}]) lexpected = self.none._asdict() lexpected["attributes"] = diffentry rexpected = lexpected.copy() rexpected["attributes"] = diffentry[::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - if is_lenient: - # It recognises no difference - assert lmetadata.difference(rmetadata) is None - assert rmetadata.difference(lmetadata) is None - else: - # As calculated above - assert lmetadata.difference(rmetadata)._asdict() == lexpected - assert rmetadata.difference(lmetadata)._asdict() == rexpected + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + if is_lenient: + # It recognises no difference + assert lmetadata.difference(rmetadata) is None + assert rmetadata.difference(lmetadata) is None + else: + # As calculated above + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected - def test_op_different__attribute_value(self, op_leniency): + def test_op_different__attribute_value(self, leniency, mocker): # lhs and rhs have different values for an attribute, both strict + lenient. - is_lenient = op_leniency == "lenient" + is_lenient = leniency == "lenient" self.lvalues["attributes"] = { "_a_common_": self.dummy, - "_b_extra_": mock.sentinel.value1, + "_b_extra_": mocker.sentinel.value1, } lmetadata = self.cls(**self.lvalues) self.rvalues["attributes"] = { "_a_common_": self.dummy, - "_b_extra_": mock.sentinel.value2, + "_b_extra_": mocker.sentinel.value2, } rmetadata = self.cls(**self.rvalues) # In this case, attributes returns a "difference dictionary" diffentry = tuple( [ - {"_b_extra_": mock.sentinel.value1}, - {"_b_extra_": mock.sentinel.value2}, + {"_b_extra_": mocker.sentinel.value1}, + {"_b_extra_": mocker.sentinel.value2}, ] ) lexpected = self.none._asdict() @@ -932,190 +927,184 @@ def test_op_different__attribute_value(self, op_leniency): rexpected = lexpected.copy() rexpected["attributes"] = diffentry[::-1] - with mock.patch("iris.common.metadata._LENIENT", return_value=is_lenient): - # As calculated above -- same for both strict + lenient - assert lmetadata.difference(rmetadata)._asdict() == lexpected - assert rmetadata.difference(lmetadata)._asdict() == rexpected + mocker.patch("iris.common.metadata._LENIENT", return_value=is_lenient) + # As calculated above -- same for both strict + lenient + assert lmetadata.difference(rmetadata)._asdict() == lexpected + assert rmetadata.difference(lmetadata)._asdict() == rexpected -class Test_equal(tests.IrisTest): - def setUp(self): +class Test_equal: + @pytest.fixture(autouse=True) + def _setup(self): self.cls = CubeMetadata self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) + assert BaseMetadata.equal.__doc__ == self.cls.equal.__doc__ def test_lenient_service(self): qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - -class Test_name(tests.IrisTest): - def setUp(self): + assert qualname_equal in _LENIENT + assert _LENIENT[qualname_equal] + assert _LENIENT[self.cls.equal] + + def test_lenient_default(self, mocker): + other = mocker.sentinel.other + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object(BaseMetadata, "equal", return_value=return_value) + result = self.none.equal(other) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=None) + + def test_lenient(self, mocker): + other = mocker.sentinel.other + lenient = mocker.sentinel.lenient + return_value = mocker.sentinel.return_value + patcher = mocker.patch.object(BaseMetadata, "equal", return_value=return_value) + result = self.none.equal(other, lenient=lenient) + + assert result == return_value + assert patcher.call_count == 1 + (arg,), kwargs = patcher.call_args + assert arg == other + assert kwargs == dict(lenient=lenient) + + +class Test_name: + @pytest.fixture(autouse=True) + def _setup(self): self.default = CubeMetadata.DEFAULT_NAME def test_standard_name(self): token = "standard_name" metadata = _make_metadata(standard_name=token) result = metadata.name() - self.assertEqual(result, token) + assert result == token result = metadata.name(token=True) - self.assertEqual(result, token) + assert result == token def test_standard_name__invalid_token(self): token = "nope nope" metadata = _make_metadata(standard_name=token) result = metadata.name() - self.assertEqual(result, token) + assert result == token result = metadata.name(token=True) - self.assertEqual(result, self.default) + assert result == self.default def test_long_name(self): token = "long_name" metadata = _make_metadata(long_name=token) result = metadata.name() - self.assertEqual(result, token) + assert result == token result = metadata.name(token=True) - self.assertEqual(result, token) + assert result == token def test_long_name__invalid_token(self): token = "nope nope" metadata = _make_metadata(long_name=token) result = metadata.name() - self.assertEqual(result, token) + assert result == token result = metadata.name(token=True) - self.assertEqual(result, self.default) + assert result == self.default def test_var_name(self): token = "var_name" metadata = _make_metadata(var_name=token) result = metadata.name() - self.assertEqual(result, token) + assert result == token result = metadata.name(token=True) - self.assertEqual(result, token) + assert result == token def test_var_name__invalid_token(self): token = "nope nope" metadata = _make_metadata(var_name=token) result = metadata.name() - self.assertEqual(result, token) + assert result == token result = metadata.name(token=True) - self.assertEqual(result, self.default) + assert result == self.default def test_attributes(self): token = "stash" metadata = _make_metadata(attributes=token) result = metadata.name() - self.assertEqual(result, token) + assert result == token result = metadata.name(token=True) - self.assertEqual(result, token) + assert result == token def test_attributes__invalid_token(self): token = "nope nope" metadata = _make_metadata(attributes=token) result = metadata.name() - self.assertEqual(result, token) + assert result == token result = metadata.name(token=True) - self.assertEqual(result, self.default) + assert result == self.default def test_attributes__non_mapping(self): metadata = _make_metadata(force_mapping=False) - self.assertIsNone(metadata.attributes) + assert metadata.attributes is None emsg = "Invalid 'CubeMetadata.attributes' member, must be a mapping." - with self.assertRaisesRegex(AttributeError, emsg): + with pytest.raises(AttributeError, match=emsg): _ = metadata.name() def test_default(self): metadata = _make_metadata() result = metadata.name() - self.assertEqual(result, self.default) + assert result == self.default result = metadata.name(token=True) - self.assertEqual(result, self.default) + assert result == self.default def test_default__invalid_token(self): token = "nope nope" metadata = _make_metadata() result = metadata.name(default=token) - self.assertEqual(result, token) + assert result == token emsg = "Cannot retrieve a valid name token" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = metadata.name(default=token, token=True) -class Test__names(tests.IrisTest): +class Test__names: def test_standard_name(self): token = "standard_name" metadata = _make_metadata(standard_name=token) expected = (token, None, None, None) result = metadata._names - self.assertEqual(expected, result) + assert result == expected def test_long_name(self): token = "long_name" metadata = _make_metadata(long_name=token) expected = (None, token, None, None) result = metadata._names - self.assertEqual(expected, result) + assert result == expected def test_var_name(self): token = "var_name" metadata = _make_metadata(var_name=token) expected = (None, None, token, None) result = metadata._names - self.assertEqual(expected, result) + assert result == expected def test_attributes(self): token = "stash" metadata = _make_metadata(attributes=token) expected = (None, None, None, token) result = metadata._names - self.assertEqual(expected, result) + assert result == expected def test_attributes__non_mapping(self): metadata = _make_metadata(force_mapping=False) - self.assertIsNone(metadata.attributes) + assert metadata.attributes is None emsg = "Invalid 'CubeMetadata.attributes' member, must be a mapping." - with self.assertRaisesRegex(AttributeError, emsg): + with pytest.raises(AttributeError, match=emsg): _ = metadata._names - def test_None(self): + def test_none(self): metadata = _make_metadata() expected = (None, None, None, None) result = metadata._names - self.assertEqual(expected, result) - - -if __name__ == "__main__": - tests.main() + assert result == expected diff --git a/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py b/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py index 63d700e53e..426bf6416a 100644 --- a/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py +++ b/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py @@ -4,16 +4,14 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.common.metadata._NamedTupleMeta`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from abc import abstractmethod +import pytest + from iris.common.metadata import _NamedTupleMeta -class Test(tests.IrisTest): +class Test: @staticmethod def names(classes): return [cls.__name__ for cls in classes] @@ -42,11 +40,11 @@ def _members(self): pass expected = ["object"] - self.assertEqual(self.names(Metadata.__bases__), expected) + assert self.names(Metadata.__bases__) == expected expected = ["Metadata", "object"] - self.assertEqual(self.names(Metadata.__mro__), expected) + assert self.names(Metadata.__mro__) == expected emsg = "Can't instantiate abstract class" - with self.assertRaisesRegex(TypeError, emsg): + with pytest.raises(TypeError, match=emsg): _ = Metadata() def test__no_bases_single_member(self): @@ -56,15 +54,15 @@ class Metadata(metaclass=_NamedTupleMeta): _members = member expected = ["MetadataNamedtuple"] - self.assertEqual(self.names(Metadata.__bases__), expected) + assert self.names(Metadata.__bases__) == expected expected = ["Metadata", "MetadataNamedtuple", "tuple", "object"] - self.assertEqual(self.names(Metadata.__mro__), expected) + assert self.names(Metadata.__mro__) == expected emsg = self.emsg_generate(member) - with self.assertRaisesRegex(TypeError, emsg): + with pytest.raises(TypeError, match=emsg): _ = Metadata() metadata = Metadata(1) - self.assertEqual(metadata._fields, (member,)) - self.assertEqual(metadata.arg_one, 1) + assert metadata._fields == (member,) + assert metadata.arg_one == 1 def test__no_bases_multiple_members(self): members = ("arg_one", "arg_two") @@ -73,17 +71,17 @@ class Metadata(metaclass=_NamedTupleMeta): _members = members expected = ["MetadataNamedtuple"] - self.assertEqual(self.names(Metadata.__bases__), expected) + assert self.names(Metadata.__bases__) == expected expected = ["Metadata", "MetadataNamedtuple", "tuple", "object"] - self.assertEqual(self.names(Metadata.__mro__), expected) + assert self.names(Metadata.__mro__) == expected emsg = self.emsg_generate(members) - with self.assertRaisesRegex(TypeError, emsg): + with pytest.raises(TypeError, match=emsg): _ = Metadata() values = range(len(members)) metadata = Metadata(*values) - self.assertEqual(metadata._fields, members) + assert metadata._fields == members expected = dict(zip(members, values)) - self.assertEqual(metadata._asdict(), expected) + assert metadata._asdict() == expected def test__multiple_bases_multiple_members(self): members_parent = ("arg_one", "arg_two") @@ -97,26 +95,26 @@ class MetadataChild(MetadataParent): # Check the parent class... expected = ["MetadataParentNamedtuple"] - self.assertEqual(self.names(MetadataParent.__bases__), expected) + assert self.names(MetadataParent.__bases__) == expected expected = [ "MetadataParent", "MetadataParentNamedtuple", "tuple", "object", ] - self.assertEqual(self.names(MetadataParent.__mro__), expected) + assert self.names(MetadataParent.__mro__) == expected emsg = self.emsg_generate(members_parent) - with self.assertRaisesRegex(TypeError, emsg): + with pytest.raises(TypeError, match=emsg): _ = MetadataParent() values_parent = range(len(members_parent)) metadata_parent = MetadataParent(*values_parent) - self.assertEqual(metadata_parent._fields, members_parent) + assert metadata_parent._fields == members_parent expected = dict(zip(members_parent, values_parent)) - self.assertEqual(metadata_parent._asdict(), expected) + assert metadata_parent._asdict() == expected # Check the dependent child class... expected = ["MetadataChildNamedtuple", "MetadataParent"] - self.assertEqual(self.names(MetadataChild.__bases__), expected) + assert self.names(MetadataChild.__bases__) == expected expected = [ "MetadataChild", "MetadataChildNamedtuple", @@ -125,17 +123,13 @@ class MetadataChild(MetadataParent): "tuple", "object", ] - self.assertEqual(self.names(MetadataChild.__mro__), expected) + assert self.names(MetadataChild.__mro__) == expected emsg = self.emsg_generate((*members_parent, *members_child)) - with self.assertRaisesRegex(TypeError, emsg): + with pytest.raises(TypeError, match=emsg): _ = MetadataChild() fields_child = (*members_parent, *members_child) values_child = range(len(fields_child)) metadata_child = MetadataChild(*values_child) - self.assertEqual(metadata_child._fields, fields_child) + assert metadata_child._fields == fields_child expected = dict(zip(fields_child, values_child)) - self.assertEqual(metadata_child._asdict(), expected) - - -if __name__ == "__main__": - tests.main() + assert metadata_child._asdict() == expected diff --git a/lib/iris/tests/unit/common/metadata/test_hexdigest.py b/lib/iris/tests/unit/common/metadata/test_hexdigest.py index 1a0a0e0120..0ce0dcd706 100644 --- a/lib/iris/tests/unit/common/metadata/test_hexdigest.py +++ b/lib/iris/tests/unit/common/metadata/test_hexdigest.py @@ -4,21 +4,17 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :func:`iris.common.metadata.hexdigest`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - import numpy as np import numpy.ma as ma +import pytest from xxhash import xxh64, xxh64_hexdigest from iris.common.metadata import hexdigest -class TestBytesLikeObject(tests.IrisTest): - def setUp(self): +class TestBytesLikeObject: + @pytest.fixture(autouse=True) + def _setup(self): self.hasher = xxh64() self.hasher.reset() @@ -42,77 +38,77 @@ def test_string(self): value = "hello world" self.hasher.update(value) expected = self.hasher.hexdigest() - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_numpy_array_int(self): value = np.arange(10, dtype=np.int_) expected = self._ndarray(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_numpy_array_float(self): value = np.arange(10, dtype=np.float64) expected = self._ndarray(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_numpy_array_float_not_int(self): ivalue = np.arange(10, dtype=np.int_) fvalue = np.arange(10, dtype=np.float64) expected = self._ndarray(ivalue) - self.assertNotEqual(expected, hexdigest(fvalue)) + assert hexdigest(fvalue) != expected def test_numpy_array_reshape(self): value = np.arange(10).reshape(2, 5) expected = self._ndarray(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_numpy_array_reshape_not_flat(self): value = np.arange(10).reshape(2, 5) expected = self._ndarray(value) - self.assertNotEqual(expected, hexdigest(value.flatten())) + assert hexdigest(value.flatten()) != expected def test_masked_array_int(self): value = ma.arange(10, dtype=np.int_) expected = self._masked(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected value[0] = ma.masked - self.assertNotEqual(expected, hexdigest(value)) + assert hexdigest(value) != expected expected = self._masked(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_masked_array_float(self): value = ma.arange(10, dtype=np.float64) expected = self._masked(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected value[0] = ma.masked - self.assertNotEqual(expected, hexdigest(value)) + assert hexdigest(value) != expected expected = self._masked(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_masked_array_float_not_int(self): ivalue = ma.arange(10, dtype=np.int_) fvalue = ma.arange(10, dtype=np.float64) expected = self._masked(ivalue) - self.assertNotEqual(expected, hexdigest(fvalue)) + assert hexdigest(fvalue) != expected def test_masked_array_not_array(self): value = ma.arange(10) expected = self._masked(value) - self.assertNotEqual(expected, hexdigest(value.data)) + assert hexdigest(value.data) != expected def test_masked_array_reshape(self): value = ma.arange(10).reshape(2, 5) expected = self._masked(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_masked_array_reshape_not_flat(self): value = ma.arange(10).reshape(2, 5) expected = self._masked(value) - self.assertNotEqual(expected, hexdigest(value.flatten())) + assert hexdigest(value.flatten()) != expected -class TestNotBytesLikeObject(tests.IrisTest): +class TestNotBytesLikeObject: def _expected(self, value): parts = str((type(value), value)) return xxh64_hexdigest(parts) @@ -120,42 +116,42 @@ def _expected(self, value): def test_int(self): value = 123 expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_numpy_int(self): value = int(123) expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_float(self): value = 123.4 expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_numpy_float(self): value = float(123.4) expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_list(self): value = [1, 2, 3] expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_tuple(self): value = (1, 2, 3) expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_dict(self): value = dict(one=1, two=2, three=3) expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected - def test_sentinel(self): - value = mock.sentinel.value + def test_sentinel(self, mocker): + value = mocker.sentinel.value expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_instance(self): class Dummy: @@ -163,13 +159,9 @@ class Dummy: value = Dummy() expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) + assert hexdigest(value) == expected def test_int_not_str(self): value = 123 expected = self._expected(value) - self.assertNotEqual(expected, hexdigest(str(value))) - - -if __name__ == "__main__": - tests.main() + assert hexdigest(str(value)) != expected diff --git a/lib/iris/tests/unit/common/metadata/test_metadata_filter.py b/lib/iris/tests/unit/common/metadata/test_metadata_filter.py index 586a5fe5f8..792e1b83b4 100644 --- a/lib/iris/tests/unit/common/metadata/test_metadata_filter.py +++ b/lib/iris/tests/unit/common/metadata/test_metadata_filter.py @@ -5,120 +5,113 @@ """Unit tests for the :func:`iris.common.metadata_filter`.""" import numpy as np +import pytest from iris.common.metadata import CoordMetadata, DimCoordMetadata, metadata_filter from iris.coords import AuxCoord -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests -Mock = tests.mock.Mock - - -class Test_standard(tests.IrisTest): - def test_instances_non_iterable(self): - item = Mock() +class Test_standard: + def test_instances_non_iterable(self, mocker): + item = mocker.Mock() item.name.return_value = "one" result = metadata_filter(item, item="one") - self.assertEqual(1, len(result)) - self.assertIn(item, result) + assert len(result) == 1 + assert item in result - def test_name(self): - name_one = Mock() + def test_name(self, mocker): + name_one = mocker.Mock() name_one.name.return_value = "one" - name_two = Mock() + name_two = mocker.Mock() name_two.name.return_value = "two" input_list = [name_one, name_two] result = metadata_filter(input_list, item="one") - self.assertIn(name_one, result) - self.assertNotIn(name_two, result) + assert name_one in result + assert name_two not in result - def test_item(self): - coord = Mock(__class__=AuxCoord) - mock = Mock() + def test_item(self, mocker): + coord = mocker.Mock(__class__=AuxCoord) + mock = mocker.Mock() input_list = [coord, mock] result = metadata_filter(input_list, item=coord) - self.assertIn(coord, result) - self.assertNotIn(mock, result) + assert coord in result + assert mock not in result - def test_item_metadata(self): - coord = Mock(metadata=CoordMetadata) - dim_coord = Mock(metadata=DimCoordMetadata) + def test_item_metadata(self, mocker): + coord = mocker.Mock(metadata=CoordMetadata) + dim_coord = mocker.Mock(metadata=DimCoordMetadata) input_list = [coord, dim_coord] result = metadata_filter(input_list, item=coord) - self.assertIn(coord, result) - self.assertNotIn(dim_coord, result) + assert coord in result + assert dim_coord not in result - def test_standard_name(self): - name_one = Mock(standard_name="one") - name_two = Mock(standard_name="two") + def test_standard_name(self, mocker): + name_one = mocker.Mock(standard_name="one") + name_two = mocker.Mock(standard_name="two") input_list = [name_one, name_two] result = metadata_filter(input_list, standard_name="one") - self.assertIn(name_one, result) - self.assertNotIn(name_two, result) + assert name_one in result + assert name_two not in result - def test_long_name(self): - name_one = Mock(long_name="one") - name_two = Mock(long_name="two") + def test_long_name(self, mocker): + name_one = mocker.Mock(long_name="one") + name_two = mocker.Mock(long_name="two") input_list = [name_one, name_two] result = metadata_filter(input_list, long_name="one") - self.assertIn(name_one, result) - self.assertNotIn(name_two, result) + assert name_one in result + assert name_two not in result - def test_var_name(self): - name_one = Mock(var_name="one") - name_two = Mock(var_name="two") + def test_var_name(self, mocker): + name_one = mocker.Mock(var_name="one") + name_two = mocker.Mock(var_name="two") input_list = [name_one, name_two] result = metadata_filter(input_list, var_name="one") - self.assertIn(name_one, result) - self.assertNotIn(name_two, result) + assert name_one in result + assert name_two not in result - def test_attributes(self): + def test_attributes(self, mocker): # Confirm that this can handle attrib dicts including np arrays. - attrib_one_two = Mock(attributes={"one": np.arange(1), "two": np.arange(2)}) - attrib_three_four = Mock( + attrib_one_two = mocker.Mock( + attributes={"one": np.arange(1), "two": np.arange(2)} + ) + attrib_three_four = mocker.Mock( attributes={"three": np.arange(3), "four": np.arange(4)} ) input_list = [attrib_one_two, attrib_three_four] result = metadata_filter(input_list, attributes=attrib_one_two.attributes) - self.assertIn(attrib_one_two, result) - self.assertNotIn(attrib_three_four, result) + assert attrib_one_two in result + assert attrib_three_four not in result - def test_invalid_attributes(self): - attrib_one = Mock(attributes={"one": 1}) + def test_invalid_attributes(self, mocker): + attrib_one = mocker.Mock(attributes={"one": 1}) input_list = [attrib_one] - self.assertRaisesRegex( - ValueError, - ".*expecting a dictionary.*", - metadata_filter, - input_list, - attributes="one", - ) + emsg = ".*expecting a dictionary.*" + with pytest.raises(ValueError, match=emsg): + _ = metadata_filter(input_list, attributes="one") - def test_axis__by_guess(self): + def test_axis__by_guess(self, mocker): # see https://docs.python.org/3/library/unittest.mock.html#deleting-attributes - axis_lon = Mock(standard_name="longitude") + axis_lon = mocker.Mock(standard_name="longitude") del axis_lon.axis - axis_lat = Mock(standard_name="latitude") + axis_lat = mocker.Mock(standard_name="latitude") del axis_lat.axis input_list = [axis_lon, axis_lat] result = metadata_filter(input_list, axis="x") - self.assertIn(axis_lon, result) - self.assertNotIn(axis_lat, result) + assert axis_lon in result + assert axis_lat not in result - def test_axis__by_member(self): - axis_x = Mock(axis="x") - axis_y = Mock(axis="y") + def test_axis__by_member(self, mocker): + axis_x = mocker.Mock(axis="x") + axis_y = mocker.Mock(axis="y") input_list = [axis_x, axis_y] result = metadata_filter(input_list, axis="x") - self.assertEqual(1, len(result)) - self.assertIn(axis_x, result) + assert len(result) == 1 + assert axis_x in result - def test_multiple_args(self): - coord_one = Mock(__class__=AuxCoord, long_name="one") - coord_two = Mock(__class__=AuxCoord, long_name="two") + def test_multiple_args(self, mocker): + coord_one = mocker.Mock(__class__=AuxCoord, long_name="one") + coord_two = mocker.Mock(__class__=AuxCoord, long_name="two") input_list = [coord_one, coord_two] result = metadata_filter(input_list, item=coord_one, long_name="one") - self.assertIn(coord_one, result) - self.assertNotIn(coord_two, result) + assert coord_one in result + assert coord_two not in result diff --git a/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py b/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py index 1fbf0da084..ab1a8eeb6c 100644 --- a/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py +++ b/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py @@ -4,14 +4,10 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :func:`iris.common.metadata.metadata_manager_factory`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import pickle -import unittest.mock as mock from cf_units import Unit +import pytest from iris.common.metadata import ( AncillaryVariableMetadata, @@ -33,15 +29,16 @@ ] -class Test_factory(tests.IrisTest): +class Test_factory: def test__kwargs_invalid(self): emsg = "Invalid 'BaseMetadata' field parameters, got 'wibble'." - with self.assertRaisesRegex(ValueError, emsg): - metadata_manager_factory(BaseMetadata, wibble="nope") + with pytest.raises(ValueError, match=emsg): + _ = metadata_manager_factory(BaseMetadata, wibble="nope") -class Test_instance(tests.IrisTest): - def setUp(self): +class Test_instance: + @pytest.fixture(autouse=True) + def _setup(self): self.bases = BASES def test__namespace(self): @@ -62,70 +59,72 @@ def test__namespace(self): for base in self.bases: metadata = metadata_manager_factory(base) for name in namespace: - self.assertTrue(hasattr(metadata, name)) + assert hasattr(metadata, name) if base is CubeMetadata: - self.assertTrue(hasattr(metadata, "_names")) - self.assertIs(metadata.cls, base) + assert hasattr(metadata, "_names") + assert metadata.cls is base def test__kwargs_default(self): for base in self.bases: kwargs = dict(zip(base._fields, [None] * len(base._fields))) metadata = metadata_manager_factory(base) - self.assertEqual(metadata.values._asdict(), kwargs) + assert metadata.values._asdict() == kwargs def test__kwargs(self): for base in self.bases: kwargs = dict(zip(base._fields, range(len(base._fields)))) metadata = metadata_manager_factory(base, **kwargs) - self.assertEqual(metadata.values._asdict(), kwargs) + assert metadata.values._asdict() == kwargs -class Test_instance___eq__(tests.IrisTest): - def setUp(self): +class Test_instance___eq__: + @pytest.fixture(autouse=True) + def _setup(self): self.metadata = metadata_manager_factory(BaseMetadata) def test__not_implemented(self): - self.assertNotEqual(self.metadata, 1) + assert self.metadata != 1 def test__not_is_cls(self): base = BaseMetadata other = metadata_manager_factory(base) - self.assertIs(other.cls, base) + assert other.cls is base other.cls = CoordMetadata - self.assertNotEqual(self.metadata, other) + assert other != self.metadata - def test__not_values(self): - standard_name = mock.sentinel.standard_name + def test__not_values(self, mocker): + standard_name = mocker.sentinel.standard_name other = metadata_manager_factory(BaseMetadata, standard_name=standard_name) - self.assertEqual(other.standard_name, standard_name) - self.assertIsNone(other.long_name) - self.assertIsNone(other.var_name) - self.assertIsNone(other.units) - self.assertIsNone(other.attributes) - self.assertNotEqual(self.metadata, other) + assert other.standard_name == standard_name + assert other.long_name is None + assert other.var_name is None + assert other.units is None + assert other.attributes is None + assert other != self.metadata def test__same_default(self): other = metadata_manager_factory(BaseMetadata) - self.assertEqual(self.metadata, other) + assert other == self.metadata def test__same(self): kwargs = dict(standard_name=1, long_name=2, var_name=3, units=4, attributes=5) metadata = metadata_manager_factory(BaseMetadata, **kwargs) other = metadata_manager_factory(BaseMetadata, **kwargs) - self.assertEqual(metadata.values._asdict(), kwargs) - self.assertEqual(metadata, other) + assert metadata.values._asdict() == kwargs + assert metadata == other -class Test_instance____repr__(tests.IrisTest): - def setUp(self): +class Test_instance____repr__: + @pytest.fixture(autouse=True) + def _setup(self): self.metadata = metadata_manager_factory(BaseMetadata) - def test(self): - standard_name = mock.sentinel.standard_name - long_name = mock.sentinel.long_name - var_name = mock.sentinel.var_name - units = mock.sentinel.units - attributes = mock.sentinel.attributes + def test(self, mocker): + standard_name = mocker.sentinel.standard_name + long_name = mocker.sentinel.long_name + var_name = mocker.sentinel.var_name + units = mocker.sentinel.units + attributes = mocker.sentinel.attributes values = (standard_name, long_name, var_name, units, attributes) for field, value in zip(self.metadata.fields, values): @@ -136,11 +135,12 @@ def test(self): "MetadataManager(standard_name={!r}, long_name={!r}, var_name={!r}, " "units={!r}, attributes={!r})" ) - self.assertEqual(result, expected.format(*values)) + assert result == expected.format(*values) -class Test_instance__pickle(tests.IrisTest): - def setUp(self): +class Test_instance__pickle: + @pytest.fixture(autouse=True) + def _setup(self): self.standard_name = "standard_name" self.long_name = "long_name" self.var_name = "var_name" @@ -156,40 +156,38 @@ def setUp(self): kwargs = dict(zip(BaseMetadata._fields, values)) self.metadata = metadata_manager_factory(BaseMetadata, **kwargs) - def test_pickle(self): + def test_pickle(self, tmp_path): for protocol in range(pickle.HIGHEST_PROTOCOL + 1): - with self.temp_filename(suffix=".pkl") as fname: - with open(fname, "wb") as fout: - pickle.dump(self.metadata, fout, protocol=protocol) - with open(fname, "rb") as fin: - metadata = pickle.load(fin) - self.assertEqual(metadata, self.metadata) + fname = tmp_path / f"pickle_{protocol}.pkl" + with open(fname, "wb") as fout: + pickle.dump(self.metadata, fout, protocol=protocol) + with open(fname, "rb") as fin: + metadata = pickle.load(fin) + assert metadata == self.metadata -class Test_instance__fields(tests.IrisTest): - def setUp(self): +class Test_instance__fields: + @pytest.fixture(autouse=True) + def _setup(self): self.bases = BASES def test(self): for base in self.bases: fields = base._fields metadata = metadata_manager_factory(base) - self.assertEqual(metadata.fields, fields) + assert metadata.fields == fields for field in fields: - hasattr(metadata, field) + assert hasattr(metadata, field) -class Test_instance__values(tests.IrisTest): - def setUp(self): +class Test_instance__values: + @pytest.fixture(autouse=True) + def _setup(self): self.bases = BASES def test(self): for base in self.bases: metadata = metadata_manager_factory(base) result = metadata.values - self.assertIsInstance(result, base) - self.assertEqual(result._fields, base._fields) - - -if __name__ == "__main__": - tests.main() + assert isinstance(result, base) + assert result._fields == base._fields diff --git a/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py b/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py index 7d414bfb54..c6de740ba1 100644 --- a/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py +++ b/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py @@ -4,14 +4,10 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.common.mixin.CFVariableMixin`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from collections import OrderedDict, namedtuple -from unittest import mock from cf_units import Unit +import pytest from iris.common.metadata import ( AncillaryVariableMetadata, @@ -24,16 +20,17 @@ from iris.common.mixin import CFVariableMixin, LimitedAttributeDict -class Test__getter(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.metadata = mock.sentinel.metadata +class Test__getter: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.standard_name = mocker.sentinel.standard_name + self.long_name = mocker.sentinel.long_name + self.var_name = mocker.sentinel.var_name + self.units = mocker.sentinel.units + self.attributes = mocker.sentinel.attributes + self.metadata = mocker.sentinel.metadata - metadata = mock.MagicMock( + metadata = mocker.MagicMock( standard_name=self.standard_name, long_name=self.long_name, var_name=self.var_name, @@ -46,32 +43,33 @@ def setUp(self): self.item._metadata_manager = metadata def test_standard_name(self): - self.assertEqual(self.item.standard_name, self.standard_name) + assert self.item.standard_name == self.standard_name def test_long_name(self): - self.assertEqual(self.item.long_name, self.long_name) + assert self.item.long_name == self.long_name def test_var_name(self): - self.assertEqual(self.item.var_name, self.var_name) + assert self.item.var_name == self.var_name def test_units(self): - self.assertEqual(self.item.units, self.units) + assert self.item.units == self.units def test_attributes(self): - self.assertEqual(self.item.attributes, self.attributes) + assert self.item.attributes == self.attributes def test_metadata(self): - self.assertEqual(self.item.metadata, self.metadata) - - -class Test__setter(tests.IrisTest): - def setUp(self): - metadata = mock.MagicMock( - standard_name=mock.sentinel.standard_name, - long_name=mock.sentinel.long_name, - var_name=mock.sentinel.var_name, - units=mock.sentinel.units, - attributes=mock.sentinel.attributes, + assert self.item.metadata == self.metadata + + +class Test__setter: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + metadata = mocker.MagicMock( + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, token=lambda name: name, ) @@ -81,68 +79,67 @@ def setUp(self): def test_standard_name__valid(self): standard_name = "air_temperature" self.item.standard_name = standard_name - self.assertEqual(self.item._metadata_manager.standard_name, standard_name) + assert self.item._metadata_manager.standard_name == standard_name def test_standard_name__none(self): self.item.standard_name = None - self.assertIsNone(self.item._metadata_manager.standard_name) + assert self.item._metadata_manager.standard_name is None def test_standard_name__invalid(self): standard_name = "nope nope" emsg = f"{standard_name!r} is not a valid standard_name" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): self.item.standard_name = standard_name def test_long_name(self): long_name = "long_name" self.item.long_name = long_name - self.assertEqual(self.item._metadata_manager.long_name, long_name) + assert self.item._metadata_manager.long_name == long_name def test_long_name__none(self): self.item.long_name = None - self.assertIsNone(self.item._metadata_manager.long_name) + assert self.item._metadata_manager.long_name is None def test_var_name(self): var_name = "var_name" self.item.var_name = var_name - self.assertEqual(self.item._metadata_manager.var_name, var_name) + assert self.item._metadata_manager.var_name == var_name def test_var_name__none(self): self.item.var_name = None - self.assertIsNone(self.item._metadata_manager.var_name) + assert self.item._metadata_manager.var_name is None def test_var_name__invalid_token(self): var_name = "nope nope" self.item._metadata_manager.token = lambda name: None emsg = f"{var_name!r} is not a valid NetCDF variable name." - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): self.item.var_name = var_name def test_attributes(self): attributes = dict(hello="world") self.item.attributes = attributes - self.assertEqual(self.item._metadata_manager.attributes, attributes) - self.assertIsNot(self.item._metadata_manager.attributes, attributes) - self.assertIsInstance( - self.item._metadata_manager.attributes, LimitedAttributeDict - ) + assert self.item._metadata_manager.attributes == attributes + assert self.item._metadata_manager.attributes is not attributes + assert isinstance(self.item._metadata_manager.attributes, LimitedAttributeDict) def test_attributes__none(self): self.item.attributes = None - self.assertEqual(self.item._metadata_manager.attributes, {}) + assert self.item._metadata_manager.attributes == {} -class Test__metadata_setter(tests.IrisTest): - def setUp(self): +class Test__metadata_setter: + @pytest.fixture(autouse=True) + def _setup(self, mocker): class Metadata: def __init__(self): self.cls = BaseMetadata self.fields = BaseMetadata._fields - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes + self.standard_name = mocker.sentinel.standard_name + self.long_name = mocker.sentinel.long_name + self.var_name = mocker.sentinel.var_name + self.units = mocker.sentinel.units + self.attributes = mocker.sentinel.attributes self.token = lambda name: name @property @@ -170,31 +167,31 @@ def values(self): def test_dict(self): metadata = dict(**self.args) self.item.metadata = metadata - self.assertEqual(self.item._metadata_manager.values, metadata) - self.assertIsNot(self.item._metadata_manager.attributes, self.attributes) + assert self.item._metadata_manager.values == metadata + assert self.item._metadata_manager.attributes is not self.attributes - def test_dict__partial(self): + def test_dict__partial(self, mocker): metadata = dict(**self.args) del metadata["standard_name"] self.item.metadata = metadata - metadata["standard_name"] = mock.sentinel.standard_name - self.assertEqual(self.item._metadata_manager.values, metadata) - self.assertIsNot(self.item._metadata_manager.attributes, self.attributes) + metadata["standard_name"] = mocker.sentinel.standard_name + assert self.item._metadata_manager.values == metadata + assert self.item._metadata_manager.attributes is not self.attributes def test_ordereddict(self): metadata = self.args self.item.metadata = metadata - self.assertEqual(self.item._metadata_manager.values, metadata) - self.assertIsNot(self.item._metadata_manager.attributes, self.attributes) + assert self.item._metadata_manager.values == metadata + assert self.item._metadata_manager.attributes is not self.attributes - def test_ordereddict__partial(self): + def test_ordereddict__partial(self, mocker): metadata = self.args del metadata["long_name"] del metadata["units"] self.item.metadata = metadata - metadata["long_name"] = mock.sentinel.long_name - metadata["units"] = mock.sentinel.units - self.assertEqual(self.item._metadata_manager.values, metadata) + metadata["long_name"] = mocker.sentinel.long_name + metadata["units"] = mocker.sentinel.units + assert self.item._metadata_manager.values == metadata def test_tuple(self): metadata = tuple(self.args.values()) @@ -205,14 +202,14 @@ def test_tuple(self): for field in self.item._metadata_manager.fields ] ) - self.assertEqual(result, metadata) - self.assertIsNot(self.item._metadata_manager.attributes, self.attributes) + assert result == metadata + assert self.item._metadata_manager.attributes is not self.attributes def test_tuple__missing(self): metadata = list(self.args.values()) del metadata[2] emsg = "Invalid .* metadata, require .* to be specified." - with self.assertRaisesRegex(TypeError, emsg): + with pytest.raises(TypeError, match=emsg): self.item.metadata = tuple(metadata) def test_namedtuple(self): @@ -222,10 +219,10 @@ def test_namedtuple(self): ) metadata = Metadata(**self.args) self.item.metadata = metadata - self.assertEqual(self.item._metadata_manager.values, metadata._asdict()) - self.assertIsNot(self.item._metadata_manager.attributes, metadata.attributes) + assert self.item._metadata_manager.values == metadata._asdict() + assert self.item._metadata_manager.attributes is not metadata.attributes - def test_namedtuple__partial(self): + def test_namedtuple__partial(self, mocker): Metadata = namedtuple( "Metadata", ("standard_name", "long_name", "var_name", "units") ) @@ -233,20 +230,20 @@ def test_namedtuple__partial(self): metadata = Metadata(**self.args) self.item.metadata = metadata expected = metadata._asdict() - expected.update(dict(attributes=mock.sentinel.attributes)) - self.assertEqual(self.item._metadata_manager.values, expected) + expected.update(dict(attributes=mocker.sentinel.attributes)) + assert self.item._metadata_manager.values == expected def test_class_ancillaryvariablemetadata(self): metadata = AncillaryVariableMetadata(**self.args) self.item.metadata = metadata - self.assertEqual(self.item._metadata_manager.values, metadata._asdict()) - self.assertIsNot(self.item._metadata_manager.attributes, metadata.attributes) + assert self.item._metadata_manager.values == metadata._asdict() + assert self.item._metadata_manager.attributes is not metadata.attributes def test_class_basemetadata(self): metadata = BaseMetadata(**self.args) self.item.metadata = metadata - self.assertEqual(self.item._metadata_manager.values, metadata._asdict()) - self.assertIsNot(self.item._metadata_manager.attributes, metadata.attributes) + assert self.item._metadata_manager.values == metadata._asdict() + assert self.item._metadata_manager.attributes is not metadata.attributes def test_class_cellmeasuremetadata(self): self.args["measure"] = None @@ -254,8 +251,8 @@ def test_class_cellmeasuremetadata(self): self.item.metadata = metadata expected = metadata._asdict() del expected["measure"] - self.assertEqual(self.item._metadata_manager.values, expected) - self.assertIsNot(self.item._metadata_manager.attributes, metadata.attributes) + assert self.item._metadata_manager.values == expected + assert self.item._metadata_manager.attributes is not metadata.attributes def test_class_connectivitymetadata(self): self.args.update(dict(cf_role=None, start_index=None, location_axis=None)) @@ -265,8 +262,8 @@ def test_class_connectivitymetadata(self): del expected["cf_role"] del expected["start_index"] del expected["location_axis"] - self.assertEqual(self.item._metadata_manager.values, expected) - self.assertIsNot(self.item._metadata_manager.attributes, metadata.attributes) + assert self.item._metadata_manager.values == expected + assert self.item._metadata_manager.attributes is not metadata.attributes def test_class_coordmetadata(self): self.args.update(dict(coord_system=None, climatological=False)) @@ -275,8 +272,8 @@ def test_class_coordmetadata(self): expected = metadata._asdict() del expected["coord_system"] del expected["climatological"] - self.assertEqual(self.item._metadata_manager.values, expected) - self.assertIsNot(self.item._metadata_manager.attributes, metadata.attributes) + assert self.item._metadata_manager.values == expected + assert self.item._metadata_manager.attributes is not metadata.attributes def test_class_cubemetadata(self): self.args["cell_methods"] = None @@ -284,19 +281,20 @@ def test_class_cubemetadata(self): self.item.metadata = metadata expected = metadata._asdict() del expected["cell_methods"] - self.assertEqual(self.item._metadata_manager.values, expected) - self.assertIsNot(self.item._metadata_manager.attributes, metadata.attributes) - - -class Test_rename(tests.IrisTest): - def setUp(self): - metadata = mock.MagicMock( - standard_name=mock.sentinel.standard_name, - long_name=mock.sentinel.long_name, - var_name=mock.sentinel.var_name, - units=mock.sentinel.units, - attributes=mock.sentinel.attributes, - values=mock.sentinel.metadata, + assert self.item._metadata_manager.values == expected + assert self.item._metadata_manager.attributes is not metadata.attributes + + +class Test_rename: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + metadata = mocker.MagicMock( + standard_name=mocker.sentinel.standard_name, + long_name=mocker.sentinel.long_name, + var_name=mocker.sentinel.var_name, + units=mocker.sentinel.units, + attributes=mocker.sentinel.attributes, + values=mocker.sentinel.metadata, token=lambda name: name, ) @@ -306,39 +304,36 @@ def setUp(self): def test__valid_standard_name(self): name = "air_temperature" self.item.rename(name) - self.assertEqual(self.item._metadata_manager.standard_name, name) - self.assertIsNone(self.item._metadata_manager.long_name) - self.assertIsNone(self.item._metadata_manager.var_name) + assert self.item._metadata_manager.standard_name == name + assert self.item._metadata_manager.long_name is None + assert self.item._metadata_manager.var_name is None def test__invalid_standard_name(self): name = "nope nope" self.item.rename(name) - self.assertIsNone(self.item._metadata_manager.standard_name) - self.assertEqual(self.item._metadata_manager.long_name, name) - self.assertIsNone(self.item._metadata_manager.var_name) + assert self.item._metadata_manager.standard_name is None + assert self.item._metadata_manager.long_name == name + assert self.item._metadata_manager.var_name is None -class Test_name(tests.IrisTest): - def setUp(self): +class Test_name: + @pytest.fixture(autouse=True) + def _setup(self, mocker): class Metadata: def __init__(self, name): - self.name = mock.MagicMock(return_value=name) + self.name = mocker.MagicMock(return_value=name) - self.name = mock.sentinel.name + self.name = mocker.sentinel.name metadata = Metadata(self.name) self.item = CFVariableMixin() self.item._metadata_manager = metadata - def test(self): - default = mock.sentinel.default - token = mock.sentinel.token + def test(self, mocker): + default = mocker.sentinel.default + token = mocker.sentinel.token result = self.item.name(default=default, token=token) - self.assertEqual(result, self.name) + assert result == self.name self.item._metadata_manager.name.assert_called_with( default=default, token=token ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/mixin/test_LimitedAttributeDict.py b/lib/iris/tests/unit/common/mixin/test_LimitedAttributeDict.py index dfb49f0f8d..ca8fd97c39 100644 --- a/lib/iris/tests/unit/common/mixin/test_LimitedAttributeDict.py +++ b/lib/iris/tests/unit/common/mixin/test_LimitedAttributeDict.py @@ -4,50 +4,45 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.common.mixin.LimitedAttributeDict`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - import numpy as np +import pytest from iris.common.mixin import LimitedAttributeDict -class Test(tests.IrisTest): - def setUp(self): +class Test: + @pytest.fixture(autouse=True) + def _setup(self): self.forbidden_keys = LimitedAttributeDict.CF_ATTRS_FORBIDDEN self.emsg = "{!r} is not a permitted attribute" def test__invalid_keys(self): for key in self.forbidden_keys: - with self.assertRaisesRegex(ValueError, self.emsg.format(key)): + with pytest.raises(ValueError, match=self.emsg.format(key)): _ = LimitedAttributeDict(**{key: None}) - def test___eq__(self): + def test___eq__(self, mocker): values = dict( - one=mock.sentinel.one, - two=mock.sentinel.two, - three=mock.sentinel.three, + one=mocker.sentinel.one, + two=mocker.sentinel.two, + three=mocker.sentinel.three, ) left = LimitedAttributeDict(**values) right = LimitedAttributeDict(**values) - self.assertEqual(left, right) - self.assertEqual(left, values) + assert left == right + assert left == values def test___eq___numpy(self): values = dict(one=np.arange(1), two=np.arange(2), three=np.arange(3)) left = LimitedAttributeDict(**values) right = LimitedAttributeDict(**values) - self.assertEqual(left, right) - self.assertEqual(left, values) - + assert left == right + assert left == values values = dict(one=np.arange(1), two=np.arange(1), three=np.arange(1)) left = LimitedAttributeDict(dict(one=0, two=0, three=0)) right = LimitedAttributeDict(**values) - self.assertEqual(left, right) - self.assertEqual(left, values) + assert left == right + assert left == values # Test inequality: values = dict(one=np.arange(1), two=np.arange(2), three=np.arange(3)) @@ -55,22 +50,18 @@ def test___eq___numpy(self): right = LimitedAttributeDict( one=np.arange(3), two=np.arange(2), three=np.arange(1) ) - self.assertNotEqual(left, right) - self.assertNotEqual(values, right) + assert right != left + assert right != values def test___setitem__(self): for key in self.forbidden_keys: item = LimitedAttributeDict() - with self.assertRaisesRegex(ValueError, self.emsg.format(key)): + with pytest.raises(ValueError, match=self.emsg.format(key)): item[key] = None def test_update(self): for key in self.forbidden_keys: item = LimitedAttributeDict() - with self.assertRaisesRegex(ValueError, self.emsg.format(key)): - other = {key: None} + other = {key: None} + with pytest.raises(ValueError, match=self.emsg.format(key)): item.update(other) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/mixin/test__get_valid_standard_name.py b/lib/iris/tests/unit/common/mixin/test__get_valid_standard_name.py index 67ba108333..c7be9d48ca 100644 --- a/lib/iris/tests/unit/common/mixin/test__get_valid_standard_name.py +++ b/lib/iris/tests/unit/common/mixin/test__get_valid_standard_name.py @@ -4,65 +4,60 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :func:`iris.common.mixin._get_valid_standard_name`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip +import pytest from iris.common.mixin import _get_valid_standard_name -class Test(tests.IrisTest): - def setUp(self): +class Test: + @pytest.fixture(autouse=True) + def _setup(self): self.emsg = "'{}' is not a valid standard_name" def test_pass_thru_none(self): name = None - self.assertEqual(_get_valid_standard_name(name), name) + assert _get_valid_standard_name(name) == name def test_pass_thru_empty(self): name = "" - self.assertEqual(_get_valid_standard_name(name), name) + assert _get_valid_standard_name(name) == name def test_pass_thru_whitespace(self): name = " " - self.assertEqual(_get_valid_standard_name(name), name) + assert _get_valid_standard_name(name) == name def test_valid_standard_name(self): name = "air_temperature" - self.assertEqual(_get_valid_standard_name(name), name) + assert _get_valid_standard_name(name) == name def test_standard_name_alias(self): name = "atmosphere_optical_thickness_due_to_pm1_ambient_aerosol" - self.assertEqual(_get_valid_standard_name(name), name) + assert _get_valid_standard_name(name) == name def test_invalid_standard_name(self): name = "not_a_standard_name" - with self.assertRaisesRegex(ValueError, self.emsg.format(name)): + with pytest.raises(ValueError, match=self.emsg.format(name)): _get_valid_standard_name(name) def test_valid_standard_name_valid_modifier(self): name = "air_temperature standard_error" - self.assertEqual(_get_valid_standard_name(name), name) + assert _get_valid_standard_name(name) == name def test_valid_standard_name_valid_modifier_extra_spaces(self): name = "air_temperature standard_error" - self.assertEqual(_get_valid_standard_name(name), name) + assert _get_valid_standard_name(name) == name def test_invalid_standard_name_valid_modifier(self): name = "not_a_standard_name standard_error" - with self.assertRaisesRegex(ValueError, self.emsg.format(name)): + with pytest.raises(ValueError, match=self.emsg.format(name)): _get_valid_standard_name(name) def test_valid_standard_invalid_name_modifier(self): name = "air_temperature extra_names standard_error" - with self.assertRaisesRegex(ValueError, self.emsg.format(name)): + with pytest.raises(ValueError, match=self.emsg.format(name)): _get_valid_standard_name(name) def test_valid_standard_valid_name_modifier_extra_names(self): name = "air_temperature standard_error extra words" - with self.assertRaisesRegex(ValueError, self.emsg.format(name)): + with pytest.raises(ValueError, match=self.emsg.format(name)): _get_valid_standard_name(name) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/resolve/test_Resolve.py b/lib/iris/tests/unit/common/resolve/test_Resolve.py index 0bad967acb..05cc0caba3 100644 --- a/lib/iris/tests/unit/common/resolve/test_Resolve.py +++ b/lib/iris/tests/unit/common/resolve/test_Resolve.py @@ -4,17 +4,13 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.common.resolve.Resolve`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from collections import namedtuple from copy import deepcopy -import unittest.mock as mock -from unittest.mock import Mock, sentinel +from unittest import mock from cf_units import Unit import numpy as np +import pytest from iris.common.lenient import LENIENT from iris.common.metadata import CubeMetadata @@ -32,137 +28,140 @@ from iris.cube import Cube -class Test___init__(tests.IrisTest): - def setUp(self): +class Test___init__: + @pytest.fixture(autouse=True) + def _setup(self, mocker): target = "iris.common.resolve.Resolve.__call__" - self.m_call = mock.MagicMock(return_value=sentinel.return_value) - _ = self.patch(target, new=self.m_call) + self.m_call = mocker.MagicMock(return_value=mocker.sentinel.return_value) + mocker.patch(target, new=self.m_call) def _assert_members_none(self, resolve): - self.assertIsNone(resolve.lhs_cube_resolved) - self.assertIsNone(resolve.rhs_cube_resolved) - self.assertIsNone(resolve.lhs_cube_category) - self.assertIsNone(resolve.rhs_cube_category) - self.assertIsNone(resolve.lhs_cube_category_local) - self.assertIsNone(resolve.rhs_cube_category_local) - self.assertIsNone(resolve.category_common) - self.assertIsNone(resolve.lhs_cube_dim_coverage) - self.assertIsNone(resolve.lhs_cube_aux_coverage) - self.assertIsNone(resolve.rhs_cube_dim_coverage) - self.assertIsNone(resolve.rhs_cube_aux_coverage) - self.assertIsNone(resolve.map_rhs_to_lhs) - self.assertIsNone(resolve.mapping) - self.assertIsNone(resolve.prepared_category) - self.assertIsNone(resolve.prepared_factories) - self.assertIsNone(resolve._broadcast_shape) + assert resolve.lhs_cube_resolved is None + assert resolve.rhs_cube_resolved is None + assert resolve.lhs_cube_category is None + assert resolve.rhs_cube_category is None + assert resolve.lhs_cube_category_local is None + assert resolve.rhs_cube_category_local is None + assert resolve.category_common is None + assert resolve.lhs_cube_dim_coverage is None + assert resolve.lhs_cube_aux_coverage is None + assert resolve.rhs_cube_dim_coverage is None + assert resolve.rhs_cube_aux_coverage is None + assert resolve.map_rhs_to_lhs is None + assert resolve.mapping is None + assert resolve.prepared_category is None + assert resolve.prepared_factories is None + assert resolve._broadcast_shape is None def test_lhs_rhs_default(self): resolve = Resolve() - self.assertIsNone(resolve.lhs_cube) - self.assertIsNone(resolve.rhs_cube) + assert resolve.lhs_cube is None + assert resolve.rhs_cube is None self._assert_members_none(resolve) - self.assertEqual(0, self.m_call.call_count) + assert self.m_call.call_count == 0 - def test_lhs_rhs_provided(self): - m_lhs = sentinel.lhs - m_rhs = sentinel.rhs + def test_lhs_rhs_provided(self, mocker): + m_lhs = mocker.sentinel.lhs + m_rhs = mocker.sentinel.rhs resolve = Resolve(lhs=m_lhs, rhs=m_rhs) # The lhs_cube and rhs_cube are only None due # to __call__ being mocked. See Test___call__ # for appropriate test coverage. - self.assertIsNone(resolve.lhs_cube) - self.assertIsNone(resolve.rhs_cube) + assert resolve.lhs_cube is None + assert resolve.rhs_cube is None self._assert_members_none(resolve) - self.assertEqual(1, self.m_call.call_count) - call_args = mock.call(m_lhs, m_rhs) - self.assertEqual(call_args, self.m_call.call_args) + assert self.m_call.call_count == 1 + call_args = mocker.call(m_lhs, m_rhs) + assert self.m_call.call_args == call_args -class Test___call__(tests.IrisTest): - def setUp(self): - self.m_lhs = mock.MagicMock(spec=Cube) - self.m_rhs = mock.MagicMock(spec=Cube) +class Test___call__: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.m_lhs = mocker.MagicMock(spec=Cube) + self.m_rhs = mocker.MagicMock(spec=Cube) target = "iris.common.resolve.Resolve.{method}" method = target.format(method="_metadata_resolve") - self.m_metadata_resolve = self.patch(method) + self.m_metadata_resolve = mocker.patch(method) method = target.format(method="_metadata_coverage") - self.m_metadata_coverage = self.patch(method) + self.m_metadata_coverage = mocker.patch(method) method = target.format(method="_metadata_mapping") - self.m_metadata_mapping = self.patch(method) + self.m_metadata_mapping = mocker.patch(method) method = target.format(method="_metadata_prepare") - self.m_metadata_prepare = self.patch(method) + self.m_metadata_prepare = mocker.patch(method) def test_lhs_not_cube(self): emsg = "'LHS' argument to be a 'Cube'" - with self.assertRaisesRegex(TypeError, emsg): + with pytest.raises(TypeError, match=emsg): _ = Resolve(rhs=self.m_rhs) def test_rhs_not_cube(self): emsg = "'RHS' argument to be a 'Cube'" - with self.assertRaisesRegex(TypeError, emsg): + with pytest.raises(TypeError, match=emsg): _ = Resolve(lhs=self.m_lhs) def _assert_called_metadata_methods(self): call_args = mock.call() - self.assertEqual(1, self.m_metadata_resolve.call_count) - self.assertEqual(call_args, self.m_metadata_resolve.call_args) - self.assertEqual(1, self.m_metadata_coverage.call_count) - self.assertEqual(call_args, self.m_metadata_coverage.call_args) - self.assertEqual(1, self.m_metadata_mapping.call_count) - self.assertEqual(call_args, self.m_metadata_mapping.call_args) - self.assertEqual(1, self.m_metadata_prepare.call_count) - self.assertEqual(call_args, self.m_metadata_prepare.call_args) + assert self.m_metadata_resolve.call_count == 1 + assert self.m_metadata_resolve.call_args == call_args + assert self.m_metadata_coverage.call_count == 1 + assert self.m_metadata_coverage.call_args == call_args + assert self.m_metadata_mapping.call_count == 1 + assert self.m_metadata_mapping.call_args == call_args + assert self.m_metadata_prepare.call_count == 1 + assert self.m_metadata_prepare.call_args == call_args def test_map_rhs_to_lhs__less_than(self): self.m_lhs.ndim = 2 self.m_rhs.ndim = 1 resolve = Resolve(lhs=self.m_lhs, rhs=self.m_rhs) - self.assertEqual(self.m_lhs, resolve.lhs_cube) - self.assertEqual(self.m_rhs, resolve.rhs_cube) - self.assertTrue(resolve.map_rhs_to_lhs) + assert resolve.lhs_cube == self.m_lhs + assert resolve.rhs_cube == self.m_rhs + assert resolve.map_rhs_to_lhs self._assert_called_metadata_methods() def test_map_rhs_to_lhs__equal(self): self.m_lhs.ndim = 2 self.m_rhs.ndim = 2 resolve = Resolve(lhs=self.m_lhs, rhs=self.m_rhs) - self.assertEqual(self.m_lhs, resolve.lhs_cube) - self.assertEqual(self.m_rhs, resolve.rhs_cube) - self.assertTrue(resolve.map_rhs_to_lhs) + assert resolve.lhs_cube == self.m_lhs + assert resolve.rhs_cube == self.m_rhs + assert resolve.map_rhs_to_lhs self._assert_called_metadata_methods() def test_map_lhs_to_rhs(self): self.m_lhs.ndim = 2 self.m_rhs.ndim = 3 resolve = Resolve(lhs=self.m_lhs, rhs=self.m_rhs) - self.assertEqual(self.m_lhs, resolve.lhs_cube) - self.assertEqual(self.m_rhs, resolve.rhs_cube) - self.assertFalse(resolve.map_rhs_to_lhs) + assert resolve.lhs_cube == self.m_lhs + assert resolve.rhs_cube == self.m_rhs + assert not resolve.map_rhs_to_lhs self._assert_called_metadata_methods() -class Test__categorise_items(tests.IrisTest): - def setUp(self): +class Test__categorise_items: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.coord_dims = {} # configure dim coords - coord = mock.Mock(metadata=sentinel.dim_metadata1) + coord = mocker.Mock(metadata=mocker.sentinel.dim_metadata1) self.dim_coords = [coord] - self.coord_dims[coord] = sentinel.dims1 + self.coord_dims[coord] = mocker.sentinel.dims1 # configure aux and scalar coords self.aux_coords = [] pairs = [ - (sentinel.aux_metadata2, sentinel.dims2), - (sentinel.aux_metadata3, sentinel.dims3), - (sentinel.scalar_metadata4, None), - (sentinel.scalar_metadata5, None), - (sentinel.scalar_metadata6, None), + (mocker.sentinel.aux_metadata2, mocker.sentinel.dims2), + (mocker.sentinel.aux_metadata3, mocker.sentinel.dims3), + (mocker.sentinel.scalar_metadata4, None), + (mocker.sentinel.scalar_metadata5, None), + (mocker.sentinel.scalar_metadata6, None), ] for metadata, dims in pairs: - coord = mock.Mock(metadata=metadata) + coord = mocker.Mock(metadata=metadata) self.aux_coords.append(coord) self.coord_dims[coord] = dims func = lambda coord: self.coord_dims[coord] - self.cube = mock.Mock( + self.cube = mocker.Mock( aux_coords=self.aux_coords, dim_coords=self.dim_coords, coord_dims=func, @@ -170,19 +169,19 @@ def setUp(self): def test(self): result = Resolve._categorise_items(self.cube) - self.assertIsInstance(result, _CategoryItems) - self.assertEqual(1, len(result.items_dim)) + assert isinstance(result, _CategoryItems) + assert len(result.items_dim) == 1 # check dim coords for item in result.items_dim: - self.assertIsInstance(item, _Item) + assert isinstance(item, _Item) (coord,) = self.dim_coords dims = self.coord_dims[coord] expected = [_Item(metadata=coord.metadata, coord=coord, dims=dims)] - self.assertEqual(expected, result.items_dim) + assert result.items_dim == expected # check aux coords - self.assertEqual(2, len(result.items_aux)) + assert len(result.items_aux) == 2 for item in result.items_aux: - self.assertIsInstance(item, _Item) + assert isinstance(item, _Item) expected_aux, expected_scalar = [], [] for coord in self.aux_coords: dims = self.coord_dims[coord] @@ -191,19 +190,20 @@ def test(self): expected_aux.append(item) else: expected_scalar.append(item) - self.assertEqual(expected_aux, result.items_aux) + assert result.items_aux == expected_aux # check scalar coords - self.assertEqual(3, len(result.items_scalar)) + assert len(result.items_scalar) == 3 for item in result.items_scalar: - self.assertIsInstance(item, _Item) - self.assertEqual(expected_scalar, result.items_scalar) + assert isinstance(item, _Item) + assert result.items_scalar == expected_scalar -class Test__metadata_resolve(tests.IrisTest): - def setUp(self): +class Test__metadata_resolve: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.target = "iris.common.resolve.Resolve._categorise_items" - self.m_lhs_cube = sentinel.lhs_cube - self.m_rhs_cube = sentinel.rhs_cube + self.m_lhs_cube = mocker.sentinel.lhs_cube + self.m_rhs_cube = mocker.sentinel.rhs_cube @staticmethod def _create_items(pairs): @@ -219,72 +219,72 @@ def _create_items(pairs): result.append(item) return result - def test_metadata_same(self): + def test_metadata_same(self, mocker): category = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) # configure dim coords - pairs = [(sentinel.dim_metadata1, sentinel.dims1)] + pairs = [(mocker.sentinel.dim_metadata1, mocker.sentinel.dims1)] category.items_dim.extend(self._create_items(pairs)) # configure aux coords pairs = [ - (sentinel.aux_metadata1, sentinel.dims2), - (sentinel.aux_metadata2, sentinel.dims3), + (mocker.sentinel.aux_metadata1, mocker.sentinel.dims2), + (mocker.sentinel.aux_metadata2, mocker.sentinel.dims3), ] category.items_aux.extend(self._create_items(pairs)) # configure scalar coords pairs = [ - (sentinel.scalar_metadata1, None), - (sentinel.scalar_metadata2, None), - (sentinel.scalar_metadata3, None), + (mocker.sentinel.scalar_metadata1, None), + (mocker.sentinel.scalar_metadata2, None), + (mocker.sentinel.scalar_metadata3, None), ] category.items_scalar.extend(self._create_items(pairs)) side_effect = (category, category) - mocker = self.patch(self.target, side_effect=side_effect) + patcher = mocker.patch(self.target, side_effect=side_effect) resolve = Resolve() - self.assertIsNone(resolve.lhs_cube) - self.assertIsNone(resolve.rhs_cube) - self.assertIsNone(resolve.lhs_cube_category) - self.assertIsNone(resolve.rhs_cube_category) - self.assertIsNone(resolve.lhs_cube_category_local) - self.assertIsNone(resolve.rhs_cube_category_local) - self.assertIsNone(resolve.category_common) + assert resolve.lhs_cube is None + assert resolve.rhs_cube is None + assert resolve.lhs_cube_category is None + assert resolve.rhs_cube_category is None + assert resolve.lhs_cube_category_local is None + assert resolve.rhs_cube_category_local is None + assert resolve.category_common is None # require to explicitly configure cubes resolve.lhs_cube = self.m_lhs_cube resolve.rhs_cube = self.m_rhs_cube resolve._metadata_resolve() - self.assertEqual(mocker.call_count, 2) - calls = [mock.call(self.m_lhs_cube), mock.call(self.m_rhs_cube)] - self.assertEqual(calls, mocker.call_args_list) + assert patcher.call_count == 2 + calls = [mocker.call(self.m_lhs_cube), mocker.call(self.m_rhs_cube)] + assert patcher.call_args_list == calls - self.assertEqual(category, resolve.lhs_cube_category) - self.assertEqual(category, resolve.rhs_cube_category) + assert resolve.lhs_cube_category == category + assert resolve.rhs_cube_category == category expected = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) - self.assertEqual(expected, resolve.lhs_cube_category_local) - self.assertEqual(expected, resolve.rhs_cube_category_local) - self.assertEqual(category, resolve.category_common) + assert resolve.lhs_cube_category_local == expected + assert resolve.rhs_cube_category_local == expected + assert resolve.category_common == category - def test_metadata_overlap(self): + def test_metadata_overlap(self, mocker): # configure the lhs cube category category_lhs = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) # configure dim coords pairs = [ - (sentinel.dim_metadata1, sentinel.dims1), - (sentinel.dim_metadata2, sentinel.dims2), + (mocker.sentinel.dim_metadata1, mocker.sentinel.dims1), + (mocker.sentinel.dim_metadata2, mocker.sentinel.dims2), ] category_lhs.items_dim.extend(self._create_items(pairs)) # configure aux coords pairs = [ - (sentinel.aux_metadata1, sentinel.dims3), - (sentinel.aux_metadata2, sentinel.dims4), + (mocker.sentinel.aux_metadata1, mocker.sentinel.dims3), + (mocker.sentinel.aux_metadata2, mocker.sentinel.dims4), ] category_lhs.items_aux.extend(self._create_items(pairs)) # configure scalar coords pairs = [ - (sentinel.scalar_metadata1, None), - (sentinel.scalar_metadata2, None), + (mocker.sentinel.scalar_metadata1, None), + (mocker.sentinel.scalar_metadata2, None), ] category_lhs.items_scalar.extend(self._create_items(pairs)) @@ -292,40 +292,40 @@ def test_metadata_overlap(self): category_rhs = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) # configure dim coords category_rhs.items_dim.append(category_lhs.items_dim[0]) - pairs = [(sentinel.dim_metadata200, sentinel.dims2)] + pairs = [(mocker.sentinel.dim_metadata200, mocker.sentinel.dims2)] category_rhs.items_dim.extend(self._create_items(pairs)) # configure aux coords category_rhs.items_aux.append(category_lhs.items_aux[0]) - pairs = [(sentinel.aux_metadata200, sentinel.dims4)] + pairs = [(mocker.sentinel.aux_metadata200, mocker.sentinel.dims4)] category_rhs.items_aux.extend(self._create_items(pairs)) # configure scalar coords category_rhs.items_scalar.append(category_lhs.items_scalar[0]) - pairs = [(sentinel.scalar_metadata200, None)] + pairs = [(mocker.sentinel.scalar_metadata200, None)] category_rhs.items_scalar.extend(self._create_items(pairs)) side_effect = (category_lhs, category_rhs) - mocker = self.patch(self.target, side_effect=side_effect) + patcher = mocker.patch(self.target, side_effect=side_effect) resolve = Resolve() - self.assertIsNone(resolve.lhs_cube) - self.assertIsNone(resolve.rhs_cube) - self.assertIsNone(resolve.lhs_cube_category) - self.assertIsNone(resolve.rhs_cube_category) - self.assertIsNone(resolve.lhs_cube_category_local) - self.assertIsNone(resolve.rhs_cube_category_local) - self.assertIsNone(resolve.category_common) + assert resolve.lhs_cube is None + assert resolve.rhs_cube is None + assert resolve.lhs_cube_category is None + assert resolve.rhs_cube_category is None + assert resolve.lhs_cube_category_local is None + assert resolve.rhs_cube_category_local is None + assert resolve.category_common is None # require to explicitly configure cubes resolve.lhs_cube = self.m_lhs_cube resolve.rhs_cube = self.m_rhs_cube resolve._metadata_resolve() - self.assertEqual(2, mocker.call_count) - calls = [mock.call(self.m_lhs_cube), mock.call(self.m_rhs_cube)] - self.assertEqual(calls, mocker.call_args_list) + assert patcher.call_count == 2 + calls = [mocker.call(self.m_lhs_cube), mocker.call(self.m_rhs_cube)] + assert patcher.call_args_list == calls - self.assertEqual(category_lhs, resolve.lhs_cube_category) - self.assertEqual(category_rhs, resolve.rhs_cube_category) + assert resolve.lhs_cube_category == category_lhs + assert resolve.rhs_cube_category == category_rhs items_dim = [category_lhs.items_dim[1]] items_aux = [category_lhs.items_aux[1]] @@ -333,7 +333,7 @@ def test_metadata_overlap(self): expected = _CategoryItems( items_dim=items_dim, items_aux=items_aux, items_scalar=items_scalar ) - self.assertEqual(expected, resolve.lhs_cube_category_local) + assert resolve.lhs_cube_category_local == expected items_dim = [category_rhs.items_dim[1]] items_aux = [category_rhs.items_aux[1]] @@ -341,7 +341,7 @@ def test_metadata_overlap(self): expected = _CategoryItems( items_dim=items_dim, items_aux=items_aux, items_scalar=items_scalar ) - self.assertEqual(expected, resolve.rhs_cube_category_local) + assert resolve.rhs_cube_category_local == expected items_dim = [category_lhs.items_dim[0]] items_aux = [category_lhs.items_aux[0]] @@ -349,27 +349,27 @@ def test_metadata_overlap(self): expected = _CategoryItems( items_dim=items_dim, items_aux=items_aux, items_scalar=items_scalar ) - self.assertEqual(expected, resolve.category_common) + assert resolve.category_common == expected - def test_metadata_different(self): + def test_metadata_different(self, mocker): # configure the lhs cube category category_lhs = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) # configure dim coords pairs = [ - (sentinel.dim_metadata1, sentinel.dims1), - (sentinel.dim_metadata2, sentinel.dims2), + (mocker.sentinel.dim_metadata1, mocker.sentinel.dims1), + (mocker.sentinel.dim_metadata2, mocker.sentinel.dims2), ] category_lhs.items_dim.extend(self._create_items(pairs)) # configure aux coords pairs = [ - (sentinel.aux_metadata1, sentinel.dims3), - (sentinel.aux_metadata2, sentinel.dims4), + (mocker.sentinel.aux_metadata1, mocker.sentinel.dims3), + (mocker.sentinel.aux_metadata2, mocker.sentinel.dims4), ] category_lhs.items_aux.extend(self._create_items(pairs)) # configure scalar coords pairs = [ - (sentinel.scalar_metadata1, None), - (sentinel.scalar_metadata2, None), + (mocker.sentinel.scalar_metadata1, None), + (mocker.sentinel.scalar_metadata2, None), ] category_lhs.items_scalar.extend(self._create_items(pairs)) @@ -377,62 +377,63 @@ def test_metadata_different(self): category_rhs = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) # configure dim coords pairs = [ - (sentinel.dim_metadata100, sentinel.dims1), - (sentinel.dim_metadata200, sentinel.dims2), + (mocker.sentinel.dim_metadata100, mocker.sentinel.dims1), + (mocker.sentinel.dim_metadata200, mocker.sentinel.dims2), ] category_rhs.items_dim.extend(self._create_items(pairs)) # configure aux coords pairs = [ - (sentinel.aux_metadata100, sentinel.dims3), - (sentinel.aux_metadata200, sentinel.dims4), + (mocker.sentinel.aux_metadata100, mocker.sentinel.dims3), + (mocker.sentinel.aux_metadata200, mocker.sentinel.dims4), ] category_rhs.items_aux.extend(self._create_items(pairs)) # configure scalar coords pairs = [ - (sentinel.scalar_metadata100, None), - (sentinel.scalar_metadata200, None), + (mocker.sentinel.scalar_metadata100, None), + (mocker.sentinel.scalar_metadata200, None), ] category_rhs.items_scalar.extend(self._create_items(pairs)) side_effect = (category_lhs, category_rhs) - mocker = self.patch(self.target, side_effect=side_effect) + patcher = mocker.patch(self.target, side_effect=side_effect) resolve = Resolve() - self.assertIsNone(resolve.lhs_cube) - self.assertIsNone(resolve.rhs_cube) - self.assertIsNone(resolve.lhs_cube_category) - self.assertIsNone(resolve.rhs_cube_category) - self.assertIsNone(resolve.lhs_cube_category_local) - self.assertIsNone(resolve.rhs_cube_category_local) - self.assertIsNone(resolve.category_common) + assert resolve.lhs_cube is None + assert resolve.rhs_cube is None + assert resolve.lhs_cube_category is None + assert resolve.rhs_cube_category is None + assert resolve.lhs_cube_category_local is None + assert resolve.rhs_cube_category_local is None + assert resolve.category_common is None # first require to explicitly lhs/rhs configure cubes resolve.lhs_cube = self.m_lhs_cube resolve.rhs_cube = self.m_rhs_cube resolve._metadata_resolve() - self.assertEqual(2, mocker.call_count) - calls = [mock.call(self.m_lhs_cube), mock.call(self.m_rhs_cube)] - self.assertEqual(calls, mocker.call_args_list) + assert patcher.call_count == 2 + calls = [mocker.call(self.m_lhs_cube), mocker.call(self.m_rhs_cube)] + assert patcher.call_args_list == calls - self.assertEqual(category_lhs, resolve.lhs_cube_category) - self.assertEqual(category_rhs, resolve.rhs_cube_category) - self.assertEqual(category_lhs, resolve.lhs_cube_category_local) - self.assertEqual(category_rhs, resolve.rhs_cube_category_local) + assert resolve.lhs_cube_category == category_lhs + assert resolve.rhs_cube_category == category_rhs + assert resolve.lhs_cube_category_local == category_lhs + assert resolve.rhs_cube_category_local == category_rhs expected = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) - self.assertEqual(expected, resolve.category_common) + assert resolve.category_common == expected -class Test__dim_coverage(tests.IrisTest): - def setUp(self): +class Test__dim_coverage: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.ndim = 4 - self.cube = mock.Mock(ndim=self.ndim) + self.cube = mocker.Mock(ndim=self.ndim) self.items = [] parts = [ - (sentinel.metadata0, sentinel.coord0, (0,)), - (sentinel.metadata1, sentinel.coord1, (1,)), - (sentinel.metadata2, sentinel.coord2, (2,)), - (sentinel.metadata3, sentinel.coord3, (3,)), + (mocker.sentinel.metadata0, mocker.sentinel.coord0, (0,)), + (mocker.sentinel.metadata1, mocker.sentinel.coord1, (1,)), + (mocker.sentinel.metadata2, mocker.sentinel.coord2, (2,)), + (mocker.sentinel.metadata3, mocker.sentinel.coord3, (3,)), ] column_parts = [x for x in zip(*parts)] self.metadata, self.coords, self.dims = [list(x) for x in column_parts] @@ -445,70 +446,75 @@ def test_coverage_no_local_no_common_all_free(self): items = [] common = [] result = Resolve._dim_coverage(self.cube, items, common) - self.assertIsInstance(result, _DimCoverage) - self.assertEqual(self.cube, result.cube) + assert isinstance(result, _DimCoverage) + assert result.cube == self.cube expected = [None] * self.ndim - self.assertEqual(expected, result.metadata) - self.assertEqual(expected, result.coords) - self.assertEqual([], result.dims_common) - self.assertEqual([], result.dims_local) + assert result.metadata == expected + assert result.coords == expected + assert result.dims_common == [] + assert result.dims_local == [] expected = list(range(self.ndim)) - self.assertEqual(expected, result.dims_free) + assert result.dims_free == expected def test_coverage_all_local_no_common_no_free(self): common = [] result = Resolve._dim_coverage(self.cube, self.items, common) - self.assertIsInstance(result, _DimCoverage) - self.assertEqual(self.cube, result.cube) - self.assertEqual(self.metadata, result.metadata) - self.assertEqual(self.coords, result.coords) - self.assertEqual([], result.dims_common) - self.assertEqual(self.dims, result.dims_local) - self.assertEqual([], result.dims_free) + assert isinstance(result, _DimCoverage) + assert result.cube == self.cube + assert result.metadata == self.metadata + assert result.coords == self.coords + assert result.dims_common == [] + assert result.dims_local == self.dims + assert result.dims_free == [] def test_coverage_no_local_all_common_no_free(self): result = Resolve._dim_coverage(self.cube, self.items, self.metadata) - self.assertIsInstance(result, _DimCoverage) - self.assertEqual(self.cube, result.cube) - self.assertEqual(self.metadata, result.metadata) - self.assertEqual(self.coords, result.coords) - self.assertEqual(self.dims, result.dims_common) - self.assertEqual([], result.dims_local) - self.assertEqual([], result.dims_free) - - def test_coverage_mixed(self): + assert isinstance(result, _DimCoverage) + assert result.cube == self.cube + assert result.metadata == self.metadata + assert result.coords == self.coords + assert result.dims_common == self.dims + assert result.dims_local == [] + assert result.dims_free == [] + + def test_coverage_mixed(self, mocker): common = [self.items[1].metadata, self.items[2].metadata] self.items.pop(0) self.items.pop(-1) - metadata, coord, dims = sentinel.metadata100, sentinel.coord100, (0,) + metadata, coord, dims = ( + mocker.sentinel.metadata100, + mocker.sentinel.coord100, + (0,), + ) self.items.append(_Item(metadata=metadata, coord=coord, dims=dims)) result = Resolve._dim_coverage(self.cube, self.items, common) - self.assertIsInstance(result, _DimCoverage) - self.assertEqual(self.cube, result.cube) + assert isinstance(result, _DimCoverage) + assert result.cube == self.cube expected = [ metadata, self.items[0].metadata, self.items[1].metadata, None, ] - self.assertEqual(expected, result.metadata) + assert result.metadata == expected expected = [coord, self.items[0].coord, self.items[1].coord, None] - self.assertEqual(expected, result.coords) - self.assertEqual([1, 2], result.dims_common) - self.assertEqual([0], result.dims_local) - self.assertEqual([3], result.dims_free) + assert result.coords == expected + assert result.dims_common == [1, 2] + assert result.dims_local == [0] + assert result.dims_free == [3] -class Test__aux_coverage(tests.IrisTest): - def setUp(self): +class Test__aux_coverage: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.ndim = 4 - self.cube = mock.Mock(ndim=self.ndim) + self.cube = mocker.Mock(ndim=self.ndim) # configure aux coords self.items_aux = [] aux_parts = [ - (sentinel.aux_metadata0, sentinel.aux_coord0, (0,)), - (sentinel.aux_metadata1, sentinel.aux_coord1, (1,)), - (sentinel.aux_metadata23, sentinel.aux_coord23, (2, 3)), + (mocker.sentinel.aux_metadata0, mocker.sentinel.aux_coord0, (0,)), + (mocker.sentinel.aux_metadata1, mocker.sentinel.aux_coord1, (1,)), + (mocker.sentinel.aux_metadata23, mocker.sentinel.aux_coord23, (2, 3)), ] column_aux_parts = [x for x in zip(*aux_parts)] self.aux_metadata, self.aux_coords, self.aux_dims = [ @@ -520,9 +526,9 @@ def setUp(self): # configure scalar coords self.items_scalar = [] scalar_parts = [ - (sentinel.scalar_metadata0, sentinel.scalar_coord0, ()), - (sentinel.scalar_metadata1, sentinel.scalar_coord1, ()), - (sentinel.scalar_metadata2, sentinel.scalar_coord2, ()), + (mocker.sentinel.scalar_metadata0, mocker.sentinel.scalar_coord0, ()), + (mocker.sentinel.scalar_metadata1, mocker.sentinel.scalar_coord1, ()), + (mocker.sentinel.scalar_metadata2, mocker.sentinel.scalar_coord2, ()), ] column_scalar_parts = [x for x in zip(*scalar_parts)] self.scalar_metadata, self.scalar_coords, self.scalar_dims = [ @@ -538,16 +544,16 @@ def test_coverage_no_local_no_common_all_free(self): result = Resolve._aux_coverage( self.cube, items_aux, items_scalar, common_aux, common_scalar ) - self.assertIsInstance(result, _AuxCoverage) - self.assertEqual(self.cube, result.cube) - self.assertEqual([], result.common_items_aux) - self.assertEqual([], result.common_items_scalar) - self.assertEqual([], result.local_items_aux) - self.assertEqual([], result.local_items_scalar) - self.assertEqual([], result.dims_common) - self.assertEqual([], result.dims_local) + assert isinstance(result, _AuxCoverage) + assert result.cube == self.cube + assert result.common_items_aux == [] + assert result.common_items_scalar == [] + assert result.local_items_aux == [] + assert result.local_items_scalar == [] + assert result.dims_common == [] + assert result.dims_local == [] expected = list(range(self.ndim)) - self.assertEqual(expected, result.dims_free) + assert result.dims_free == expected def test_coverage_all_local_no_common_no_free(self): common_aux, common_scalar = [], [] @@ -558,17 +564,16 @@ def test_coverage_all_local_no_common_no_free(self): common_aux, common_scalar, ) - self.assertIsInstance(result, _AuxCoverage) - self.assertEqual(self.cube, result.cube) - expected = [] - self.assertEqual(expected, result.common_items_aux) - self.assertEqual(expected, result.common_items_scalar) - self.assertEqual(self.items_aux, result.local_items_aux) - self.assertEqual(self.items_scalar, result.local_items_scalar) - self.assertEqual([], result.dims_common) + assert isinstance(result, _AuxCoverage) + assert result.cube == self.cube + assert result.common_items_aux == [] + assert result.common_items_scalar == [] + assert result.local_items_aux == self.items_aux + assert result.local_items_scalar == self.items_scalar + assert result.dims_common == [] expected = list(range(self.ndim)) - self.assertEqual(expected, result.dims_local) - self.assertEqual([], result.dims_free) + assert result.dims_local == expected + assert result.dims_free == [] def test_coverage_no_local_all_common_no_free(self): result = Resolve._aux_coverage( @@ -578,16 +583,16 @@ def test_coverage_no_local_all_common_no_free(self): self.aux_metadata, self.scalar_metadata, ) - self.assertIsInstance(result, _AuxCoverage) - self.assertEqual(self.cube, result.cube) - self.assertEqual(self.items_aux, result.common_items_aux) - self.assertEqual(self.items_scalar, result.common_items_scalar) - self.assertEqual([], result.local_items_aux) - self.assertEqual([], result.local_items_scalar) + assert isinstance(result, _AuxCoverage) + assert result.cube == self.cube + assert result.common_items_aux == self.items_aux + assert result.common_items_scalar == self.items_scalar + assert result.local_items_aux == [] + assert result.local_items_scalar == [] expected = list(range(self.ndim)) - self.assertEqual(expected, result.dims_common) - self.assertEqual([], result.dims_local) - self.assertEqual([], result.dims_free) + assert result.dims_common == expected + assert result.dims_local == [] + assert result.dims_free == [] def test_coverage_mixed(self): common_aux = [self.items_aux[-1].metadata] @@ -600,41 +605,42 @@ def test_coverage_mixed(self): common_aux, common_scalar, ) - self.assertIsInstance(result, _AuxCoverage) - self.assertEqual(self.cube, result.cube) + assert isinstance(result, _AuxCoverage) + assert result.cube == self.cube expected = [self.items_aux[-1]] - self.assertEqual(expected, result.common_items_aux) + assert result.common_items_aux == expected expected = [self.items_scalar[1]] - self.assertEqual(expected, result.common_items_scalar) + assert result.common_items_scalar == expected expected = [self.items_aux[0]] - self.assertEqual(expected, result.local_items_aux) + assert result.local_items_aux == expected expected = [self.items_scalar[0], self.items_scalar[2]] - self.assertEqual(expected, result.local_items_scalar) - self.assertEqual([2, 3], result.dims_common) - self.assertEqual([0], result.dims_local) - self.assertEqual([1], result.dims_free) + assert result.local_items_scalar == expected + assert result.dims_common == [2, 3] + assert result.dims_local == [0] + assert result.dims_free == [1] -class Test__metadata_coverage(tests.IrisTest): - def setUp(self): +class Test__metadata_coverage: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.resolve = Resolve() - self.m_lhs_cube = sentinel.lhs_cube + self.m_lhs_cube = mocker.sentinel.lhs_cube self.resolve.lhs_cube = self.m_lhs_cube - self.m_rhs_cube = sentinel.rhs_cube + self.m_rhs_cube = mocker.sentinel.rhs_cube self.resolve.rhs_cube = self.m_rhs_cube - self.m_items_dim_metadata = sentinel.items_dim_metadata - self.m_items_aux_metadata = sentinel.items_aux_metadata - self.m_items_scalar_metadata = sentinel.items_scalar_metadata - items_dim = [mock.Mock(metadata=self.m_items_dim_metadata)] - items_aux = [mock.Mock(metadata=self.m_items_aux_metadata)] - items_scalar = [mock.Mock(metadata=self.m_items_scalar_metadata)] + self.m_items_dim_metadata = mocker.sentinel.items_dim_metadata + self.m_items_aux_metadata = mocker.sentinel.items_aux_metadata + self.m_items_scalar_metadata = mocker.sentinel.items_scalar_metadata + items_dim = [mocker.Mock(metadata=self.m_items_dim_metadata)] + items_aux = [mocker.Mock(metadata=self.m_items_aux_metadata)] + items_scalar = [mocker.Mock(metadata=self.m_items_scalar_metadata)] category = _CategoryItems( items_dim=items_dim, items_aux=items_aux, items_scalar=items_scalar ) self.resolve.category_common = category - self.m_items_dim = sentinel.items_dim - self.m_items_aux = sentinel.items_aux - self.m_items_scalar = sentinel.items_scalar + self.m_items_dim = mocker.sentinel.items_dim + self.m_items_aux = mocker.sentinel.items_aux + self.m_items_scalar = mocker.sentinel.items_scalar category = _CategoryItems( items_dim=self.m_items_dim, items_aux=self.m_items_aux, @@ -643,40 +649,40 @@ def setUp(self): self.resolve.lhs_cube_category = category self.resolve.rhs_cube_category = category target = "iris.common.resolve.Resolve._dim_coverage" - self.m_lhs_cube_dim_coverage = sentinel.lhs_cube_dim_coverage - self.m_rhs_cube_dim_coverage = sentinel.rhs_cube_dim_coverage + self.m_lhs_cube_dim_coverage = mocker.sentinel.lhs_cube_dim_coverage + self.m_rhs_cube_dim_coverage = mocker.sentinel.rhs_cube_dim_coverage side_effect = ( self.m_lhs_cube_dim_coverage, self.m_rhs_cube_dim_coverage, ) - self.mocker_dim_coverage = self.patch(target, side_effect=side_effect) + self.mocker_dim_coverage = mocker.patch(target, side_effect=side_effect) target = "iris.common.resolve.Resolve._aux_coverage" - self.m_lhs_cube_aux_coverage = sentinel.lhs_cube_aux_coverage - self.m_rhs_cube_aux_coverage = sentinel.rhs_cube_aux_coverage + self.m_lhs_cube_aux_coverage = mocker.sentinel.lhs_cube_aux_coverage + self.m_rhs_cube_aux_coverage = mocker.sentinel.rhs_cube_aux_coverage side_effect = ( self.m_lhs_cube_aux_coverage, self.m_rhs_cube_aux_coverage, ) - self.mocker_aux_coverage = self.patch(target, side_effect=side_effect) + self.mocker_aux_coverage = mocker.patch(target, side_effect=side_effect) - def test(self): + def test(self, mocker): self.resolve._metadata_coverage() - self.assertEqual(2, self.mocker_dim_coverage.call_count) + assert self.mocker_dim_coverage.call_count == 2 calls = [ - mock.call(self.m_lhs_cube, self.m_items_dim, [self.m_items_dim_metadata]), - mock.call(self.m_rhs_cube, self.m_items_dim, [self.m_items_dim_metadata]), + mocker.call(self.m_lhs_cube, self.m_items_dim, [self.m_items_dim_metadata]), + mocker.call(self.m_rhs_cube, self.m_items_dim, [self.m_items_dim_metadata]), ] - self.assertEqual(calls, self.mocker_dim_coverage.call_args_list) - self.assertEqual(2, self.mocker_aux_coverage.call_count) + assert self.mocker_dim_coverage.call_args_list == calls + assert self.mocker_aux_coverage.call_count == 2 calls = [ - mock.call( + mocker.call( self.m_lhs_cube, self.m_items_aux, self.m_items_scalar, [self.m_items_aux_metadata], [self.m_items_scalar_metadata], ), - mock.call( + mocker.call( self.m_rhs_cube, self.m_items_aux, self.m_items_scalar, @@ -684,26 +690,19 @@ def test(self): [self.m_items_scalar_metadata], ), ] - self.assertEqual(calls, self.mocker_aux_coverage.call_args_list) - self.assertEqual( - self.m_lhs_cube_dim_coverage, self.resolve.lhs_cube_dim_coverage - ) - self.assertEqual( - self.m_rhs_cube_dim_coverage, self.resolve.rhs_cube_dim_coverage - ) - self.assertEqual( - self.m_lhs_cube_aux_coverage, self.resolve.lhs_cube_aux_coverage - ) - self.assertEqual( - self.m_rhs_cube_aux_coverage, self.resolve.rhs_cube_aux_coverage - ) + assert self.mocker_aux_coverage.call_args_list == calls + assert self.resolve.lhs_cube_dim_coverage == self.m_lhs_cube_dim_coverage + assert self.resolve.rhs_cube_dim_coverage == self.m_rhs_cube_dim_coverage + assert self.resolve.lhs_cube_aux_coverage == self.m_lhs_cube_aux_coverage + assert self.resolve.rhs_cube_aux_coverage == self.m_rhs_cube_aux_coverage -class Test__dim_mapping(tests.IrisTest): - def setUp(self): +class Test__dim_mapping: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.ndim = 3 Wrapper = namedtuple("Wrapper", ("name",)) - cube = Wrapper(name=lambda: sentinel.name) + cube = Wrapper(name=lambda: mocker.sentinel.name) self.src_coverage = _DimCoverage( cube=cube, metadata=[], @@ -721,17 +720,21 @@ def setUp(self): dims_free=None, ) self.metadata = [ - sentinel.metadata_0, - sentinel.metadata_1, - sentinel.metadata_2, + mocker.sentinel.metadata_0, + mocker.sentinel.metadata_1, + mocker.sentinel.metadata_2, + ] + self.dummy = [ + mocker.sentinel.dummy_0, + mocker.sentinel.dummy_1, + mocker.sentinel.dummy_2, ] - self.dummy = [sentinel.dummy_0, sentinel.dummy_1, sentinel.dummy_2] def test_no_mapping(self): self.src_coverage.metadata.extend(self.metadata) self.tgt_coverage.metadata.extend(self.dummy) result = Resolve._dim_mapping(self.src_coverage, self.tgt_coverage) - self.assertEqual(dict(), result) + assert result == {} def test_full_mapping(self): self.src_coverage.metadata.extend(self.metadata) @@ -740,7 +743,7 @@ def test_full_mapping(self): self.tgt_coverage.dims_common.extend(dims_common) result = Resolve._dim_mapping(self.src_coverage, self.tgt_coverage) expected = {0: 0, 1: 1, 2: 2} - self.assertEqual(expected, result) + assert result == expected def test_transpose_mapping(self): self.src_coverage.metadata.extend(self.metadata[::-1]) @@ -749,34 +752,35 @@ def test_transpose_mapping(self): self.tgt_coverage.dims_common.extend(dims_common) result = Resolve._dim_mapping(self.src_coverage, self.tgt_coverage) expected = {0: 2, 1: 1, 2: 0} - self.assertEqual(expected, result) + assert result == expected - def test_partial_mapping__transposed(self): + def test_partial_mapping__transposed(self, mocker): self.src_coverage.metadata.extend(self.metadata) - self.metadata[1] = sentinel.nope + self.metadata[1] = mocker.sentinel.nope self.tgt_coverage.metadata.extend(self.metadata[::-1]) dims_common = [0, 2] self.tgt_coverage.dims_common.extend(dims_common) result = Resolve._dim_mapping(self.src_coverage, self.tgt_coverage) expected = {0: 2, 2: 0} - self.assertEqual(expected, result) + assert result == expected - def test_bad_metadata_mapping(self): + def test_bad_metadata_mapping(self, mocker): self.src_coverage.metadata.extend(self.metadata) - self.metadata[0] = sentinel.bad + self.metadata[0] = mocker.sentinel.bad self.tgt_coverage.metadata.extend(self.metadata) dims_common = [0] self.tgt_coverage.dims_common.extend(dims_common) emsg = "Failed to map common dim coordinate metadata" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = Resolve._dim_mapping(self.src_coverage, self.tgt_coverage) -class Test__aux_mapping(tests.IrisTest): - def setUp(self): +class Test__aux_mapping: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.ndim = 3 Wrapper = namedtuple("Wrapper", ("name",)) - cube = Wrapper(name=lambda: sentinel.name) + cube = Wrapper(name=lambda: mocker.sentinel.name) self.src_coverage = _AuxCoverage( cube=cube, common_items_aux=[], @@ -798,21 +802,33 @@ def setUp(self): dims_free=None, ) self.items = [ - _Item(metadata=sentinel.metadata0, coord=sentinel.coord0, dims=[0]), - _Item(metadata=sentinel.metadata1, coord=sentinel.coord1, dims=[1]), - _Item(metadata=sentinel.metadata2, coord=sentinel.coord2, dims=[2]), + _Item( + metadata=mocker.sentinel.metadata0, + coord=mocker.sentinel.coord0, + dims=[0], + ), + _Item( + metadata=mocker.sentinel.metadata1, + coord=mocker.sentinel.coord1, + dims=[1], + ), + _Item( + metadata=mocker.sentinel.metadata2, + coord=mocker.sentinel.coord2, + dims=[2], + ), ] def test_no_mapping(self): result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) - self.assertEqual(dict(), result) + assert result == {} def test_full_mapping(self): self.src_coverage.common_items_aux.extend(self.items) self.tgt_coverage.common_items_aux.extend(self.items) result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) expected = {0: 0, 1: 1, 2: 2} - self.assertEqual(expected, result) + assert result == expected def test_transpose_mapping(self): self.src_coverage.common_items_aux.extend(self.items) @@ -822,7 +838,7 @@ def test_transpose_mapping(self): self.tgt_coverage.common_items_aux.extend(items) result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) expected = {0: 2, 1: 1, 2: 0} - self.assertEqual(expected, result) + assert result == expected def test_partial_mapping__transposed(self): _ = self.items.pop(1) @@ -833,7 +849,7 @@ def test_partial_mapping__transposed(self): self.tgt_coverage.common_items_aux.extend(items) result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) expected = {0: 2, 2: 0} - self.assertEqual(expected, result) + assert result == expected def test_mapping__match_multiple_src_metadata(self): items = deepcopy(self.items) @@ -843,7 +859,7 @@ def test_mapping__match_multiple_src_metadata(self): self.tgt_coverage.common_items_aux.extend(items) result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) expected = {0: 0, 2: 2} - self.assertEqual(expected, result) + assert result == expected def test_mapping__skip_match_multiple_src_metadata(self): items = deepcopy(self.items) @@ -853,7 +869,7 @@ def test_mapping__skip_match_multiple_src_metadata(self): self.src_coverage.common_items_aux.extend(items) result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) expected = {2: 2} - self.assertEqual(expected, result) + assert result == expected def test_mapping__skip_different_rank(self): items = deepcopy(self.items) @@ -862,57 +878,58 @@ def test_mapping__skip_different_rank(self): self.tgt_coverage.common_items_aux.extend(items) result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) expected = {0: 0, 1: 1} - self.assertEqual(expected, result) + assert result == expected - def test_bad_metadata_mapping(self): + def test_bad_metadata_mapping(self, mocker): self.src_coverage.common_items_aux.extend(self.items) items = deepcopy(self.items) - items[0] = items[0]._replace(metadata=sentinel.bad) + items[0] = items[0]._replace(metadata=mocker.sentinel.bad) self.tgt_coverage.common_items_aux.extend(items) emsg = "Failed to map common aux coordinate metadata" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) -class Test_mapped(tests.IrisTest): +class Test_mapped: def test_mapping_none(self): resolve = Resolve() - self.assertIsNone(resolve.mapping) - self.assertIsNone(resolve.mapped) + assert resolve.mapping is None + assert resolve.mapped is None - def test_mapped__src_cube_lhs(self): + def test_mapped__src_cube_lhs(self, mocker): resolve = Resolve() - lhs = mock.Mock(ndim=2) - rhs = mock.Mock(ndim=3) + lhs = mocker.Mock(ndim=2) + rhs = mocker.Mock(ndim=3) resolve.lhs_cube = lhs resolve.rhs_cube = rhs resolve.map_rhs_to_lhs = False resolve.mapping = {0: 0, 1: 1} - self.assertTrue(resolve.mapped) + assert resolve.mapped - def test_mapped__src_cube_rhs(self): + def test_mapped__src_cube_rhs(self, mocker): resolve = Resolve() - lhs = mock.Mock(ndim=3) - rhs = mock.Mock(ndim=2) + lhs = mocker.Mock(ndim=3) + rhs = mocker.Mock(ndim=2) resolve.lhs_cube = lhs resolve.rhs_cube = rhs resolve.map_rhs_to_lhs = True resolve.mapping = {0: 0, 1: 1} - self.assertTrue(resolve.mapped) + assert resolve.mapped - def test_partial_mapping(self): + def test_partial_mapping(self, mocker): resolve = Resolve() - lhs = mock.Mock(ndim=3) - rhs = mock.Mock(ndim=2) + lhs = mocker.Mock(ndim=3) + rhs = mocker.Mock(ndim=2) resolve.lhs_cube = lhs resolve.rhs_cube = rhs resolve.map_rhs_to_lhs = True resolve.mapping = {0: 0} - self.assertFalse(resolve.mapped) + assert not resolve.mapped -class Test__free_mapping(tests.IrisTest): - def setUp(self): +class Test__free_mapping: + @pytest.fixture(autouse=True) + def _setup(self): self.Cube = namedtuple("Wrapper", ("name", "ndim", "shape")) self.src_dim_coverage = dict( cube=None, @@ -955,7 +972,7 @@ def test_mapping_no_dims_free(self): self.tgt_dim_coverage["cube"] = cube args = self._make_args() emsg = "Insufficient matching coordinate metadata" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): self.resolve._free_mapping(**args) def _make_coverage(self, name, shape, dims_free): @@ -995,7 +1012,7 @@ def test_mapping_src_free_to_tgt_local(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_src_free_to_tgt_local__broadcast_src_first(self): # key: (state) c=common, f=free, l=local @@ -1021,7 +1038,7 @@ def test_mapping_src_free_to_tgt_local__broadcast_src_first(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_src_free_to_tgt_local__broadcast_src_last(self): # key: (state) c=common, f=free, l=local @@ -1047,7 +1064,7 @@ def test_mapping_src_free_to_tgt_local__broadcast_src_last(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_src_free_to_tgt_local__broadcast_src_both(self): # key: (state) c=common, f=free, l=local @@ -1073,7 +1090,7 @@ def test_mapping_src_free_to_tgt_local__broadcast_src_both(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 1, 1: 2, 2: 3} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_src_free_to_tgt_free(self): # key: (state) c=common, f=free, l=local @@ -1098,7 +1115,7 @@ def test_mapping_src_free_to_tgt_free(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 0, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_src_free_to_tgt_free__broadcast_src_first(self): # key: (state) c=common, f=free, l=local @@ -1124,7 +1141,7 @@ def test_mapping_src_free_to_tgt_free__broadcast_src_first(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 0, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_src_free_to_tgt_free__broadcast_src_last(self): # key: (state) c=common, f=free, l=local @@ -1150,7 +1167,7 @@ def test_mapping_src_free_to_tgt_free__broadcast_src_last(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 0, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_src_free_to_tgt_free__broadcast_src_both(self): # key: (state) c=common, f=free, l=local @@ -1176,7 +1193,7 @@ def test_mapping_src_free_to_tgt_free__broadcast_src_both(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 0, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_src_free_to_tgt__fail(self): # key: (state) c=common, f=free, l=local @@ -1201,7 +1218,7 @@ def test_mapping_src_free_to_tgt__fail(self): self.resolve.mapping = {1: 2} args = self._make_args() emsg = "Insufficient matching coordinate metadata to resolve cubes" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): self.resolve._free_mapping(**args) def test_mapping_tgt_free_to_src_local(self): @@ -1227,7 +1244,7 @@ def test_mapping_tgt_free_to_src_local(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_tgt_free_to_src_local__broadcast_tgt_first(self): # key: (state) c=common, f=free, l=local @@ -1253,7 +1270,7 @@ def test_mapping_tgt_free_to_src_local__broadcast_tgt_first(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_tgt_free_to_src_local__broadcast_tgt_last(self): # key: (state) c=common, f=free, l=local @@ -1279,7 +1296,7 @@ def test_mapping_tgt_free_to_src_local__broadcast_tgt_last(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_tgt_free_to_src_local__broadcast_tgt_both(self): # key: (state) c=common, f=free, l=local @@ -1305,7 +1322,7 @@ def test_mapping_tgt_free_to_src_local__broadcast_tgt_both(self): args = self._make_args() self.resolve._free_mapping(**args) expected = {0: 1, 1: 2, 2: 3} - self.assertEqual(expected, self.resolve.mapping) + assert self.resolve.mapping == expected def test_mapping_tgt_free_to_src_no_free__fail(self): # key: (state) c=common, f=free, l=local @@ -1330,179 +1347,189 @@ def test_mapping_tgt_free_to_src_no_free__fail(self): self.resolve.mapping = {1: 2} args = self._make_args() emsg = "Insufficient matching coordinate metadata to resolve cubes" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): self.resolve._free_mapping(**args) -class Test__src_cube(tests.IrisTest): - def setUp(self): +class Test__src_cube: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.resolve = Resolve() - self.expected = sentinel.cube + self.expected = mocker.sentinel.cube def test_rhs_cube(self): self.resolve.map_rhs_to_lhs = True self.resolve.rhs_cube = self.expected - self.assertEqual(self.expected, self.resolve._src_cube) + assert self.resolve._src_cube == self.expected def test_lhs_cube(self): self.resolve.map_rhs_to_lhs = False self.resolve.lhs_cube = self.expected - self.assertEqual(self.expected, self.resolve._src_cube) + assert self.resolve._src_cube == self.expected def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.resolve._src_cube -class Test__src_cube_position(tests.IrisTest): - def setUp(self): +class Test__src_cube_position: + @pytest.fixture(autouse=True) + def _setup(self): self.resolve = Resolve() def test_rhs_cube(self): self.resolve.map_rhs_to_lhs = True - self.assertEqual("RHS", self.resolve._src_cube_position) + assert self.resolve._src_cube_position == "RHS" def test_lhs_cube(self): self.resolve.map_rhs_to_lhs = False - self.assertEqual("LHS", self.resolve._src_cube_position) + assert self.resolve._src_cube_position == "LHS" def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.resolve._src_cube_position -class Test__src_cube_resolved__getter(tests.IrisTest): - def setUp(self): +class Test__src_cube_resolved__getter: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.resolve = Resolve() - self.expected = sentinel.cube + self.expected = mocker.sentinel.cube def test_rhs_cube(self): self.resolve.map_rhs_to_lhs = True self.resolve.rhs_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve._src_cube_resolved) + assert self.resolve._src_cube_resolved == self.expected def test_lhs_cube(self): self.resolve.map_rhs_to_lhs = False self.resolve.lhs_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve._src_cube_resolved) + assert self.resolve._src_cube_resolved == self.expected def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.resolve._src_cube_resolved -class Test__src_cube_resolved__setter(tests.IrisTest): - def setUp(self): +class Test__src_cube_resolved__setter: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.resolve = Resolve() - self.expected = sentinel.cube + self.expected = mocker.sentinel.cube def test_rhs_cube(self): self.resolve.map_rhs_to_lhs = True self.resolve._src_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve.rhs_cube_resolved) + assert self.resolve.rhs_cube_resolved == self.expected def test_lhs_cube(self): self.resolve.map_rhs_to_lhs = False self.resolve._src_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve.lhs_cube_resolved) + assert self.resolve.lhs_cube_resolved == self.expected def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.resolve._src_cube_resolved = self.expected -class Test__tgt_cube(tests.IrisTest): - def setUp(self): +class Test__tgt_cube: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.resolve = Resolve() - self.expected = sentinel.cube + self.expected = mocker.sentinel.cube def test_rhs_cube(self): self.resolve.map_rhs_to_lhs = False self.resolve.rhs_cube = self.expected - self.assertEqual(self.expected, self.resolve._tgt_cube) + assert self.resolve._tgt_cube == self.expected def test_lhs_cube(self): self.resolve.map_rhs_to_lhs = True self.resolve.lhs_cube = self.expected - self.assertEqual(self.expected, self.resolve._tgt_cube) + assert self.resolve._tgt_cube == self.expected def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.resolve._tgt_cube -class Test__tgt_cube_position(tests.IrisTest): - def setUp(self): +class Test__tgt_cube_position: + @pytest.fixture(autouse=True) + def _setup(self): self.resolve = Resolve() def test_rhs_cube(self): self.resolve.map_rhs_to_lhs = False - self.assertEqual("RHS", self.resolve._tgt_cube_position) + assert self.resolve._tgt_cube_position == "RHS" def test_lhs_cube(self): self.resolve.map_rhs_to_lhs = True - self.assertEqual("LHS", self.resolve._tgt_cube_position) + assert self.resolve._tgt_cube_position == "LHS" def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.resolve._tgt_cube_position -class Test__tgt_cube_resolved__getter(tests.IrisTest): - def setUp(self): +class Test__tgt_cube_resolved__getter: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.resolve = Resolve() - self.expected = sentinel.cube + self.expected = mocker.sentinel.cube def test_rhs_cube(self): self.resolve.map_rhs_to_lhs = False self.resolve.rhs_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve._tgt_cube_resolved) + assert self.resolve._tgt_cube_resolved == self.expected def test_lhs_cube(self): self.resolve.map_rhs_to_lhs = True self.resolve.lhs_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve._tgt_cube_resolved) + assert self.resolve._tgt_cube_resolved == self.expected def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.resolve._tgt_cube_resolved -class Test__tgt_cube_resolved__setter(tests.IrisTest): - def setUp(self): +class Test__tgt_cube_resolved__setter: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.resolve = Resolve() - self.expected = sentinel.cube + self.expected = mocker.sentinel.cube def test_rhs_cube(self): self.resolve.map_rhs_to_lhs = False self.resolve._tgt_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve.rhs_cube_resolved) + assert self.resolve.rhs_cube_resolved == self.expected def test_lhs_cube(self): self.resolve.map_rhs_to_lhs = True self.resolve._tgt_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve.lhs_cube_resolved) + assert self.resolve.lhs_cube_resolved == self.expected def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.resolve._tgt_cube_resolved = self.expected -class Test_shape(tests.IrisTest): - def setUp(self): +class Test_shape: + @pytest.fixture(autouse=True) + def _setup(self): self.resolve = Resolve() def test_no_shape(self): - self.assertIsNone(self.resolve.shape) + assert self.resolve.shape is None - def test_shape(self): - expected = sentinel.shape + def test_shape(self, mocker): + expected = mocker.sentinel.shape self.resolve._broadcast_shape = expected - self.assertEqual(expected, self.resolve.shape) + assert self.resolve.shape == expected -class Test__as_compatible_cubes(tests.IrisTest): - def setUp(self): +class Test__as_compatible_cubes: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.Cube = namedtuple( "Wrapper", ( @@ -1520,7 +1547,7 @@ def setUp(self): self.resolve = Resolve() self.resolve.map_rhs_to_lhs = True self.resolve.mapping = {} - self.mocker = self.patch("iris.cube.Cube") + self.mocker = mocker.patch("iris.cube.Cube") self.args = dict( name=None, ndim=None, @@ -1539,8 +1566,8 @@ def _make_cube(self, name, shape, transpose_shape=None): self.args["ndim"] = ndim self.args["shape"] = shape if name == "src": - self.args["metadata"] = sentinel.metadata - self.reshape = sentinel.reshape + self.args["metadata"] = mock.sentinel.metadata + self.reshape = mock.sentinel.reshape m_reshape = mock.Mock(return_value=self.reshape) self.transpose = mock.Mock(shape=transpose_shape, reshape=m_reshape) m_transpose = mock.Mock(return_value=self.transpose) @@ -1549,9 +1576,9 @@ def _make_cube(self, name, shape, transpose_shape=None): m_core_data = mock.Mock(copy=m_copy) self.args["core_data"] = mock.Mock(return_value=m_core_data) self.args["coord_dims"] = mock.Mock(side_effect=([0], [ndim - 1])) - self.dim_coord = sentinel.dim_coord - self.aux_coord = sentinel.aux_coord - self.aux_factory = sentinel.aux_factory + self.dim_coord = mock.sentinel.dim_coord + self.aux_coord = mock.sentinel.aux_coord + self.aux_factory = mock.sentinel.aux_factory self.args["dim_coords"] = [self.dim_coord] self.args["aux_coords"] = [self.aux_coord] self.args["aux_factories"] = [self.aux_factory] @@ -1568,7 +1595,7 @@ def test_incomplete_src_to_tgt_mapping__fail(self): self._make_cube("src", src_shape) tgt_shape = (3, 4) self._make_cube("tgt", tgt_shape) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.resolve._as_compatible_cubes() def test_incompatible_shapes__fail(self): @@ -1588,7 +1615,7 @@ def test_incompatible_shapes__fail(self): self._make_cube("tgt", tgt_shape) self.resolve.mapping = {0: 1, 1: 2, 2: 3} emsg = "Cannot resolve cubes" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): self.resolve._as_compatible_cubes() def test_incompatible_shapes__fail_broadcast(self): @@ -1608,37 +1635,32 @@ def test_incompatible_shapes__fail_broadcast(self): self._make_cube("tgt", tgt_shape) self.resolve.mapping = {0: 3, 1: 2, 2: 1} emsg = "Cannot resolve cubes" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): self.resolve._as_compatible_cubes() def _check_compatible(self, broadcast_shape): - self.assertEqual(self.resolve.lhs_cube, self.resolve._tgt_cube_resolved) - self.assertEqual(self.cube, self.resolve._src_cube_resolved) - self.assertEqual(broadcast_shape, self.resolve._broadcast_shape) - self.assertEqual(1, self.mocker.call_count) - self.assertEqual(self.args["metadata"], self.cube.metadata) - self.assertEqual(2, self.resolve.rhs_cube.coord_dims.call_count) - self.assertEqual( - [mock.call(self.dim_coord), mock.call(self.aux_coord)], - self.resolve.rhs_cube.coord_dims.call_args_list, - ) - self.assertEqual(1, self.cube.add_dim_coord.call_count) - self.assertEqual( - [mock.call(self.dim_coord, [self.resolve.mapping[0]])], - self.cube.add_dim_coord.call_args_list, - ) - self.assertEqual(1, self.cube.add_aux_coord.call_count) - self.assertEqual( - [mock.call(self.aux_coord, [self.resolve.mapping[2]])], - self.cube.add_aux_coord.call_args_list, - ) - self.assertEqual(1, self.cube.add_aux_factory.call_count) - self.assertEqual( - [mock.call(self.aux_factory)], - self.cube.add_aux_factory.call_args_list, - ) - - def test_compatible(self): + assert self.resolve.lhs_cube == self.resolve._tgt_cube_resolved + assert self.resolve._src_cube_resolved == self.cube + assert self.resolve._broadcast_shape == broadcast_shape + assert self.mocker.call_count == 1 + assert self.args["metadata"] == self.cube.metadata + assert self.resolve.rhs_cube.coord_dims.call_count == 2 + assert self.resolve.rhs_cube.coord_dims.call_args_list == [ + mock.call(self.dim_coord), + mock.call(self.aux_coord), + ] + assert self.cube.add_dim_coord.call_count == 1 + assert self.cube.add_dim_coord.call_args_list == [ + mock.call(self.dim_coord, [self.resolve.mapping[0]]) + ] + assert self.cube.add_aux_coord.call_count == 1 + assert self.cube.add_aux_coord.call_args_list == [ + mock.call(self.aux_coord, [self.resolve.mapping[2]]) + ] + assert self.cube.add_aux_factory.call_count == 1 + assert self.cube.add_aux_factory.call_args_list == [mock.call(self.aux_factory)] + + def test_compatible(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -1658,9 +1680,9 @@ def test_compatible(self): self.resolve.mapping = mapping self.resolve._as_compatible_cubes() self._check_compatible(broadcast_shape=tgt_shape) - self.assertEqual([mock.call(self.data)], self.mocker.call_args_list) + assert self.mocker.call_args_list == [mocker.call(self.data)] - def test_compatible__transpose(self): + def test_compatible__transpose(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -1680,11 +1702,11 @@ def test_compatible__transpose(self): self.resolve.mapping = mapping self.resolve._as_compatible_cubes() self._check_compatible(broadcast_shape=tgt_shape) - self.assertEqual(1, self.data.transpose.call_count) - self.assertEqual([mock.call([2, 1, 0])], self.data.transpose.call_args_list) - self.assertEqual([mock.call(self.transpose)], self.mocker.call_args_list) + assert self.data.transpose.call_count == 1 + assert self.data.transpose.call_args_list == [mocker.call([2, 1, 0])] + assert self.mocker.call_args_list == [mocker.call(self.transpose)] - def test_compatible__reshape(self): + def test_compatible__reshape(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -1704,13 +1726,11 @@ def test_compatible__reshape(self): self.resolve.mapping = mapping self.resolve._as_compatible_cubes() self._check_compatible(broadcast_shape=tgt_shape) - self.assertEqual(1, self.data.reshape.call_count) - self.assertEqual( - [mock.call((1,) + src_shape)], self.data.reshape.call_args_list - ) - self.assertEqual([mock.call(self.reshape)], self.mocker.call_args_list) + assert self.data.reshape.call_count == 1 + assert self.data.reshape.call_args_list == [mocker.call((1,) + src_shape)] + assert self.mocker.call_args_list == [mocker.call(self.reshape)] - def test_compatible__transpose_reshape(self): + def test_compatible__transpose_reshape(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -1731,16 +1751,13 @@ def test_compatible__transpose_reshape(self): self.resolve.mapping = mapping self.resolve._as_compatible_cubes() self._check_compatible(broadcast_shape=tgt_shape) - self.assertEqual(1, self.data.transpose.call_count) - self.assertEqual([mock.call([2, 1, 0])], self.data.transpose.call_args_list) - self.assertEqual(1, self.data.reshape.call_count) - self.assertEqual( - [mock.call((1,) + transpose_shape)], - self.data.reshape.call_args_list, - ) - self.assertEqual([mock.call(self.reshape)], self.mocker.call_args_list) + assert self.data.transpose.call_count == 1 + assert self.data.transpose.call_args_list == [mocker.call([2, 1, 0])] + assert self.data.reshape.call_count == 1 + assert self.data.reshape.call_args_list == [mocker.call((1,) + transpose_shape)] + assert self.mocker.call_args_list == [mocker.call(self.reshape)] - def test_compatible__broadcast(self): + def test_compatible__broadcast(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -1761,9 +1778,9 @@ def test_compatible__broadcast(self): self.resolve.mapping = mapping self.resolve._as_compatible_cubes() self._check_compatible(broadcast_shape=(4, 3, 2)) - self.assertEqual([mock.call(self.data)], self.mocker.call_args_list) + assert self.mocker.call_args_list == [mocker.call(self.data)] - def test_compatible__broadcast_transpose_reshape(self): + def test_compatible__broadcast_transpose_reshape(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -1785,25 +1802,23 @@ def test_compatible__broadcast_transpose_reshape(self): self.resolve.mapping = mapping self.resolve._as_compatible_cubes() self._check_compatible(broadcast_shape=(5, 4, 3, 2)) - self.assertEqual(1, self.data.transpose.call_count) - self.assertEqual([mock.call([2, 1, 0])], self.data.transpose.call_args_list) - self.assertEqual(1, self.data.reshape.call_count) - self.assertEqual( - [mock.call((1,) + transpose_shape)], - self.data.reshape.call_args_list, - ) - self.assertEqual([mock.call(self.reshape)], self.mocker.call_args_list) - - -class Test__metadata_mapping(tests.IrisTest): - def setUp(self): - self.ndim = sentinel.ndim - self.src_cube = mock.Mock(ndim=self.ndim) - self.src_dim_coverage = mock.Mock(dims_free=[]) - self.src_aux_coverage = mock.Mock(dims_free=[]) - self.tgt_cube = mock.Mock(ndim=self.ndim) - self.tgt_dim_coverage = mock.Mock(dims_free=[]) - self.tgt_aux_coverage = mock.Mock(dims_free=[]) + assert self.data.transpose.call_count == 1 + assert self.data.transpose.call_args_list == [mocker.call([2, 1, 0])] + assert self.data.reshape.call_count == 1 + assert self.data.reshape.call_args_list == [mocker.call((1,) + transpose_shape)] + assert self.mocker.call_args_list == [mocker.call(self.reshape)] + + +class Test__metadata_mapping: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.ndim = mocker.sentinel.ndim + self.src_cube = mocker.Mock(ndim=self.ndim) + self.src_dim_coverage = mocker.Mock(dims_free=[]) + self.src_aux_coverage = mocker.Mock(dims_free=[]) + self.tgt_cube = mocker.Mock(ndim=self.ndim) + self.tgt_dim_coverage = mocker.Mock(dims_free=[]) + self.tgt_aux_coverage = mocker.Mock(dims_free=[]) self.resolve = Resolve() self.map_rhs_to_lhs = True self.resolve.map_rhs_to_lhs = self.map_rhs_to_lhs @@ -1814,23 +1829,23 @@ def setUp(self): self.resolve.lhs_cube_dim_coverage = self.tgt_dim_coverage self.resolve.lhs_cube_aux_coverage = self.tgt_aux_coverage self.resolve.mapping = {} - self.shape = sentinel.shape + self.shape = mocker.sentinel.shape self.resolve._broadcast_shape = self.shape - self.resolve._src_cube_resolved = mock.Mock(shape=self.shape) - self.resolve._tgt_cube_resolved = mock.Mock(shape=self.shape) - self.m_dim_mapping = self.patch( + self.resolve._src_cube_resolved = mocker.Mock(shape=self.shape) + self.resolve._tgt_cube_resolved = mocker.Mock(shape=self.shape) + self.m_dim_mapping = mocker.patch( "iris.common.resolve.Resolve._dim_mapping", return_value={} ) - self.m_aux_mapping = self.patch( + self.m_aux_mapping = mocker.patch( "iris.common.resolve.Resolve._aux_mapping", return_value={} ) - self.m_free_mapping = self.patch("iris.common.resolve.Resolve._free_mapping") - self.m_as_compatible_cubes = self.patch( + self.m_free_mapping = mocker.patch("iris.common.resolve.Resolve._free_mapping") + self.m_as_compatible_cubes = mocker.patch( "iris.common.resolve.Resolve._as_compatible_cubes" ) self.mapping = {0: 1, 1: 2, 2: 3} - def test_mapped__dim_coords(self): + def test_mapped__dim_coords(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -1845,15 +1860,15 @@ def test_mapped__dim_coords(self): self.src_cube.ndim = 3 self.m_dim_mapping.return_value = self.mapping self.resolve._metadata_mapping() - self.assertEqual(self.mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(0, self.m_aux_mapping.call_count) - self.assertEqual(0, self.m_free_mapping.call_count) - self.assertEqual(1, self.m_as_compatible_cubes.call_count) - - def test_mapped__aux_coords(self): + assert self.resolve.mapping == self.mapping + assert self.m_dim_mapping.call_count == 1 + expected = [mocker.call(self.src_dim_coverage, self.tgt_dim_coverage)] + assert self.m_dim_mapping.call_args_list == expected + assert self.m_aux_mapping.call_count == 0 + assert self.m_free_mapping.call_count == 0 + assert self.m_as_compatible_cubes.call_count == 1 + + def test_mapped__aux_coords(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -1868,17 +1883,17 @@ def test_mapped__aux_coords(self): self.src_cube.ndim = 3 self.m_aux_mapping.return_value = self.mapping self.resolve._metadata_mapping() - self.assertEqual(self.mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(1, self.m_aux_mapping.call_count) - expected = [mock.call(self.src_aux_coverage, self.tgt_aux_coverage)] - self.assertEqual(expected, self.m_aux_mapping.call_args_list) - self.assertEqual(0, self.m_free_mapping.call_count) - self.assertEqual(1, self.m_as_compatible_cubes.call_count) - - def test_mapped__dim_and_aux_coords(self): + assert self.resolve.mapping == self.mapping + assert self.m_dim_mapping.call_count == 1 + expected = [mocker.call(self.src_dim_coverage, self.tgt_dim_coverage)] + assert self.m_dim_mapping.call_args_list == expected + assert self.m_aux_mapping.call_count == 1 + expected = [mocker.call(self.src_aux_coverage, self.tgt_aux_coverage)] + assert self.m_aux_mapping.call_args_list == expected + assert self.m_free_mapping.call_count == 0 + assert self.m_as_compatible_cubes.call_count == 1 + + def test_mapped__dim_and_aux_coords(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -1896,17 +1911,17 @@ def test_mapped__dim_and_aux_coords(self): self.m_dim_mapping.return_value = dim_mapping self.m_aux_mapping.return_value = aux_mapping self.resolve._metadata_mapping() - self.assertEqual(self.mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(1, self.m_aux_mapping.call_count) - expected = [mock.call(self.src_aux_coverage, self.tgt_aux_coverage)] - self.assertEqual(expected, self.m_aux_mapping.call_args_list) - self.assertEqual(0, self.m_free_mapping.call_count) - self.assertEqual(1, self.m_as_compatible_cubes.call_count) - - def test_mapped__dim_coords_and_free_dims(self): + assert self.resolve.mapping == self.mapping + assert self.m_dim_mapping.call_count == 1 + expected = [mocker.call(self.src_dim_coverage, self.tgt_dim_coverage)] + assert self.m_dim_mapping.call_args_list == expected + assert self.m_aux_mapping.call_count == 1 + expected = [mocker.call(self.src_aux_coverage, self.tgt_aux_coverage)] + assert self.m_aux_mapping.call_args_list == expected + assert self.m_free_mapping.call_count == 0 + assert self.m_as_compatible_cubes.call_count == 1 + + def test_mapped__dim_coords_and_free_dims(self, mocker): # key: (state) c=common, f=free, l=local # (coord) a=aux, d=dim # @@ -1925,26 +1940,26 @@ def test_mapped__dim_coords_and_free_dims(self): side_effect = lambda a, b, c, d: self.resolve.mapping.update(free_mapping) self.m_free_mapping.side_effect = side_effect self.resolve._metadata_mapping() - self.assertEqual(self.mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(1, self.m_aux_mapping.call_count) - expected = [mock.call(self.src_aux_coverage, self.tgt_aux_coverage)] - self.assertEqual(expected, self.m_aux_mapping.call_args_list) - self.assertEqual(1, self.m_free_mapping.call_count) + assert self.resolve.mapping == self.mapping + assert self.m_dim_mapping.call_count == 1 + expected = [mocker.call(self.src_dim_coverage, self.tgt_dim_coverage)] + assert self.m_dim_mapping.call_args_list == expected + assert self.m_aux_mapping.call_count == 1 + expected = [mocker.call(self.src_aux_coverage, self.tgt_aux_coverage)] + assert self.m_aux_mapping.call_args_list == expected + assert self.m_free_mapping.call_count == 1 expected = [ - mock.call( + mocker.call( self.src_dim_coverage, self.tgt_dim_coverage, self.src_aux_coverage, self.tgt_aux_coverage, ) ] - self.assertEqual(expected, self.m_free_mapping.call_args_list) - self.assertEqual(1, self.m_as_compatible_cubes.call_count) + assert self.m_free_mapping.call_args_list == expected + assert self.m_as_compatible_cubes.call_count == 1 - def test_mapped__dim_coords_with_broadcast_flip(self): + def test_mapped__dim_coords_with_broadcast_flip(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -1965,16 +1980,16 @@ def test_mapped__dim_coords_with_broadcast_flip(self): self.resolve._src_cube_resolved.shape = broadcast_shape self.resolve._tgt_cube_resolved.shape = (1, 4, 3, 2) self.resolve._metadata_mapping() - self.assertEqual(mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(0, self.m_aux_mapping.call_count) - self.assertEqual(0, self.m_free_mapping.call_count) - self.assertEqual(2, self.m_as_compatible_cubes.call_count) - self.assertEqual(not self.map_rhs_to_lhs, self.resolve.map_rhs_to_lhs) - - def test_mapped__dim_coords_free_flip_with_free_flip(self): + assert self.resolve.mapping == mapping + assert self.m_dim_mapping.call_count == 1 + expected = [mocker.call(self.src_dim_coverage, self.tgt_dim_coverage)] + assert self.m_dim_mapping.call_args_list == expected + assert self.m_aux_mapping.call_count == 0 + assert self.m_free_mapping.call_count == 0 + assert self.m_as_compatible_cubes.call_count == 2 + assert self.resolve.map_rhs_to_lhs != self.map_rhs_to_lhs + + def test_mapped__dim_coords_free_flip_with_free_flip(self, mocker): # key: (state) c=common, f=free, l=local # (coord) a=aux, d=dim # @@ -1997,28 +2012,29 @@ def test_mapped__dim_coords_free_flip_with_free_flip(self): self.tgt_dim_coverage.dims_free = [0, 1] self.tgt_aux_coverage.dims_free = [0, 1] self.resolve._metadata_mapping() - self.assertEqual(mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(1, self.m_aux_mapping.call_count) - expected = [mock.call(self.src_aux_coverage, self.tgt_aux_coverage)] - self.assertEqual(expected, self.m_aux_mapping.call_args_list) - self.assertEqual(1, self.m_free_mapping.call_count) + assert self.resolve.mapping == mapping + assert self.m_dim_mapping.call_count == 1 + expected = [mocker.call(self.src_dim_coverage, self.tgt_dim_coverage)] + assert self.m_dim_mapping.call_args_list == expected + assert self.m_aux_mapping.call_count == 1 + expected = [mocker.call(self.src_aux_coverage, self.tgt_aux_coverage)] + assert self.m_aux_mapping.call_args_list == expected + assert self.m_free_mapping.call_count == 1 expected = [ - mock.call( + mocker.call( self.src_dim_coverage, self.tgt_dim_coverage, self.src_aux_coverage, self.tgt_aux_coverage, ) ] - self.assertEqual(expected, self.m_free_mapping.call_args_list) - self.assertEqual(2, self.m_as_compatible_cubes.call_count) + assert self.m_free_mapping.call_args_list == expected + assert self.m_as_compatible_cubes.call_count == 2 -class Test__prepare_common_dim_payload(tests.IrisTest): - def setUp(self): +class Test__prepare_common_dim_payload: + @pytest.fixture(autouse=True) + def _setup(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -2031,26 +2047,30 @@ def setUp(self): # src-to-tgt mapping: # 0->1, 1->2, 2->3 self.points = ( - sentinel.points_0, - sentinel.points_1, - sentinel.points_2, - sentinel.points_3, + mocker.sentinel.points_0, + mocker.sentinel.points_1, + mocker.sentinel.points_2, + mocker.sentinel.points_3, + ) + self.bounds = ( + mocker.sentinel.bounds_0, + mocker.sentinel.bounds_1, + mocker.sentinel.bounds_2, ) - self.bounds = sentinel.bounds_0, sentinel.bounds_1, sentinel.bounds_2 self.pb_0 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[0])), - mock.Mock(copy=mock.Mock(return_value=self.bounds[0])), + mocker.Mock(copy=mocker.Mock(return_value=self.points[0])), + mocker.Mock(copy=mocker.Mock(return_value=self.bounds[0])), ) self.pb_1 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[1])), + mocker.Mock(copy=mocker.Mock(return_value=self.points[1])), None, ) self.pb_2 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[2])), - mock.Mock(copy=mock.Mock(return_value=self.bounds[2])), + mocker.Mock(copy=mocker.Mock(return_value=self.points[2])), + mocker.Mock(copy=mocker.Mock(return_value=self.bounds[2])), ) side_effect = (self.pb_0, self.pb_1, self.pb_2) - self.m_prepare_points_and_bounds = self.patch( + self.m_prepare_points_and_bounds = mocker.patch( "iris.common.resolve.Resolve._prepare_points_and_bounds", side_effect=side_effect, ) @@ -2061,12 +2081,12 @@ def setUp(self): self.mapping = {0: 1, 1: 2, 2: 3} self.resolve.mapping = self.mapping self.metadata_combined = ( - sentinel.combined_0, - sentinel.combined_1, - sentinel.combined_2, + mocker.sentinel.combined_0, + mocker.sentinel.combined_1, + mocker.sentinel.combined_2, ) - self.src_metadata = mock.Mock( - combine=mock.Mock(side_effect=self.metadata_combined) + self.src_metadata = mocker.Mock( + combine=mocker.Mock(side_effect=self.metadata_combined) ) metadata = [self.src_metadata] * len(self.mapping) self.src_coords = [ @@ -2074,9 +2094,9 @@ def setUp(self): # be of a class which is not-a-MeshCoord. # NOTE: strictly, bounds should =above values, and support .copy(). # For these tests, just omitting them works + is simpler. - Mock(spec=DimCoord, points=self.points[0], bounds=None), - Mock(spec=DimCoord, points=self.points[1], bounds=None), - Mock(spec=DimCoord, points=self.points[2], bounds=None), + mocker.Mock(spec=DimCoord, points=self.points[0], bounds=None), + mocker.Mock(spec=DimCoord, points=self.points[1], bounds=None), + mocker.Mock(spec=DimCoord, points=self.points[2], bounds=None), ] self.src_dims_common = [0, 1, 2] self.container = DimCoord @@ -2089,20 +2109,20 @@ def setUp(self): dims_free=[], ) self.tgt_metadata = [ - sentinel.tgt_metadata_0, - sentinel.tgt_metadata_1, - sentinel.tgt_metadata_2, - sentinel.tgt_metadata_3, + mocker.sentinel.tgt_metadata_0, + mocker.sentinel.tgt_metadata_1, + mocker.sentinel.tgt_metadata_2, + mocker.sentinel.tgt_metadata_3, ] self.tgt_coords = [ # N.B. these need to mimic a Coord with points and bounds, and # be of a class which is not-a-MeshCoord. # NOTE: strictly, bounds should =above values, and support .copy(). # For these tests, just omitting them works + is simpler. - Mock(spec=DimCoord, points=self.points[0], bounds=None), - Mock(spec=DimCoord, points=self.points[1], bounds=None), - Mock(spec=DimCoord, points=self.points[2], bounds=None), - Mock(spec=DimCoord, points=self.points[3], bounds=None), + mocker.Mock(spec=DimCoord, points=self.points[0], bounds=None), + mocker.Mock(spec=DimCoord, points=self.points[1], bounds=None), + mocker.Mock(spec=DimCoord, points=self.points[2], bounds=None), + mocker.Mock(spec=DimCoord, points=self.points[3], bounds=None), ] self.tgt_dims_common = [1, 2, 3] self.tgt_dim_coverage = _DimCoverage( @@ -2122,10 +2142,10 @@ def _check(self, ignore_mismatch=None, bad_points=None): self.tgt_dim_coverage, ignore_mismatch=ignore_mismatch, ) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_aux) == 0 + assert len(self.resolve.prepared_category.items_scalar) == 0 if not bad_points: - self.assertEqual(3, len(self.resolve.prepared_category.items_dim)) + assert len(self.resolve.prepared_category.items_dim) == 3 expected = [ _PreparedItem( metadata=_PreparedMetadata( @@ -2161,10 +2181,10 @@ def _check(self, ignore_mismatch=None, bad_points=None): container=self.container, ), ] - self.assertEqual(expected, self.resolve.prepared_category.items_dim) + assert self.resolve.prepared_category.items_dim == expected else: - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) - self.assertEqual(3, self.m_prepare_points_and_bounds.call_count) + assert len(self.resolve.prepared_category.items_dim) == 0 + assert self.m_prepare_points_and_bounds.call_count == 3 if ignore_mismatch is None: ignore_mismatch = False expected = [ @@ -2190,11 +2210,11 @@ def _check(self, ignore_mismatch=None, bad_points=None): ignore_mismatch=ignore_mismatch, ), ] - self.assertEqual(expected, self.m_prepare_points_and_bounds.call_args_list) + assert self.m_prepare_points_and_bounds.call_args_list == expected if not bad_points: - self.assertEqual(3, self.src_metadata.combine.call_count) + assert self.src_metadata.combine.call_count == 3 expected = [mock.call(metadata) for metadata in self.tgt_metadata[1:]] - self.assertEqual(expected, self.src_metadata.combine.call_args_list) + assert self.src_metadata.combine.call_args_list == expected def test__default_ignore_mismatch(self): self._check() @@ -2211,8 +2231,9 @@ def test__bad_points(self): self._check(bad_points=True) -class Test__prepare_common_aux_payload(tests.IrisTest): - def setUp(self): +class Test__prepare_common_aux_payload: + @pytest.fixture(autouse=True) + def _setup(self, mocker): # key: (state) c=common, f=free # (coord) a=aux, d=dim # @@ -2225,26 +2246,30 @@ def setUp(self): # src-to-tgt mapping: # 0->1, 1->2, 2->3 self.points = ( - sentinel.points_0, - sentinel.points_1, - sentinel.points_2, - sentinel.points_3, + mocker.sentinel.points_0, + mocker.sentinel.points_1, + mocker.sentinel.points_2, + mocker.sentinel.points_3, + ) + self.bounds = ( + mocker.sentinel.bounds_0, + mocker.sentinel.bounds_1, + mocker.sentinel.bounds_2, ) - self.bounds = (sentinel.bounds_0, sentinel.bounds_1, sentinel.bounds_2) self.pb_0 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[0])), - mock.Mock(copy=mock.Mock(return_value=self.bounds[0])), + mocker.Mock(copy=mocker.Mock(return_value=self.points[0])), + mocker.Mock(copy=mocker.Mock(return_value=self.bounds[0])), ) self.pb_1 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[1])), + mocker.Mock(copy=mocker.Mock(return_value=self.points[1])), None, ) self.pb_2 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[2])), - mock.Mock(copy=mock.Mock(return_value=self.bounds[2])), + mocker.Mock(copy=mocker.Mock(return_value=self.points[2])), + mocker.Mock(copy=mocker.Mock(return_value=self.bounds[2])), ) side_effect = (self.pb_0, self.pb_1, self.pb_2) - self.m_prepare_points_and_bounds = self.patch( + self.m_prepare_points_and_bounds = mocker.patch( "iris.common.resolve.Resolve._prepare_points_and_bounds", side_effect=side_effect, ) @@ -2256,14 +2281,14 @@ def setUp(self): self.resolve.mapping = self.mapping self.resolve.map_rhs_to_lhs = True self.metadata_combined = ( - sentinel.combined_0, - sentinel.combined_1, - sentinel.combined_2, + mocker.sentinel.combined_0, + mocker.sentinel.combined_1, + mocker.sentinel.combined_2, ) self.src_metadata = [ - mock.Mock(combine=mock.Mock(return_value=self.metadata_combined[0])), - mock.Mock(combine=mock.Mock(return_value=self.metadata_combined[1])), - mock.Mock(combine=mock.Mock(return_value=self.metadata_combined[2])), + mocker.Mock(combine=mocker.Mock(return_value=self.metadata_combined[0])), + mocker.Mock(combine=mocker.Mock(return_value=self.metadata_combined[1])), + mocker.Mock(combine=mocker.Mock(return_value=self.metadata_combined[2])), ] self.src_coords = [ # N.B. these need to mimic a Coord with points and bounds, but also @@ -2279,7 +2304,7 @@ def setUp(self): _Item(*item) for item in zip(self.src_metadata, self.src_coords, self.src_dims) ] - self.tgt_metadata = [sentinel.tgt_metadata_0] + self.src_metadata + self.tgt_metadata = [mocker.sentinel.tgt_metadata_0] + self.src_metadata self.tgt_coords = [ # N.B. these need to mimic a Coord with points and bounds, but also # the type() defines the 'container' property of a prepared item. @@ -2308,7 +2333,7 @@ def _check(self, ignore_mismatch=None, bad_points=None): ignore_mismatch=ignore_mismatch, ) if not bad_points: - self.assertEqual(3, len(prepared_items)) + assert len(prepared_items) == 3 expected = [ _PreparedItem( metadata=_PreparedMetadata( @@ -2344,10 +2369,10 @@ def _check(self, ignore_mismatch=None, bad_points=None): container=self.container, ), ] - self.assertEqual(expected, prepared_items) + assert prepared_items == expected else: - self.assertEqual(0, len(prepared_items)) - self.assertEqual(3, self.m_prepare_points_and_bounds.call_count) + assert len(prepared_items) == 0 + assert self.m_prepare_points_and_bounds.call_count == 3 if ignore_mismatch is None: ignore_mismatch = False expected = [ @@ -2373,14 +2398,14 @@ def _check(self, ignore_mismatch=None, bad_points=None): ignore_mismatch=ignore_mismatch, ), ] - self.assertEqual(expected, self.m_prepare_points_and_bounds.call_args_list) + assert self.m_prepare_points_and_bounds.call_args_list == expected if not bad_points: for src_metadata, tgt_metadata in zip( self.src_metadata, self.tgt_metadata[1:] ): - self.assertEqual(1, src_metadata.combine.call_count) + assert src_metadata.combine.call_count == 1 expected = [mock.call(tgt_metadata)] - self.assertEqual(expected, src_metadata.combine.call_args_list) + assert src_metadata.combine.call_args_list == expected def test__default_ignore_mismatch(self): self._check() @@ -2403,7 +2428,7 @@ def test__no_tgt_metadata_match(self): self.resolve._prepare_common_aux_payload( self.src_common_items, tgt_common_items, prepared_items ) - self.assertEqual(0, len(prepared_items)) + assert len(prepared_items) == 0 def test__multi_tgt_metadata_match(self): item = self.tgt_common_items[1] @@ -2412,11 +2437,12 @@ def test__multi_tgt_metadata_match(self): self.resolve._prepare_common_aux_payload( self.src_common_items, tgt_common_items, prepared_items ) - self.assertEqual(0, len(prepared_items)) + assert len(prepared_items) == 0 -class Test__prepare_points_and_bounds(tests.IrisTest): - def setUp(self): +class Test__prepare_points_and_bounds: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.Coord = namedtuple( "Coord", [ @@ -2432,10 +2458,10 @@ def setUp(self): self.Cube = namedtuple("Cube", ["name", "shape"]) self.resolve = Resolve() self.resolve.map_rhs_to_lhs = True - self.src_name = sentinel.src_name - self.src_points = sentinel.src_points - self.src_bounds = sentinel.src_bounds - self.src_metadata = sentinel.src_metadata + self.src_name = mocker.sentinel.src_name + self.src_points = mocker.sentinel.src_points + self.src_bounds = mocker.sentinel.src_bounds + self.src_metadata = mocker.sentinel.src_metadata self.src_items = dict( name=lambda: self.src_name, points=self.src_points, @@ -2445,10 +2471,10 @@ def setUp(self): shape=None, has_bounds=None, ) - self.tgt_name = sentinel.tgt_name - self.tgt_points = sentinel.tgt_points - self.tgt_bounds = sentinel.tgt_bounds - self.tgt_metadata = sentinel.tgt_metadata + self.tgt_name = mocker.sentinel.tgt_name + self.tgt_points = mocker.sentinel.tgt_points + self.tgt_bounds = mocker.sentinel.tgt_bounds + self.tgt_metadata = mocker.sentinel.tgt_metadata self.tgt_items = dict( name=lambda: self.tgt_name, points=self.tgt_points, @@ -2458,7 +2484,7 @@ def setUp(self): shape=None, has_bounds=None, ) - self.m_array_equal = self.patch( + self.m_array_equal = mocker.patch( "iris.util.array_equal", side_effect=(True, True) ) @@ -2470,8 +2496,8 @@ def test_coord_ndim_unequal__tgt_ndim_greater(self): points, bounds = self.resolve._prepare_points_and_bounds( src_coord, tgt_coord, src_dims=None, tgt_dims=None ) - self.assertEqual(self.tgt_points, points) - self.assertEqual(self.tgt_bounds, bounds) + assert points == self.tgt_points + assert bounds == self.tgt_bounds def test_coord_ndim_unequal__src_ndim_greater(self): self.src_items["ndim"] = 10 @@ -2481,8 +2507,8 @@ def test_coord_ndim_unequal__src_ndim_greater(self): points, bounds = self.resolve._prepare_points_and_bounds( src_coord, tgt_coord, src_dims=None, tgt_dims=None ) - self.assertEqual(self.src_points, points) - self.assertEqual(self.src_bounds, bounds) + assert points == self.src_points + assert bounds == self.src_bounds def test_coord_ndim_equal__shape_unequal_with_src_broadcasting(self): # key: (state) c=common, f=free @@ -2517,8 +2543,8 @@ def test_coord_ndim_equal__shape_unequal_with_src_broadcasting(self): points, bounds = self.resolve._prepare_points_and_bounds( src_coord, tgt_coord, src_dims, tgt_dims ) - self.assertEqual(self.tgt_points, points) - self.assertEqual(self.tgt_bounds, bounds) + assert points == self.tgt_points + assert bounds == self.tgt_bounds def test_coord_ndim_equal__shape_unequal_with_tgt_broadcasting(self): # key: (state) c=common, f=free @@ -2553,11 +2579,12 @@ def test_coord_ndim_equal__shape_unequal_with_tgt_broadcasting(self): points, bounds = self.resolve._prepare_points_and_bounds( src_coord, tgt_coord, src_dims, tgt_dims ) - self.assertEqual(self.src_points, points) - self.assertEqual(self.src_bounds, bounds) + assert points == self.src_points + assert bounds == self.src_bounds def test_coord_ndim_equal__shape_unequal_with_unsupported_broadcasting( self, + mocker, ): # key: (state) c=common, f=free # (coord) x=coord @@ -2579,7 +2606,7 @@ def test_coord_ndim_equal__shape_unequal_with_unsupported_broadcasting( src_shape = (9, 1) src_dims = tuple(mapping.keys()) self.resolve.rhs_cube = self.Cube( - name=lambda: sentinel.src_cube, shape=src_shape + name=lambda: mocker.sentinel.src_cube, shape=src_shape ) self.src_items["ndim"] = ndim self.src_items["shape"] = src_shape @@ -2587,13 +2614,13 @@ def test_coord_ndim_equal__shape_unequal_with_unsupported_broadcasting( tgt_shape = (1, 9) tgt_dims = tuple(mapping.values()) self.resolve.lhs_cube = self.Cube( - name=lambda: sentinel.tgt_cube, shape=tgt_shape + name=lambda: mocker.sentinel.tgt_cube, shape=tgt_shape ) self.tgt_items["ndim"] = ndim self.tgt_items["shape"] = tgt_shape tgt_coord = self.Coord(**self.tgt_items) emsg = "Cannot broadcast" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = self.resolve._prepare_points_and_bounds( src_coord, tgt_coord, src_dims, tgt_dims ) @@ -2614,8 +2641,12 @@ def _populate(self, src_points, tgt_points, src_bounds=None, tgt_bounds=None): mapping = {0: 0, 1: 1} self.resolve.mapping = mapping self.resolve.map_rhs_to_lhs = True - self.resolve.rhs_cube = self.Cube(name=lambda: sentinel.src_cube, shape=None) - self.resolve.lhs_cube = self.Cube(name=lambda: sentinel.tgt_cube, shape=None) + self.resolve.rhs_cube = self.Cube( + name=lambda: mock.sentinel.src_cube, shape=None + ) + self.resolve.lhs_cube = self.Cube( + name=lambda: mock.sentinel.tgt_cube, shape=None + ) ndim = 1 src_dims = 1 self.src_items["ndim"] = ndim @@ -2639,40 +2670,42 @@ def _populate(self, src_points, tgt_points, src_bounds=None, tgt_bounds=None): ) return args - def test_coord_ndim_and_shape_equal__points_equal_with_no_bounds(self): + def test_coord_ndim_and_shape_equal__points_equal_with_no_bounds(self, mocker): args = self._populate(self.src_points, self.src_points) points, bounds = self.resolve._prepare_points_and_bounds(**args) - self.assertEqual(self.src_points, points) - self.assertIsNone(bounds) - self.assertEqual(1, self.m_array_equal.call_count) - expected = [mock.call(self.src_points, self.src_points, withnans=True)] - self.assertEqual(expected, self.m_array_equal.call_args_list) + assert points == self.src_points + assert bounds is None + assert self.m_array_equal.call_count == 1 + expected = [mocker.call(self.src_points, self.src_points, withnans=True)] + assert self.m_array_equal.call_args_list == expected def test_coord_ndim_and_shape_equal__points_equal_with_src_bounds_only( self, + mocker, ): args = self._populate( self.src_points, self.src_points, src_bounds=self.src_bounds ) points, bounds = self.resolve._prepare_points_and_bounds(**args) - self.assertEqual(self.src_points, points) - self.assertEqual(self.src_bounds, bounds) - self.assertEqual(1, self.m_array_equal.call_count) - expected = [mock.call(self.src_points, self.src_points, withnans=True)] - self.assertEqual(expected, self.m_array_equal.call_args_list) + assert points == self.src_points + assert bounds == self.src_bounds + assert self.m_array_equal.call_count == 1 + expected = [mocker.call(self.src_points, self.src_points, withnans=True)] + assert self.m_array_equal.call_args_list == expected def test_coord_ndim_and_shape_equal__points_equal_with_tgt_bounds_only( self, + mocker, ): args = self._populate( self.src_points, self.src_points, tgt_bounds=self.tgt_bounds ) points, bounds = self.resolve._prepare_points_and_bounds(**args) - self.assertEqual(self.src_points, points) - self.assertEqual(self.tgt_bounds, bounds) - self.assertEqual(1, self.m_array_equal.call_count) - expected = [mock.call(self.src_points, self.src_points, withnans=True)] - self.assertEqual(expected, self.m_array_equal.call_args_list) + assert points == self.src_points + assert bounds == self.tgt_bounds + assert self.m_array_equal.call_count == 1 + expected = [mocker.call(self.src_points, self.src_points, withnans=True)] + assert self.m_array_equal.call_args_list == expected def test_coord_ndim_and_shape_equal__points_equal_with_src_bounds_only_strict( self, @@ -2682,7 +2715,7 @@ def test_coord_ndim_and_shape_equal__points_equal_with_src_bounds_only_strict( ) with LENIENT.context(maths=False): emsg = f"Coordinate {self.src_name} has bounds" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = self.resolve._prepare_points_and_bounds(**args) def test_coord_ndim_and_shape_equal__points_equal_with_tgt_bounds_only_strict( @@ -2693,10 +2726,10 @@ def test_coord_ndim_and_shape_equal__points_equal_with_tgt_bounds_only_strict( ) with LENIENT.context(maths=False): emsg = f"Coordinate {self.tgt_name} has bounds" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = self.resolve._prepare_points_and_bounds(**args) - def test_coord_ndim_and_shape_equal__points_equal_with_bounds_equal(self): + def test_coord_ndim_and_shape_equal__points_equal_with_bounds_equal(self, mocker): args = self._populate( self.src_points, self.src_points, @@ -2704,14 +2737,14 @@ def test_coord_ndim_and_shape_equal__points_equal_with_bounds_equal(self): tgt_bounds=self.src_bounds, ) points, bounds = self.resolve._prepare_points_and_bounds(**args) - self.assertEqual(self.src_points, points) - self.assertEqual(self.src_bounds, bounds) - self.assertEqual(2, self.m_array_equal.call_count) + assert points == self.src_points + assert bounds == self.src_bounds + assert self.m_array_equal.call_count == 2 expected = [ - mock.call(self.src_points, self.src_points, withnans=True), - mock.call(self.src_bounds, self.src_bounds, withnans=True), + mocker.call(self.src_points, self.src_points, withnans=True), + mocker.call(self.src_bounds, self.src_bounds, withnans=True), ] - self.assertEqual(expected, self.m_array_equal.call_args_list) + assert self.m_array_equal.call_args_list == expected def test_coord_ndim_and_shape_equal__points_equal_with_bounds_different( self, @@ -2724,11 +2757,12 @@ def test_coord_ndim_and_shape_equal__points_equal_with_bounds_different( tgt_bounds=self.tgt_bounds, ) emsg = f"Coordinate {self.src_name} has different bounds" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = self.resolve._prepare_points_and_bounds(**args) def test_coord_ndim_and_shape_equal__points_equal_with_bounds_different_ignore_mismatch( self, + mocker, ): self.m_array_equal.side_effect = (True, False) args = self._populate( @@ -2740,14 +2774,14 @@ def test_coord_ndim_and_shape_equal__points_equal_with_bounds_different_ignore_m points, bounds = self.resolve._prepare_points_and_bounds( **args, ignore_mismatch=True ) - self.assertEqual(self.src_points, points) - self.assertIsNone(bounds) - self.assertEqual(2, self.m_array_equal.call_count) + assert points == self.src_points + assert bounds is None + assert self.m_array_equal.call_count == 2 expected = [ - mock.call(self.src_points, self.src_points, withnans=True), - mock.call(self.src_bounds, self.tgt_bounds, withnans=True), + mocker.call(self.src_points, self.src_points, withnans=True), + mocker.call(self.src_bounds, self.tgt_bounds, withnans=True), ] - self.assertEqual(expected, self.m_array_equal.call_args_list) + assert self.m_array_equal.call_args_list == expected def test_coord_ndim_and_shape_equal__points_equal_with_bounds_different_strict( self, @@ -2761,14 +2795,14 @@ def test_coord_ndim_and_shape_equal__points_equal_with_bounds_different_strict( ) with LENIENT.context(maths=False): emsg = f"Coordinate {self.src_name} has different bounds" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = self.resolve._prepare_points_and_bounds(**args) def test_coord_ndim_and_shape_equal__points_different(self): self.m_array_equal.side_effect = (False,) args = self._populate(self.src_points, self.tgt_points) emsg = f"Coordinate {self.src_name} has different points" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = self.resolve._prepare_points_and_bounds(**args) def test_coord_ndim_and_shape_equal__points_different_ignore_mismatch( @@ -2779,30 +2813,31 @@ def test_coord_ndim_and_shape_equal__points_different_ignore_mismatch( points, bounds = self.resolve._prepare_points_and_bounds( **args, ignore_mismatch=True ) - self.assertIsNone(points) - self.assertIsNone(bounds) + assert points is None + assert bounds is None def test_coord_ndim_and_shape_equal__points_different_strict(self): self.m_array_equal.side_effect = (False,) args = self._populate(self.src_points, self.tgt_points) with LENIENT.context(maths=False): emsg = f"Coordinate {self.src_name} has different points" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = self.resolve._prepare_points_and_bounds(**args) -class Test__create_prepared_item(tests.IrisTest): - def setUp(self): +class Test__create_prepared_item: + @pytest.fixture(autouse=True) + def _setup(self, mocker): Coord = namedtuple("Coord", ["points", "bounds"]) - self.points_value = sentinel.points - self.points = mock.Mock(copy=mock.Mock(return_value=self.points_value)) - self.bounds_value = sentinel.bounds - self.bounds = mock.Mock(copy=mock.Mock(return_value=self.bounds_value)) + self.points_value = mocker.sentinel.points + self.points = mocker.Mock(copy=mocker.Mock(return_value=self.points_value)) + self.bounds_value = mocker.sentinel.bounds + self.bounds = mocker.Mock(copy=mocker.Mock(return_value=self.bounds_value)) self.coord = Coord(points=self.points, bounds=self.bounds) self.container = type(self.coord) - self.combined = sentinel.combined - self.src = mock.Mock(combine=mock.Mock(return_value=self.combined)) - self.tgt = sentinel.tgt + self.combined = mocker.sentinel.combined + self.src = mocker.Mock(combine=mocker.Mock(return_value=self.combined)) + self.tgt = mocker.sentinel.tgt def _check(self, src=None, tgt=None): dims = 0 @@ -2813,18 +2848,18 @@ def _check(self, src=None, tgt=None): result = Resolve._create_prepared_item( self.coord, dims, src_metadata=src, tgt_metadata=tgt ) - self.assertIsInstance(result, _PreparedItem) - self.assertIsInstance(result.metadata, _PreparedMetadata) + assert isinstance(result, _PreparedItem) + assert isinstance(result.metadata, _PreparedMetadata) expected = _PreparedMetadata(combined=combined, src=src, tgt=tgt) - self.assertEqual(expected, result.metadata) - self.assertEqual(self.points_value, result.points) - self.assertEqual(1, self.points.copy.call_count) - self.assertEqual([mock.call()], self.points.copy.call_args_list) - self.assertEqual(self.bounds_value, result.bounds) - self.assertEqual(1, self.bounds.copy.call_count) - self.assertEqual([mock.call()], self.bounds.copy.call_args_list) - self.assertEqual((dims,), result.dims) - self.assertEqual(self.container, result.container) + assert result.metadata == expected + assert result.points == self.points_value + assert self.points.copy.call_count == 1 + assert self.points.copy.call_args_list == [mock.call()] + assert result.bounds == self.bounds_value + assert self.bounds.copy.call_count == 1 + assert self.bounds.copy.call_args_list == [mock.call()] + assert result.dims == (dims,) + assert result.container == self.container def test__no_metadata(self): self._check() @@ -2839,8 +2874,9 @@ def test__combine_metadata(self): self._check(src=self.src, tgt=self.tgt) -class Test__prepare_local_payload_dim(tests.IrisTest): - def setUp(self): +class Test__prepare_local_payload_dim: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.Cube = namedtuple("Cube", ["ndim"]) self.resolve = Resolve() self.resolve.prepared_category = _CategoryItems( @@ -2856,8 +2892,8 @@ def setUp(self): dims_free=None, ) self.tgt_coverage = deepcopy(self.src_coverage) - self.prepared_item = sentinel.prepared_item - self.m_create_prepared_item = self.patch( + self.prepared_item = mocker.sentinel.prepared_item + self.m_create_prepared_item = mocker.patch( "iris.common.resolve.Resolve._create_prepared_item", return_value=self.prepared_item, ) @@ -2880,7 +2916,7 @@ def test_src_no_local_with_tgt_no_local(self): self.tgt_coverage["cube"] = self.Cube(ndim=2) tgt_coverage = _DimCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) + assert len(self.resolve.prepared_category.items_dim) == 0 def test_src_no_local_with_tgt_no_local__strict(self): # key: (state) c=common, f=free, l=local @@ -2901,7 +2937,7 @@ def test_src_no_local_with_tgt_no_local__strict(self): tgt_coverage = _DimCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) + assert len(self.resolve.prepared_category.items_dim) == 0 def test_src_local_with_tgt_local(self): # key: (state) c=common, f=free, l=local @@ -2923,7 +2959,7 @@ def test_src_local_with_tgt_local(self): self.tgt_coverage["cube"] = self.Cube(ndim=2) tgt_coverage = _DimCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) + assert len(self.resolve.prepared_category.items_dim) == 0 def test_src_local_with_tgt_local__strict(self): # key: (state) c=common, f=free, l=local @@ -2946,9 +2982,9 @@ def test_src_local_with_tgt_local__strict(self): tgt_coverage = _DimCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) + assert len(self.resolve.prepared_category.items_dim) == 0 - def test_src_local_with_tgt_free(self): + def test_src_local_with_tgt_free(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -2964,23 +3000,21 @@ def test_src_local_with_tgt_free(self): self.resolve.mapping = mapping src_dim = 1 self.src_coverage["dims_local"] = (src_dim,) - src_metadata = sentinel.src_metadata + src_metadata = mocker.sentinel.src_metadata self.src_coverage["metadata"] = [None, src_metadata] - src_coord = sentinel.src_coord + src_coord = mocker.sentinel.src_coord self.src_coverage["coords"] = [None, src_coord] src_coverage = _DimCoverage(**self.src_coverage) self.tgt_coverage["cube"] = self.Cube(ndim=2) tgt_coverage = _DimCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_dim)) - self.assertEqual( - self.prepared_item, self.resolve.prepared_category.items_dim[0] - ) - self.assertEqual(1, self.m_create_prepared_item.call_count) - expected = [mock.call(src_coord, mapping[src_dim], src_metadata=src_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert len(self.resolve.prepared_category.items_dim) == 1 + assert self.resolve.prepared_category.items_dim[0] == self.prepared_item + assert self.m_create_prepared_item.call_count == 1 + expected = [mocker.call(src_coord, mapping[src_dim], src_metadata=src_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_local_with_tgt_free__strict(self): + def test_src_local_with_tgt_free__strict(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -2996,18 +3030,18 @@ def test_src_local_with_tgt_free__strict(self): self.resolve.mapping = mapping src_dim = 1 self.src_coverage["dims_local"] = (src_dim,) - src_metadata = sentinel.src_metadata + src_metadata = mocker.sentinel.src_metadata self.src_coverage["metadata"] = [None, src_metadata] - src_coord = sentinel.src_coord + src_coord = mocker.sentinel.src_coord self.src_coverage["coords"] = [None, src_coord] src_coverage = _DimCoverage(**self.src_coverage) self.tgt_coverage["cube"] = self.Cube(ndim=2) tgt_coverage = _DimCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) + assert len(self.resolve.prepared_category.items_dim) == 0 - def test_src_free_with_tgt_local(self): + def test_src_free_with_tgt_local(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3025,21 +3059,19 @@ def test_src_free_with_tgt_local(self): self.tgt_coverage["cube"] = self.Cube(ndim=2) tgt_dim = 1 self.tgt_coverage["dims_local"] = (tgt_dim,) - tgt_metadata = sentinel.tgt_metadata + tgt_metadata = mocker.sentinel.tgt_metadata self.tgt_coverage["metadata"] = [None, tgt_metadata] - tgt_coord = sentinel.tgt_coord + tgt_coord = mocker.sentinel.tgt_coord self.tgt_coverage["coords"] = [None, tgt_coord] tgt_coverage = _DimCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_dim)) - self.assertEqual( - self.prepared_item, self.resolve.prepared_category.items_dim[0] - ) - self.assertEqual(1, self.m_create_prepared_item.call_count) - expected = [mock.call(tgt_coord, tgt_dim, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert len(self.resolve.prepared_category.items_dim) == 1 + assert self.prepared_item == self.resolve.prepared_category.items_dim[0] + assert self.m_create_prepared_item.call_count == 1 + expected = [mocker.call(tgt_coord, tgt_dim, tgt_metadata=tgt_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_free_with_tgt_local__strict(self): + def test_src_free_with_tgt_local__strict(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3057,16 +3089,16 @@ def test_src_free_with_tgt_local__strict(self): self.tgt_coverage["cube"] = self.Cube(ndim=2) tgt_dim = 1 self.tgt_coverage["dims_local"] = (tgt_dim,) - tgt_metadata = sentinel.tgt_metadata + tgt_metadata = mocker.sentinel.tgt_metadata self.tgt_coverage["metadata"] = [None, tgt_metadata] - tgt_coord = sentinel.tgt_coord + tgt_coord = mocker.sentinel.tgt_coord self.tgt_coverage["coords"] = [None, tgt_coord] tgt_coverage = _DimCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) + assert len(self.resolve.prepared_category.items_dim) == 0 - def test_src_no_local_with_tgt_local__extra_dims(self): + def test_src_no_local_with_tgt_local__extra_dims(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3084,21 +3116,19 @@ def test_src_no_local_with_tgt_local__extra_dims(self): self.tgt_coverage["cube"] = self.Cube(ndim=3) tgt_dim = 0 self.tgt_coverage["dims_local"] = (tgt_dim,) - tgt_metadata = sentinel.tgt_metadata + tgt_metadata = mocker.sentinel.tgt_metadata self.tgt_coverage["metadata"] = [tgt_metadata, None, None] - tgt_coord = sentinel.tgt_coord + tgt_coord = mocker.sentinel.tgt_coord self.tgt_coverage["coords"] = [tgt_coord, None, None] tgt_coverage = _DimCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_dim)) - self.assertEqual( - self.prepared_item, self.resolve.prepared_category.items_dim[0] - ) - self.assertEqual(1, self.m_create_prepared_item.call_count) - expected = [mock.call(tgt_coord, tgt_dim, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert len(self.resolve.prepared_category.items_dim) == 1 + assert self.resolve.prepared_category.items_dim[0] == self.prepared_item + assert self.m_create_prepared_item.call_count == 1 + expected = [mocker.call(tgt_coord, tgt_dim, tgt_metadata=tgt_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_no_local_with_tgt_local__extra_dims_strict(self): + def test_src_no_local_with_tgt_local__extra_dims_strict(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3116,24 +3146,23 @@ def test_src_no_local_with_tgt_local__extra_dims_strict(self): self.tgt_coverage["cube"] = self.Cube(ndim=3) tgt_dim = 0 self.tgt_coverage["dims_local"] = (tgt_dim,) - tgt_metadata = sentinel.tgt_metadata + tgt_metadata = mocker.sentinel.tgt_metadata self.tgt_coverage["metadata"] = [tgt_metadata, None, None] - tgt_coord = sentinel.tgt_coord + tgt_coord = mocker.sentinel.tgt_coord self.tgt_coverage["coords"] = [tgt_coord, None, None] tgt_coverage = _DimCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_dim)) - self.assertEqual( - self.prepared_item, self.resolve.prepared_category.items_dim[0] - ) - self.assertEqual(1, self.m_create_prepared_item.call_count) - expected = [mock.call(tgt_coord, tgt_dim, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert len(self.resolve.prepared_category.items_dim) == 1 + assert self.resolve.prepared_category.items_dim[0] == self.prepared_item + assert self.m_create_prepared_item.call_count == 1 + expected = [mocker.call(tgt_coord, tgt_dim, tgt_metadata=tgt_metadata)] + assert self.m_create_prepared_item.call_args_list == expected -class Test__prepare_local_payload_aux(tests.IrisTest): - def setUp(self): +class Test__prepare_local_payload_aux: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.Cube = namedtuple("Cube", ["ndim"]) self.resolve = Resolve() self.resolve.prepared_category = _CategoryItems( @@ -3151,9 +3180,9 @@ def setUp(self): dims_free=None, ) self.tgt_coverage = deepcopy(self.src_coverage) - self.src_prepared_item = sentinel.src_prepared_item - self.tgt_prepared_item = sentinel.tgt_prepared_item - self.m_create_prepared_item = self.patch( + self.src_prepared_item = mocker.sentinel.src_prepared_item + self.tgt_prepared_item = mocker.sentinel.tgt_prepared_item + self.m_create_prepared_item = mocker.patch( "iris.common.resolve.Resolve._create_prepared_item", side_effect=(self.src_prepared_item, self.tgt_prepared_item), ) @@ -3176,7 +3205,7 @@ def test_src_no_local_with_tgt_no_local(self): self.tgt_coverage["cube"] = self.Cube(ndim=2) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) + assert len(self.resolve.prepared_category.items_aux) == 0 def test_src_no_local_with_tgt_no_local__strict(self): # key: (state) c=common, f=free, l=local @@ -3197,9 +3226,9 @@ def test_src_no_local_with_tgt_no_local__strict(self): tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) + assert len(self.resolve.prepared_category.items_aux) == 0 - def test_src_local_with_tgt_local(self): + def test_src_local_with_tgt_local(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3213,30 +3242,30 @@ def test_src_local_with_tgt_local(self): # 0->0, 1->1 mapping = {0: 0, 1: 1} self.resolve.mapping = mapping - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_dims = (1,) src_item = _Item(metadata=src_metadata, coord=src_coord, dims=src_dims) self.src_coverage["local_items_aux"].append(src_item) src_coverage = _AuxCoverage(**self.src_coverage) self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_dims = (1,) tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) self.tgt_coverage["local_items_aux"].append(tgt_item) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(2, len(self.resolve.prepared_category.items_aux)) + assert len(self.resolve.prepared_category.items_aux) == 2 expected = [self.src_prepared_item, self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_aux) + assert self.resolve.prepared_category.items_aux == expected expected = [ - mock.call(src_coord, tgt_dims, src_metadata=src_metadata), - mock.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata), + mocker.call(src_coord, tgt_dims, src_metadata=src_metadata), + mocker.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata), ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.m_create_prepared_item.call_args_list == expected - def test_src_local_with_tgt_local__strict(self): + def test_src_local_with_tgt_local__strict(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3250,24 +3279,24 @@ def test_src_local_with_tgt_local__strict(self): # 0->0, 1->1 mapping = {0: 0, 1: 1} self.resolve.mapping = mapping - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_dims = (1,) src_item = _Item(metadata=src_metadata, coord=src_coord, dims=src_dims) self.src_coverage["local_items_aux"].append(src_item) src_coverage = _AuxCoverage(**self.src_coverage) self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_dims = (1,) tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) self.tgt_coverage["local_items_aux"].append(tgt_item) tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) + assert len(self.resolve.prepared_category.items_aux) == 0 - def test_src_local_with_tgt_free(self): + def test_src_local_with_tgt_free(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3281,8 +3310,8 @@ def test_src_local_with_tgt_free(self): # 0->0, 1->1 mapping = {0: 0, 1: 1} self.resolve.mapping = mapping - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_dims = (1,) src_item = _Item(metadata=src_metadata, coord=src_coord, dims=src_dims) self.src_coverage["local_items_aux"].append(src_item) @@ -3291,13 +3320,13 @@ def test_src_local_with_tgt_free(self): self.tgt_coverage["cube"] = self.Cube(ndim=2) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_aux)) + assert len(self.resolve.prepared_category.items_aux) == 1 expected = [self.src_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_aux) - expected = [mock.call(src_coord, src_dims, src_metadata=src_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.resolve.prepared_category.items_aux == expected + expected = [mocker.call(src_coord, src_dims, src_metadata=src_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_local_with_tgt_free__strict(self): + def test_src_local_with_tgt_free__strict(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3311,8 +3340,8 @@ def test_src_local_with_tgt_free__strict(self): # 0->0, 1->1 mapping = {0: 0, 1: 1} self.resolve.mapping = mapping - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_dims = (1,) src_item = _Item(metadata=src_metadata, coord=src_coord, dims=src_dims) self.src_coverage["local_items_aux"].append(src_item) @@ -3322,9 +3351,9 @@ def test_src_local_with_tgt_free__strict(self): tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) + assert len(self.resolve.prepared_category.items_aux) == 0 - def test_src_free_with_tgt_local(self): + def test_src_free_with_tgt_local(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3341,21 +3370,21 @@ def test_src_free_with_tgt_local(self): self.resolve.mapping = mapping src_coverage = _AuxCoverage(**self.src_coverage) self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_dims = (1,) tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) self.tgt_coverage["local_items_aux"].append(tgt_item) self.tgt_coverage["dims_local"].extend(tgt_dims) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_aux)) + assert len(self.resolve.prepared_category.items_aux) == 1 expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_aux) - expected = [mock.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.resolve.prepared_category.items_aux == expected + expected = [mocker.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_free_with_tgt_local__strict(self): + def test_src_free_with_tgt_local__strict(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3372,8 +3401,8 @@ def test_src_free_with_tgt_local__strict(self): self.resolve.mapping = mapping src_coverage = _AuxCoverage(**self.src_coverage) self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_dims = (1,) tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) self.tgt_coverage["local_items_aux"].append(tgt_item) @@ -3381,9 +3410,9 @@ def test_src_free_with_tgt_local__strict(self): tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) + assert len(self.resolve.prepared_category.items_aux) == 0 - def test_src_no_local_with_tgt_local__extra_dims(self): + def test_src_no_local_with_tgt_local__extra_dims(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3400,21 +3429,21 @@ def test_src_no_local_with_tgt_local__extra_dims(self): self.resolve.mapping = mapping src_coverage = _AuxCoverage(**self.src_coverage) self.tgt_coverage["cube"] = self.Cube(ndim=3) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_dims = (0,) tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) self.tgt_coverage["local_items_aux"].append(tgt_item) self.tgt_coverage["dims_local"].extend(tgt_dims) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_aux)) + assert len(self.resolve.prepared_category.items_aux) == 1 expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_aux) - expected = [mock.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.resolve.prepared_category.items_aux == expected + expected = [mocker.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_no_local_with_tgt_local__extra_dims_strict(self): + def test_src_no_local_with_tgt_local__extra_dims_strict(self, mocker): # key: (state) c=common, f=free, l=local # (coord) d=dim # @@ -3431,8 +3460,8 @@ def test_src_no_local_with_tgt_local__extra_dims_strict(self): self.resolve.mapping = mapping src_coverage = _AuxCoverage(**self.src_coverage) self.tgt_coverage["cube"] = self.Cube(ndim=3) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_dims = (0,) tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) self.tgt_coverage["local_items_aux"].append(tgt_item) @@ -3440,15 +3469,16 @@ def test_src_no_local_with_tgt_local__extra_dims_strict(self): tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=True): self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_aux)) + assert len(self.resolve.prepared_category.items_aux) == 1 expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_aux) - expected = [mock.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.resolve.prepared_category.items_aux == expected + expected = [mocker.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata)] + assert self.m_create_prepared_item.call_args_list == expected -class Test__prepare_local_payload_scalar(tests.IrisTest): - def setUp(self): +class Test__prepare_local_payload_scalar: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.Cube = namedtuple("Cube", ["ndim"]) self.resolve = Resolve() self.resolve.prepared_category = _CategoryItems( @@ -3465,9 +3495,9 @@ def setUp(self): dims_free=None, ) self.tgt_coverage = deepcopy(self.src_coverage) - self.src_prepared_item = sentinel.src_prepared_item - self.tgt_prepared_item = sentinel.tgt_prepared_item - self.m_create_prepared_item = self.patch( + self.src_prepared_item = mocker.sentinel.src_prepared_item + self.tgt_prepared_item = mocker.sentinel.tgt_prepared_item + self.m_create_prepared_item = mocker.patch( "iris.common.resolve.Resolve._create_prepared_item", side_effect=(self.src_prepared_item, self.tgt_prepared_item), ) @@ -3480,7 +3510,7 @@ def test_src_no_local_with_tgt_no_local(self): src_coverage = _AuxCoverage(**self.src_coverage) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 0 def test_src_no_local_with_tgt_no_local__strict(self): ndim = 2 @@ -3489,7 +3519,7 @@ def test_src_no_local_with_tgt_no_local__strict(self): tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 0 def test_src_no_local_with_tgt_no_local__src_scalar_cube(self): ndim = 0 @@ -3497,7 +3527,7 @@ def test_src_no_local_with_tgt_no_local__src_scalar_cube(self): src_coverage = _AuxCoverage(**self.src_coverage) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 0 def test_src_no_local_with_tgt_no_local__src_scalar_cube_strict(self): ndim = 0 @@ -3506,223 +3536,223 @@ def test_src_no_local_with_tgt_no_local__src_scalar_cube_strict(self): tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 0 - def test_src_local_with_tgt_no_local(self): + def test_src_local_with_tgt_no_local(self, mocker): ndim = 2 self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_item = _Item(metadata=src_metadata, coord=src_coord, dims=self.src_dims) self.src_coverage["local_items_scalar"].append(src_item) src_coverage = _AuxCoverage(**self.src_coverage) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 1 expected = [self.src_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [mock.call(src_coord, self.src_dims, src_metadata=src_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.resolve.prepared_category.items_scalar == expected + expected = [mocker.call(src_coord, self.src_dims, src_metadata=src_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_local_with_tgt_no_local__strict(self): + def test_src_local_with_tgt_no_local__strict(self, mocker): ndim = 2 self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_item = _Item(metadata=src_metadata, coord=src_coord, dims=self.src_dims) self.src_coverage["local_items_scalar"].append(src_item) src_coverage = _AuxCoverage(**self.src_coverage) tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 0 - def test_src_local_with_tgt_no_local__src_scalar_cube(self): + def test_src_local_with_tgt_no_local__src_scalar_cube(self, mocker): ndim = 0 self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_item = _Item(metadata=src_metadata, coord=src_coord, dims=self.src_dims) self.src_coverage["local_items_scalar"].append(src_item) src_coverage = _AuxCoverage(**self.src_coverage) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 1 expected = [self.src_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [mock.call(src_coord, self.src_dims, src_metadata=src_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.resolve.prepared_category.items_scalar == expected + expected = [mocker.call(src_coord, self.src_dims, src_metadata=src_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_local_with_tgt_no_local__src_scalar_cube_strict(self): + def test_src_local_with_tgt_no_local__src_scalar_cube_strict(self, mocker): ndim = 0 self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_item = _Item(metadata=src_metadata, coord=src_coord, dims=self.src_dims) self.src_coverage["local_items_scalar"].append(src_item) src_coverage = _AuxCoverage(**self.src_coverage) tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 0 - def test_src_no_local_with_tgt_local(self): + def test_src_no_local_with_tgt_local(self, mocker): self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) ndim = 2 self.src_coverage["cube"] = self.Cube(ndim=ndim) src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims) self.tgt_coverage["local_items_scalar"].append(tgt_item) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 1 expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [mock.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.resolve.prepared_category.items_scalar == expected + expected = [mocker.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_no_local_with_tgt_local__strict(self): + def test_src_no_local_with_tgt_local__strict(self, mocker): self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) ndim = 2 self.src_coverage["cube"] = self.Cube(ndim=ndim) src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims) self.tgt_coverage["local_items_scalar"].append(tgt_item) tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 0 - def test_src_no_local_with_tgt_local__src_scalar_cube(self): + def test_src_no_local_with_tgt_local__src_scalar_cube(self, mocker): self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) ndim = 0 self.src_coverage["cube"] = self.Cube(ndim=ndim) src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims) self.tgt_coverage["local_items_scalar"].append(tgt_item) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 1 expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [mock.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.resolve.prepared_category.items_scalar == expected + expected = [mocker.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_no_local_with_tgt_local__src_scalar_cube_strict(self): + def test_src_no_local_with_tgt_local__src_scalar_cube_strict(self, mocker): self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) ndim = 0 self.src_coverage["cube"] = self.Cube(ndim=ndim) src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims) self.tgt_coverage["local_items_scalar"].append(tgt_item) tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 1 expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [mock.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.resolve.prepared_category.items_scalar == expected + expected = [mocker.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata)] + assert self.m_create_prepared_item.call_args_list == expected - def test_src_local_with_tgt_local(self): + def test_src_local_with_tgt_local(self, mocker): ndim = 2 self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_item = _Item(metadata=src_metadata, coord=src_coord, dims=self.src_dims) self.src_coverage["local_items_scalar"].append(src_item) src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims) self.tgt_coverage["local_items_scalar"].append(tgt_item) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(2, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 2 expected = [self.src_prepared_item, self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) + assert self.resolve.prepared_category.items_scalar == expected expected = [ - mock.call(src_coord, self.src_dims, src_metadata=src_metadata), - mock.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata), + mocker.call(src_coord, self.src_dims, src_metadata=src_metadata), + mocker.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata), ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.m_create_prepared_item.call_args_list == expected - def test_src_local_with_tgt_local__strict(self): + def test_src_local_with_tgt_local__strict(self, mocker): ndim = 2 self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_item = _Item(metadata=src_metadata, coord=src_coord, dims=self.src_dims) self.src_coverage["local_items_scalar"].append(src_item) src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims) self.tgt_coverage["local_items_scalar"].append(tgt_item) tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 0 - def test_src_local_with_tgt_local__src_scalar_cube(self): + def test_src_local_with_tgt_local__src_scalar_cube(self, mocker): ndim = 0 self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_item = _Item(metadata=src_metadata, coord=src_coord, dims=self.src_dims) self.src_coverage["local_items_scalar"].append(src_item) src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims) self.tgt_coverage["local_items_scalar"].append(tgt_item) tgt_coverage = _AuxCoverage(**self.tgt_coverage) self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(2, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 2 expected = [self.src_prepared_item, self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) + assert self.resolve.prepared_category.items_scalar == expected expected = [ - mock.call(src_coord, self.src_dims, src_metadata=src_metadata), - mock.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata), + mocker.call(src_coord, self.src_dims, src_metadata=src_metadata), + mocker.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata), ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.m_create_prepared_item.call_args_list == expected - def test_src_local_with_tgt_local__src_scalar_cube_strict(self): + def test_src_local_with_tgt_local__src_scalar_cube_strict(self, mocker): ndim = 0 self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord + src_metadata = mocker.sentinel.src_metadata + src_coord = mocker.sentinel.src_coord src_item = _Item(metadata=src_metadata, coord=src_coord, dims=self.src_dims) self.src_coverage["local_items_scalar"].append(src_item) src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord + tgt_metadata = mocker.sentinel.tgt_metadata + tgt_coord = mocker.sentinel.tgt_coord tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims) self.tgt_coverage["local_items_scalar"].append(tgt_item) tgt_coverage = _AuxCoverage(**self.tgt_coverage) with LENIENT.context(maths=False): self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) + assert len(self.resolve.prepared_category.items_scalar) == 0 -class Test__prepare_local_payload(tests.IrisTest): - def test(self): - src_dim_coverage = sentinel.src_dim_coverage - src_aux_coverage = sentinel.src_aux_coverage - tgt_dim_coverage = sentinel.tgt_dim_coverage - tgt_aux_coverage = sentinel.tgt_aux_coverage +class Test__prepare_local_payload: + def test(self, mocker): + src_dim_coverage = mocker.sentinel.src_dim_coverage + src_aux_coverage = mocker.sentinel.src_aux_coverage + tgt_dim_coverage = mocker.sentinel.tgt_dim_coverage + tgt_aux_coverage = mocker.sentinel.tgt_aux_coverage root = "iris.common.resolve.Resolve" - m_prepare_dim = self.patch(f"{root}._prepare_local_payload_dim") - m_prepare_aux = self.patch(f"{root}._prepare_local_payload_aux") - m_prepare_scalar = self.patch(f"{root}._prepare_local_payload_scalar") + m_prepare_dim = mocker.patch(f"{root}._prepare_local_payload_dim") + m_prepare_aux = mocker.patch(f"{root}._prepare_local_payload_aux") + m_prepare_scalar = mocker.patch(f"{root}._prepare_local_payload_scalar") resolve = Resolve() resolve._prepare_local_payload( src_dim_coverage, @@ -3730,55 +3760,58 @@ def test(self): tgt_dim_coverage, tgt_aux_coverage, ) - self.assertEqual(1, m_prepare_dim.call_count) - expected = [mock.call(src_dim_coverage, tgt_dim_coverage)] - self.assertEqual(expected, m_prepare_dim.call_args_list) - self.assertEqual(1, m_prepare_aux.call_count) - expected = [mock.call(src_aux_coverage, tgt_aux_coverage)] - self.assertEqual(expected, m_prepare_aux.call_args_list) - self.assertEqual(1, m_prepare_scalar.call_count) - expected = [mock.call(src_aux_coverage, tgt_aux_coverage)] - self.assertEqual(expected, m_prepare_scalar.call_args_list) - - -class Test__metadata_prepare(tests.IrisTest): - def setUp(self): - self.src_cube = sentinel.src_cube - self.src_category_local = sentinel.src_category_local - self.src_dim_coverage = sentinel.src_dim_coverage - self.src_aux_coverage = mock.Mock( - common_items_aux=sentinel.src_aux_coverage_common_items_aux, - common_items_scalar=sentinel.src_aux_coverage_common_items_scalar, - ) - self.tgt_cube = sentinel.tgt_cube - self.tgt_category_local = sentinel.tgt_category_local - self.tgt_dim_coverage = sentinel.tgt_dim_coverage - self.tgt_aux_coverage = mock.Mock( - common_items_aux=sentinel.tgt_aux_coverage_common_items_aux, - common_items_scalar=sentinel.tgt_aux_coverage_common_items_scalar, + assert m_prepare_dim.call_count == 1 + expected = [mocker.call(src_dim_coverage, tgt_dim_coverage)] + assert m_prepare_dim.call_args_list == expected + assert m_prepare_aux.call_count == 1 + expected = [mocker.call(src_aux_coverage, tgt_aux_coverage)] + assert m_prepare_aux.call_args_list == expected + assert m_prepare_scalar.call_count == 1 + expected = [mocker.call(src_aux_coverage, tgt_aux_coverage)] + assert m_prepare_scalar.call_args_list == expected + + +class Test__metadata_prepare: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.src_cube = mocker.sentinel.src_cube + self.src_category_local = mocker.sentinel.src_category_local + self.src_dim_coverage = mocker.sentinel.src_dim_coverage + self.src_aux_coverage = mocker.Mock( + common_items_aux=mocker.sentinel.src_aux_coverage_common_items_aux, + common_items_scalar=mocker.sentinel.src_aux_coverage_common_items_scalar, + ) + self.tgt_cube = mocker.sentinel.tgt_cube + self.tgt_category_local = mocker.sentinel.tgt_category_local + self.tgt_dim_coverage = mocker.sentinel.tgt_dim_coverage + self.tgt_aux_coverage = mocker.Mock( + common_items_aux=mocker.sentinel.tgt_aux_coverage_common_items_aux, + common_items_scalar=mocker.sentinel.tgt_aux_coverage_common_items_scalar, ) self.resolve = Resolve() root = "iris.common.resolve.Resolve" - self.m_prepare_common_dim_payload = self.patch( + self.m_prepare_common_dim_payload = mocker.patch( f"{root}._prepare_common_dim_payload" ) - self.m_prepare_common_aux_payload = self.patch( + self.m_prepare_common_aux_payload = mocker.patch( f"{root}._prepare_common_aux_payload" ) - self.m_prepare_local_payload = self.patch(f"{root}._prepare_local_payload") - self.m_prepare_factory_payload = self.patch(f"{root}._prepare_factory_payload") + self.m_prepare_local_payload = mocker.patch(f"{root}._prepare_local_payload") + self.m_prepare_factory_payload = mocker.patch( + f"{root}._prepare_factory_payload" + ) def _check(self): - self.assertIsNone(self.resolve.prepared_category) - self.assertIsNone(self.resolve.prepared_factories) + assert self.resolve.prepared_category is None + assert self.resolve.prepared_factories is None self.resolve._metadata_prepare() expected = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) - self.assertEqual(expected, self.resolve.prepared_category) - self.assertEqual([], self.resolve.prepared_factories) - self.assertEqual(1, self.m_prepare_common_dim_payload.call_count) + assert self.resolve.prepared_category == expected + assert self.resolve.prepared_factories == [] + assert self.m_prepare_common_dim_payload.call_count == 1 expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_prepare_common_dim_payload.call_args_list) - self.assertEqual(2, self.m_prepare_common_aux_payload.call_count) + assert self.m_prepare_common_dim_payload.call_args_list == expected + assert self.m_prepare_common_aux_payload.call_count == 2 expected = [ mock.call( self.src_aux_coverage.common_items_aux, @@ -3792,8 +3825,8 @@ def _check(self): ignore_mismatch=True, ), ] - self.assertEqual(expected, self.m_prepare_common_aux_payload.call_args_list) - self.assertEqual(1, self.m_prepare_local_payload.call_count) + assert self.m_prepare_common_aux_payload.call_args_list == expected + assert self.m_prepare_local_payload.call_count == 1 expected = [ mock.call( self.src_dim_coverage, @@ -3802,13 +3835,13 @@ def _check(self): self.tgt_aux_coverage, ) ] - self.assertEqual(expected, self.m_prepare_local_payload.call_args_list) - self.assertEqual(2, self.m_prepare_factory_payload.call_count) + assert self.m_prepare_local_payload.call_args_list == expected + assert self.m_prepare_factory_payload.call_count == 2 expected = [ mock.call(self.tgt_cube, self.tgt_category_local, from_src=False), mock.call(self.src_cube, self.src_category_local), ] - self.assertEqual(expected, self.m_prepare_factory_payload.call_args_list) + assert self.m_prepare_factory_payload.call_args_list == expected def test_map_rhs_to_lhs__true(self): self.resolve.map_rhs_to_lhs = True @@ -3835,8 +3868,9 @@ def test_map_rhs_to_lhs__false(self): self._check() -class Test__prepare_factory_payload(tests.IrisTest): - def setUp(self): +class Test__prepare_factory_payload: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.Cube = namedtuple("Cube", ["aux_factories"]) self.Coord = namedtuple("Coord", ["metadata"]) self.Factory_T1 = namedtuple( @@ -3850,16 +3884,16 @@ def setUp(self): self.resolve = Resolve() self.resolve.map_rhs_to_lhs = True self.resolve.prepared_factories = [] - self.m_get_prepared_item = self.patch( + self.m_get_prepared_item = mocker.patch( "iris.common.resolve.Resolve._get_prepared_item" ) - self.category_local = sentinel.category_local - self.from_src = sentinel.from_src + self.category_local = mocker.sentinel.category_local + self.from_src = mocker.sentinel.from_src def test_no_factory(self): cube = self.Cube(aux_factories=[]) self.resolve._prepare_factory_payload(cube, self.category_local) - self.assertEqual(0, len(self.resolve.prepared_factories)) + assert len(self.resolve.prepared_factories) == 0 def test_skip_factory__already_prepared(self): aux_factory = self.Factory_T1(dependencies=None) @@ -3871,12 +3905,12 @@ def test_skip_factory__already_prepared(self): ] self.resolve.prepared_factories.extend(prepared_factories) self.resolve._prepare_factory_payload(cube, self.category_local) - self.assertEqual(prepared_factories, self.resolve.prepared_factories) + assert self.resolve.prepared_factories == prepared_factories - def test_factory__dependency_already_prepared(self): - coord_a = self.Coord(metadata=sentinel.coord_a_metadata) - coord_b = self.Coord(metadata=sentinel.coord_b_metadata) - coord_c = self.Coord(metadata=sentinel.coord_c_metadata) + def test_factory__dependency_already_prepared(self, mocker): + coord_a = self.Coord(metadata=mocker.sentinel.coord_a_metadata) + coord_b = self.Coord(metadata=mocker.sentinel.coord_b_metadata) + coord_c = self.Coord(metadata=mocker.sentinel.coord_c_metadata) side_effect = (coord_a, coord_b, coord_c) self.m_get_prepared_item.side_effect = side_effect dependencies = dict(name_a=coord_a, name_b=coord_b, name_c=coord_c) @@ -3886,7 +3920,7 @@ def test_factory__dependency_already_prepared(self): self.resolve._prepare_factory_payload( cube, self.category_local, from_src=self.from_src ) - self.assertEqual(1, len(self.resolve.prepared_factories)) + assert len(self.resolve.prepared_factories) == 1 prepared_dependencies = { name: coord.metadata for name, coord in dependencies.items() } @@ -3895,21 +3929,21 @@ def test_factory__dependency_already_prepared(self): container=self.container_T1, dependencies=prepared_dependencies ) ] - self.assertEqual(expected, self.resolve.prepared_factories) - self.assertEqual(len(side_effect), self.m_get_prepared_item.call_count) + assert self.resolve.prepared_factories == expected + assert self.m_get_prepared_item.call_count == len(side_effect) expected = [ - mock.call(coord_a.metadata, self.category_local, from_src=self.from_src), - mock.call(coord_b.metadata, self.category_local, from_src=self.from_src), - mock.call(coord_c.metadata, self.category_local, from_src=self.from_src), + mocker.call(coord_a.metadata, self.category_local, from_src=self.from_src), + mocker.call(coord_b.metadata, self.category_local, from_src=self.from_src), + mocker.call(coord_c.metadata, self.category_local, from_src=self.from_src), ] actual = self.m_get_prepared_item.call_args_list for call in expected: - self.assertIn(call, actual) + assert call in actual - def test_factory__dependency_local_not_prepared(self): - coord_a = self.Coord(metadata=sentinel.coord_a_metadata) - coord_b = self.Coord(metadata=sentinel.coord_b_metadata) - coord_c = self.Coord(metadata=sentinel.coord_c_metadata) + def test_factory__dependency_local_not_prepared(self, mocker): + coord_a = self.Coord(metadata=mocker.sentinel.coord_a_metadata) + coord_b = self.Coord(metadata=mocker.sentinel.coord_b_metadata) + coord_c = self.Coord(metadata=mocker.sentinel.coord_c_metadata) side_effect = (None, coord_a, None, coord_b, None, coord_c) self.m_get_prepared_item.side_effect = side_effect dependencies = dict(name_a=coord_a, name_b=coord_b, name_c=coord_c) @@ -3919,7 +3953,7 @@ def test_factory__dependency_local_not_prepared(self): self.resolve._prepare_factory_payload( cube, self.category_local, from_src=self.from_src ) - self.assertEqual(1, len(self.resolve.prepared_factories)) + assert len(self.resolve.prepared_factories) == 1 prepared_dependencies = { name: coord.metadata for name, coord in dependencies.items() } @@ -3928,25 +3962,25 @@ def test_factory__dependency_local_not_prepared(self): container=self.container_T1, dependencies=prepared_dependencies ) ] - self.assertEqual(expected, self.resolve.prepared_factories) - self.assertEqual(len(side_effect), self.m_get_prepared_item.call_count) + assert self.resolve.prepared_factories == expected + assert self.m_get_prepared_item.call_count == len(side_effect) expected = [ - mock.call(coord_a.metadata, self.category_local, from_src=self.from_src), - mock.call(coord_b.metadata, self.category_local, from_src=self.from_src), - mock.call(coord_c.metadata, self.category_local, from_src=self.from_src), - mock.call( + mocker.call(coord_a.metadata, self.category_local, from_src=self.from_src), + mocker.call(coord_b.metadata, self.category_local, from_src=self.from_src), + mocker.call(coord_c.metadata, self.category_local, from_src=self.from_src), + mocker.call( coord_a.metadata, self.category_local, from_src=self.from_src, from_local=True, ), - mock.call( + mocker.call( coord_b.metadata, self.category_local, from_src=self.from_src, from_local=True, ), - mock.call( + mocker.call( coord_c.metadata, self.category_local, from_src=self.from_src, @@ -3955,12 +3989,12 @@ def test_factory__dependency_local_not_prepared(self): ] actual = self.m_get_prepared_item.call_args_list for call in expected: - self.assertIn(call, actual) + assert call in actual - def test_factory__dependency_not_found(self): - coord_a = self.Coord(metadata=sentinel.coord_a_metadata) - coord_b = self.Coord(metadata=sentinel.coord_b_metadata) - coord_c = self.Coord(metadata=sentinel.coord_c_metadata) + def test_factory__dependency_not_found(self, mocker): + coord_a = self.Coord(metadata=mocker.sentinel.coord_a_metadata) + coord_b = self.Coord(metadata=mocker.sentinel.coord_b_metadata) + coord_c = self.Coord(metadata=mocker.sentinel.coord_c_metadata) side_effect = (None, None) self.m_get_prepared_item.side_effect = side_effect dependencies = dict(name_a=coord_a, name_b=coord_b, name_c=coord_c) @@ -3970,25 +4004,25 @@ def test_factory__dependency_not_found(self): self.resolve._prepare_factory_payload( cube, self.category_local, from_src=self.from_src ) - self.assertEqual(0, len(self.resolve.prepared_factories)) - self.assertEqual(len(side_effect), self.m_get_prepared_item.call_count) + assert len(self.resolve.prepared_factories) == 0 + assert self.m_get_prepared_item.call_count == len(side_effect) expected = [ - mock.call(coord_a.metadata, self.category_local, from_src=self.from_src), - mock.call(coord_b.metadata, self.category_local, from_src=self.from_src), - mock.call(coord_c.metadata, self.category_local, from_src=self.from_src), - mock.call( + mocker.call(coord_a.metadata, self.category_local, from_src=self.from_src), + mocker.call(coord_b.metadata, self.category_local, from_src=self.from_src), + mocker.call(coord_c.metadata, self.category_local, from_src=self.from_src), + mocker.call( coord_a.metadata, self.category_local, from_src=self.from_src, from_local=True, ), - mock.call( + mocker.call( coord_b.metadata, self.category_local, from_src=self.from_src, from_local=True, ), - mock.call( + mocker.call( coord_c.metadata, self.category_local, from_src=self.from_src, @@ -3997,15 +4031,16 @@ def test_factory__dependency_not_found(self): ] actual = self.m_get_prepared_item.call_args_list for call in actual: - self.assertIn(call, expected) + assert call in expected -class Test__get_prepared_item(tests.IrisTest): - def setUp(self): +class Test__get_prepared_item: + @pytest.fixture(autouse=True) + def _setup(self, mocker): PreparedItem = namedtuple("PreparedItem", ["metadata"]) self.resolve = Resolve() - self.prepared_dim_metadata_src = sentinel.prepared_dim_metadata_src - self.prepared_dim_metadata_tgt = sentinel.prepared_dim_metadata_tgt + self.prepared_dim_metadata_src = mocker.sentinel.prepared_dim_metadata_src + self.prepared_dim_metadata_tgt = mocker.sentinel.prepared_dim_metadata_tgt self.prepared_items_dim = PreparedItem( metadata=_PreparedMetadata( combined=None, @@ -4013,8 +4048,8 @@ def setUp(self): tgt=self.prepared_dim_metadata_tgt, ) ) - self.prepared_aux_metadata_src = sentinel.prepared_aux_metadata_src - self.prepared_aux_metadata_tgt = sentinel.prepared_aux_metadata_tgt + self.prepared_aux_metadata_src = mocker.sentinel.prepared_aux_metadata_src + self.prepared_aux_metadata_tgt = mocker.sentinel.prepared_aux_metadata_tgt self.prepared_items_aux = PreparedItem( metadata=_PreparedMetadata( combined=None, @@ -4022,8 +4057,8 @@ def setUp(self): tgt=self.prepared_aux_metadata_tgt, ) ) - self.prepared_scalar_metadata_src = sentinel.prepared_scalar_metadata_src - self.prepared_scalar_metadata_tgt = sentinel.prepared_scalar_metadata_tgt + self.prepared_scalar_metadata_src = mocker.sentinel.prepared_scalar_metadata_src + self.prepared_scalar_metadata_tgt = mocker.sentinel.prepared_scalar_metadata_tgt self.prepared_items_scalar = PreparedItem( metadata=_PreparedMetadata( combined=None, @@ -4037,13 +4072,13 @@ def setUp(self): items_scalar=[self.prepared_items_scalar], ) self.resolve.mapping = {0: 10} - self.m_create_prepared_item = self.patch( + self.m_create_prepared_item = mocker.patch( "iris.common.resolve.Resolve._create_prepared_item" ) - self.local_dim_metadata = sentinel.local_dim_metadata - self.local_aux_metadata = sentinel.local_aux_metadata - self.local_scalar_metadata = sentinel.local_scalar_metadata - self.local_coord = sentinel.local_coord + self.local_dim_metadata = mocker.sentinel.local_dim_metadata + self.local_aux_metadata = mocker.sentinel.local_aux_metadata + self.local_scalar_metadata = mocker.sentinel.local_scalar_metadata + self.local_coord = mocker.sentinel.local_coord self.local_coord_dims = (0,) self.local_items_dim = _Item( metadata=self.local_dim_metadata, @@ -4066,25 +4101,25 @@ def setUp(self): items_scalar=[self.local_items_scalar], ) - def test_missing_prepared_coord__from_src(self): - metadata = sentinel.missing + def test_missing_prepared_coord__from_src(self, mocker): + metadata = mocker.sentinel.missing category_local = None result = self.resolve._get_prepared_item(metadata, category_local) - self.assertIsNone(result) + assert result is None - def test_missing_prepared_coord__from_tgt(self): - metadata = sentinel.missing + def test_missing_prepared_coord__from_tgt(self, mocker): + metadata = mocker.sentinel.missing category_local = None result = self.resolve._get_prepared_item( metadata, category_local, from_src=False ) - self.assertIsNone(result) + assert result is None def test_get_prepared_dim_coord__from_src(self): metadata = self.prepared_dim_metadata_src category_local = None result = self.resolve._get_prepared_item(metadata, category_local) - self.assertEqual(self.prepared_items_dim, result) + assert result == self.prepared_items_dim def test_get_prepared_dim_coord__from_tgt(self): metadata = self.prepared_dim_metadata_tgt @@ -4092,13 +4127,13 @@ def test_get_prepared_dim_coord__from_tgt(self): result = self.resolve._get_prepared_item( metadata, category_local, from_src=False ) - self.assertEqual(self.prepared_items_dim, result) + assert result == self.prepared_items_dim def test_get_prepared_aux_coord__from_src(self): metadata = self.prepared_aux_metadata_src category_local = None result = self.resolve._get_prepared_item(metadata, category_local) - self.assertEqual(self.prepared_items_aux, result) + assert result == self.prepared_items_aux def test_get_prepared_aux_coord__from_tgt(self): metadata = self.prepared_aux_metadata_tgt @@ -4106,13 +4141,13 @@ def test_get_prepared_aux_coord__from_tgt(self): result = self.resolve._get_prepared_item( metadata, category_local, from_src=False ) - self.assertEqual(self.prepared_items_aux, result) + assert result == self.prepared_items_aux def test_get_prepared_scalar_coord__from_src(self): metadata = self.prepared_scalar_metadata_src category_local = None result = self.resolve._get_prepared_item(metadata, category_local) - self.assertEqual(self.prepared_items_scalar, result) + assert result == self.prepared_items_scalar def test_get_prepared_scalar_coord__from_tgt(self): metadata = self.prepared_scalar_metadata_tgt @@ -4120,163 +4155,164 @@ def test_get_prepared_scalar_coord__from_tgt(self): result = self.resolve._get_prepared_item( metadata, category_local, from_src=False ) - self.assertEqual(self.prepared_items_scalar, result) + assert result == self.prepared_items_scalar - def test_missing_local_coord__from_src(self): - metadata = sentinel.missing + def test_missing_local_coord__from_src(self, mocker): + metadata = mocker.sentinel.missing result = self.resolve._get_prepared_item( metadata, self.category_local, from_local=True ) - self.assertIsNone(result) + assert result is None - def test_missing_local_coord__from_tgt(self): - metadata = sentinel.missing + def test_missing_local_coord__from_tgt(self, mocker): + metadata = mocker.sentinel.missing result = self.resolve._get_prepared_item( metadata, self.category_local, from_src=False, from_local=True ) - self.assertIsNone(result) + assert result is None - def test_get_local_dim_coord__from_src(self): - created_local_item = sentinel.created_local_item + def test_get_local_dim_coord__from_src(self, mocker): + created_local_item = mocker.sentinel.created_local_item self.m_create_prepared_item.return_value = created_local_item metadata = self.local_dim_metadata result = self.resolve._get_prepared_item( metadata, self.category_local, from_local=True ) expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_dim)) - self.assertEqual(expected, self.resolve.prepared_category.items_dim[1]) - self.assertEqual(1, self.m_create_prepared_item.call_count) + assert result == expected + assert len(self.resolve.prepared_category.items_dim) == 2 + assert self.resolve.prepared_category.items_dim[1] == expected + assert self.m_create_prepared_item.call_count == 1 dims = (self.resolve.mapping[self.local_coord_dims[0]],) expected = [ - mock.call( + mocker.call( self.local_coord, dims, src_metadata=metadata, tgt_metadata=None, ) ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.m_create_prepared_item.call_args_list == expected - def test_get_local_dim_coord__from_tgt(self): - created_local_item = sentinel.created_local_item + def test_get_local_dim_coord__from_tgt(self, mocker): + created_local_item = mocker.sentinel.created_local_item self.m_create_prepared_item.return_value = created_local_item metadata = self.local_dim_metadata result = self.resolve._get_prepared_item( metadata, self.category_local, from_src=False, from_local=True ) expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_dim)) - self.assertEqual(expected, self.resolve.prepared_category.items_dim[1]) - self.assertEqual(1, self.m_create_prepared_item.call_count) + assert result == expected + assert len(self.resolve.prepared_category.items_dim) == 2 + assert self.resolve.prepared_category.items_dim[1] == expected + assert self.m_create_prepared_item.call_count == 1 dims = self.local_coord_dims expected = [ - mock.call( + mocker.call( self.local_coord, dims, src_metadata=None, tgt_metadata=metadata, ) ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.m_create_prepared_item.call_args_list == expected - def test_get_local_aux_coord__from_src(self): - created_local_item = sentinel.created_local_item + def test_get_local_aux_coord__from_src(self, mocker): + created_local_item = mocker.sentinel.created_local_item self.m_create_prepared_item.return_value = created_local_item metadata = self.local_aux_metadata result = self.resolve._get_prepared_item( metadata, self.category_local, from_local=True ) expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_aux)) - self.assertEqual(expected, self.resolve.prepared_category.items_aux[1]) - self.assertEqual(1, self.m_create_prepared_item.call_count) + assert result == expected + assert len(self.resolve.prepared_category.items_aux) == 2 + assert self.resolve.prepared_category.items_aux[1] == expected + assert self.m_create_prepared_item.call_count == 1 dims = (self.resolve.mapping[self.local_coord_dims[0]],) expected = [ - mock.call( + mocker.call( self.local_coord, dims, src_metadata=metadata, tgt_metadata=None, ) ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.m_create_prepared_item.call_args_list == expected - def test_get_local_aux_coord__from_tgt(self): - created_local_item = sentinel.created_local_item + def test_get_local_aux_coord__from_tgt(self, mocker): + created_local_item = mocker.sentinel.created_local_item self.m_create_prepared_item.return_value = created_local_item metadata = self.local_aux_metadata result = self.resolve._get_prepared_item( metadata, self.category_local, from_src=False, from_local=True ) expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_aux)) - self.assertEqual(expected, self.resolve.prepared_category.items_aux[1]) - self.assertEqual(1, self.m_create_prepared_item.call_count) + assert result == expected + assert len(self.resolve.prepared_category.items_aux) == 2 + assert self.resolve.prepared_category.items_aux[1] == expected + assert self.m_create_prepared_item.call_count == 1 dims = self.local_coord_dims expected = [ - mock.call( + mocker.call( self.local_coord, dims, src_metadata=None, tgt_metadata=metadata, ) ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.m_create_prepared_item.call_args_list == expected - def test_get_local_scalar_coord__from_src(self): - created_local_item = sentinel.created_local_item + def test_get_local_scalar_coord__from_src(self, mocker): + created_local_item = mocker.sentinel.created_local_item self.m_create_prepared_item.return_value = created_local_item metadata = self.local_scalar_metadata result = self.resolve._get_prepared_item( metadata, self.category_local, from_local=True ) expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_scalar)) - self.assertEqual(expected, self.resolve.prepared_category.items_scalar[1]) - self.assertEqual(1, self.m_create_prepared_item.call_count) + assert result == expected + assert len(self.resolve.prepared_category.items_scalar) == 2 + assert self.resolve.prepared_category.items_scalar[1] == expected + assert self.m_create_prepared_item.call_count == 1 dims = (self.resolve.mapping[self.local_coord_dims[0]],) expected = [ - mock.call( + mocker.call( self.local_coord, dims, src_metadata=metadata, tgt_metadata=None, ) ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.m_create_prepared_item.call_args_list == expected - def test_get_local_scalar_coord__from_tgt(self): - created_local_item = sentinel.created_local_item + def test_get_local_scalar_coord__from_tgt(self, mocker): + created_local_item = mocker.sentinel.created_local_item self.m_create_prepared_item.return_value = created_local_item metadata = self.local_scalar_metadata result = self.resolve._get_prepared_item( metadata, self.category_local, from_src=False, from_local=True ) expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_scalar)) - self.assertEqual(expected, self.resolve.prepared_category.items_scalar[1]) - self.assertEqual(1, self.m_create_prepared_item.call_count) + assert result == expected + assert len(self.resolve.prepared_category.items_scalar) == 2 + assert self.resolve.prepared_category.items_scalar[1] == expected + assert self.m_create_prepared_item.call_count == 1 dims = self.local_coord_dims expected = [ - mock.call( + mocker.call( self.local_coord, dims, src_metadata=None, tgt_metadata=metadata, ) ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) + assert self.m_create_prepared_item.call_args_list == expected -class Test_cube(tests.IrisTest): - def setUp(self): +class Test_cube: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.shape = (2, 3) self.data = np.zeros(np.multiply(*self.shape), dtype=np.int8).reshape( self.shape @@ -4299,25 +4335,25 @@ def setUp(self): rhs_cube = Cube(self.data) rhs_cube.metadata = self.cube_metadata self.resolve.rhs_cube = rhs_cube - self.m_add_dim_coord = self.patch("iris.cube.Cube.add_dim_coord") - self.m_add_aux_coord = self.patch("iris.cube.Cube.add_aux_coord") - self.m_add_aux_factory = self.patch("iris.cube.Cube.add_aux_factory") - self.m_coord = self.patch("iris.cube.Cube.coord") + self.m_add_dim_coord = mocker.patch("iris.cube.Cube.add_dim_coord") + self.m_add_aux_coord = mocker.patch("iris.cube.Cube.add_aux_coord") + self.m_add_aux_factory = mocker.patch("iris.cube.Cube.add_aux_factory") + self.m_coord = mocker.patch("iris.cube.Cube.coord") # # prepared coordinates # prepared_category = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) # prepared dim coordinates self.prepared_dim_0_metadata = _PreparedMetadata( - combined=sentinel.prepared_dim_0_metadata_combined, + combined=mocker.sentinel.prepared_dim_0_metadata_combined, src=None, tgt=None, ) - self.prepared_dim_0_points = sentinel.prepared_dim_0_points - self.prepared_dim_0_bounds = sentinel.prepared_dim_0_bounds + self.prepared_dim_0_points = mocker.sentinel.prepared_dim_0_points + self.prepared_dim_0_bounds = mocker.sentinel.prepared_dim_0_bounds self.prepared_dim_0_dims = (0,) - self.prepared_dim_0_coord = mock.Mock(metadata=None) - self.prepared_dim_0_container = mock.Mock( + self.prepared_dim_0_coord = mocker.Mock(metadata=None) + self.prepared_dim_0_container = mocker.Mock( return_value=self.prepared_dim_0_coord ) self.prepared_dim_0 = _PreparedItem( @@ -4329,15 +4365,15 @@ def setUp(self): ) prepared_category.items_dim.append(self.prepared_dim_0) self.prepared_dim_1_metadata = _PreparedMetadata( - combined=sentinel.prepared_dim_1_metadata_combined, + combined=mocker.sentinel.prepared_dim_1_metadata_combined, src=None, tgt=None, ) - self.prepared_dim_1_points = sentinel.prepared_dim_1_points - self.prepared_dim_1_bounds = sentinel.prepared_dim_1_bounds + self.prepared_dim_1_points = mocker.sentinel.prepared_dim_1_points + self.prepared_dim_1_bounds = mocker.sentinel.prepared_dim_1_bounds self.prepared_dim_1_dims = (1,) - self.prepared_dim_1_coord = mock.Mock(metadata=None) - self.prepared_dim_1_container = mock.Mock( + self.prepared_dim_1_coord = mocker.Mock(metadata=None) + self.prepared_dim_1_container = mocker.Mock( return_value=self.prepared_dim_1_coord ) self.prepared_dim_1 = _PreparedItem( @@ -4351,15 +4387,15 @@ def setUp(self): # prepared auxiliary coordinates self.prepared_aux_0_metadata = _PreparedMetadata( - combined=sentinel.prepared_aux_0_metadata_combined, + combined=mocker.sentinel.prepared_aux_0_metadata_combined, src=None, tgt=None, ) - self.prepared_aux_0_points = sentinel.prepared_aux_0_points - self.prepared_aux_0_bounds = sentinel.prepared_aux_0_bounds + self.prepared_aux_0_points = mocker.sentinel.prepared_aux_0_points + self.prepared_aux_0_bounds = mocker.sentinel.prepared_aux_0_bounds self.prepared_aux_0_dims = (0,) - self.prepared_aux_0_coord = mock.Mock(metadata=None) - self.prepared_aux_0_container = mock.Mock( + self.prepared_aux_0_coord = mocker.Mock(metadata=None) + self.prepared_aux_0_container = mocker.Mock( return_value=self.prepared_aux_0_coord ) self.prepared_aux_0 = _PreparedItem( @@ -4371,15 +4407,15 @@ def setUp(self): ) prepared_category.items_aux.append(self.prepared_aux_0) self.prepared_aux_1_metadata = _PreparedMetadata( - combined=sentinel.prepared_aux_1_metadata_combined, + combined=mocker.sentinel.prepared_aux_1_metadata_combined, src=None, tgt=None, ) - self.prepared_aux_1_points = sentinel.prepared_aux_1_points - self.prepared_aux_1_bounds = sentinel.prepared_aux_1_bounds + self.prepared_aux_1_points = mocker.sentinel.prepared_aux_1_points + self.prepared_aux_1_bounds = mocker.sentinel.prepared_aux_1_bounds self.prepared_aux_1_dims = (1,) - self.prepared_aux_1_coord = mock.Mock(metadata=None) - self.prepared_aux_1_container = mock.Mock( + self.prepared_aux_1_coord = mocker.Mock(metadata=None) + self.prepared_aux_1_container = mocker.Mock( return_value=self.prepared_aux_1_coord ) self.prepared_aux_1 = _PreparedItem( @@ -4393,15 +4429,15 @@ def setUp(self): # prepare scalar coordinates self.prepared_scalar_0_metadata = _PreparedMetadata( - combined=sentinel.prepared_scalar_0_metadata_combined, + combined=mocker.sentinel.prepared_scalar_0_metadata_combined, src=None, tgt=None, ) - self.prepared_scalar_0_points = sentinel.prepared_scalar_0_points - self.prepared_scalar_0_bounds = sentinel.prepared_scalar_0_bounds + self.prepared_scalar_0_points = mocker.sentinel.prepared_scalar_0_points + self.prepared_scalar_0_bounds = mocker.sentinel.prepared_scalar_0_bounds self.prepared_scalar_0_dims = () - self.prepared_scalar_0_coord = mock.Mock(metadata=None) - self.prepared_scalar_0_container = mock.Mock( + self.prepared_scalar_0_coord = mocker.Mock(metadata=None) + self.prepared_scalar_0_container = mocker.Mock( return_value=self.prepared_scalar_0_coord ) self.prepared_scalar_0 = _PreparedItem( @@ -4413,15 +4449,15 @@ def setUp(self): ) prepared_category.items_scalar.append(self.prepared_scalar_0) self.prepared_scalar_1_metadata = _PreparedMetadata( - combined=sentinel.prepared_scalar_1_metadata_combined, + combined=mocker.sentinel.prepared_scalar_1_metadata_combined, src=None, tgt=None, ) - self.prepared_scalar_1_points = sentinel.prepared_scalar_1_points - self.prepared_scalar_1_bounds = sentinel.prepared_scalar_1_bounds + self.prepared_scalar_1_points = mocker.sentinel.prepared_scalar_1_points + self.prepared_scalar_1_bounds = mocker.sentinel.prepared_scalar_1_bounds self.prepared_scalar_1_dims = () - self.prepared_scalar_1_coord = mock.Mock(metadata=None) - self.prepared_scalar_1_container = mock.Mock( + self.prepared_scalar_1_coord = mocker.Mock(metadata=None) + self.prepared_scalar_1_container = mocker.Mock( return_value=self.prepared_scalar_1_coord ) self.prepared_scalar_1 = _PreparedItem( @@ -4436,20 +4472,20 @@ def setUp(self): # prepared factories # prepared_factories = [] - self.aux_factory = sentinel.aux_factory - self.prepared_factory_container = mock.Mock(return_value=self.aux_factory) + self.aux_factory = mocker.sentinel.aux_factory + self.prepared_factory_container = mocker.Mock(return_value=self.aux_factory) self.prepared_factory_metadata_a = _PreparedMetadata( - combined=sentinel.prepared_factory_metadata_a_combined, + combined=mocker.sentinel.prepared_factory_metadata_a_combined, src=None, tgt=None, ) self.prepared_factory_metadata_b = _PreparedMetadata( - combined=sentinel.prepared_factory_metadata_b_combined, + combined=mocker.sentinel.prepared_factory_metadata_b_combined, src=None, tgt=None, ) self.prepared_factory_metadata_c = _PreparedMetadata( - combined=sentinel.prepared_factory_metadata_c_combined, + combined=mocker.sentinel.prepared_factory_metadata_c_combined, src=None, tgt=None, ) @@ -4464,125 +4500,121 @@ def setUp(self): ) prepared_factories.append(self.prepared_factory) self.prepared_factory_side_effect = ( - sentinel.prepared_factory_coord_a, - sentinel.prepared_factory_coord_b, - sentinel.prepared_factory_coord_c, + mocker.sentinel.prepared_factory_coord_a, + mocker.sentinel.prepared_factory_coord_b, + mocker.sentinel.prepared_factory_coord_c, ) self.m_coord.side_effect = self.prepared_factory_side_effect self.resolve.prepared_category = prepared_category self.resolve.prepared_factories = prepared_factories # Required to stop mock 'containers' failing in an 'issubclass' call. - self.patch("iris.common.resolve.issubclass", mock.Mock(return_value=False)) + mocker.patch("iris.common.resolve.issubclass", mocker.Mock(return_value=False)) def test_no_resolved_shape(self): self.resolve._broadcast_shape = None data = None emsg = "Cannot resolve resultant cube, as no candidate cubes have been provided" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = self.resolve.cube(data) def test_bad_data_shape(self): emsg = "Cannot resolve resultant cube, as the provided data must have shape" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = self.resolve.cube(self.bad_data) def test_bad_data_shape__inplace(self): self.resolve.lhs_cube = Cube(self.bad_data) emsg = "Cannot resolve resultant cube in-place" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): _ = self.resolve.cube(self.data, in_place=True) def _check(self): # check dim coordinate 0 - self.assertEqual(1, self.prepared_dim_0.container.call_count) + assert self.prepared_dim_0.container.call_count == 1 expected = [ mock.call(self.prepared_dim_0_points, bounds=self.prepared_dim_0_bounds) ] - self.assertEqual(expected, self.prepared_dim_0.container.call_args_list) - self.assertEqual( - self.prepared_dim_0_coord.metadata, - self.prepared_dim_0_metadata.combined, + assert self.prepared_dim_0.container.call_args_list == expected + assert ( + self.prepared_dim_0_metadata.combined == self.prepared_dim_0_coord.metadata ) # check dim coordinate 1 - self.assertEqual(1, self.prepared_dim_1.container.call_count) + assert self.prepared_dim_1.container.call_count == 1 expected = [ mock.call(self.prepared_dim_1_points, bounds=self.prepared_dim_1_bounds) ] - self.assertEqual(expected, self.prepared_dim_1.container.call_args_list) - self.assertEqual( - self.prepared_dim_1_coord.metadata, - self.prepared_dim_1_metadata.combined, + assert self.prepared_dim_1.container.call_args_list == expected + assert ( + self.prepared_dim_1_metadata.combined == self.prepared_dim_1_coord.metadata ) # check add_dim_coord - self.assertEqual(2, self.m_add_dim_coord.call_count) + assert self.m_add_dim_coord.call_count == 2 expected = [ mock.call(self.prepared_dim_0_coord, self.prepared_dim_0_dims), mock.call(self.prepared_dim_1_coord, self.prepared_dim_1_dims), ] - self.assertEqual(expected, self.m_add_dim_coord.call_args_list) + assert self.m_add_dim_coord.call_args_list == expected # check aux coordinate 0 - self.assertEqual(1, self.prepared_aux_0.container.call_count) + assert self.prepared_aux_0.container.call_count == 1 expected = [ mock.call(self.prepared_aux_0_points, bounds=self.prepared_aux_0_bounds) ] - self.assertEqual(expected, self.prepared_aux_0.container.call_args_list) - self.assertEqual( - self.prepared_aux_0_coord.metadata, - self.prepared_aux_0_metadata.combined, + assert self.prepared_aux_0.container.call_args_list == expected + assert ( + self.prepared_aux_0_metadata.combined == self.prepared_aux_0_coord.metadata ) # check aux coordinate 1 - self.assertEqual(1, self.prepared_aux_1.container.call_count) + assert self.prepared_aux_1.container.call_count == 1 expected = [ mock.call(self.prepared_aux_1_points, bounds=self.prepared_aux_1_bounds) ] - self.assertEqual(expected, self.prepared_aux_1.container.call_args_list) - self.assertEqual( - self.prepared_aux_1_coord.metadata, - self.prepared_aux_1_metadata.combined, + assert self.prepared_aux_1.container.call_args_list == expected + assert ( + self.prepared_aux_1_metadata.combined == self.prepared_aux_1_coord.metadata ) # check scalar coordinate 0 - self.assertEqual(1, self.prepared_scalar_0.container.call_count) + assert self.prepared_scalar_0.container.call_count == 1 expected = [ mock.call( self.prepared_scalar_0_points, bounds=self.prepared_scalar_0_bounds, ) ] - self.assertEqual(expected, self.prepared_scalar_0.container.call_args_list) - self.assertEqual( - self.prepared_scalar_0_coord.metadata, - self.prepared_scalar_0_metadata.combined, + assert self.prepared_scalar_0.container.call_args_list == expected + assert ( + self.prepared_scalar_0_metadata.combined + == self.prepared_scalar_0_coord.metadata ) # check scalar coordinate 1 - self.assertEqual(1, self.prepared_scalar_1.container.call_count) + assert self.prepared_scalar_1.container.call_count == 1 expected = [ mock.call( self.prepared_scalar_1_points, bounds=self.prepared_scalar_1_bounds, ) ] - self.assertEqual(expected, self.prepared_scalar_1.container.call_args_list) - self.assertEqual( - self.prepared_scalar_1_coord.metadata, - self.prepared_scalar_1_metadata.combined, + assert self.prepared_scalar_1.container.call_args_list == expected + assert ( + self.prepared_scalar_1_metadata.combined + == self.prepared_scalar_1_coord.metadata ) # check add_aux_coord - self.assertEqual(4, self.m_add_aux_coord.call_count) + assert self.m_add_aux_coord.call_count == 4 expected = [ mock.call(self.prepared_aux_0_coord, self.prepared_aux_0_dims), mock.call(self.prepared_aux_1_coord, self.prepared_aux_1_dims), mock.call(self.prepared_scalar_0_coord, self.prepared_scalar_0_dims), mock.call(self.prepared_scalar_1_coord, self.prepared_scalar_1_dims), ] - self.assertEqual(expected, self.m_add_aux_coord.call_args_list) + assert self.m_add_aux_coord.call_args_list == expected # check auxiliary factories - self.assertEqual(1, self.m_add_aux_factory.call_count) + assert self.m_add_aux_factory.call_count == 1 expected = [mock.call(self.aux_factory)] - self.assertEqual(expected, self.m_add_aux_factory.call_args_list) - self.assertEqual(1, self.prepared_factory_container.call_count) + assert self.m_add_aux_factory.call_args_list == expected + assert self.prepared_factory_container.call_count == 1 expected = [ mock.call( **{ @@ -4594,27 +4626,23 @@ def _check(self): } ) ] - self.assertEqual(expected, self.prepared_factory_container.call_args_list) - self.assertEqual(3, self.m_coord.call_count) + assert self.prepared_factory_container.call_args_list == expected + assert self.m_coord.call_count == 3 expected = [ mock.call(self.prepared_factory_metadata_a.combined), mock.call(self.prepared_factory_metadata_b.combined), mock.call(self.prepared_factory_metadata_c.combined), ] - self.assertEqual(expected, self.m_coord.call_args_list) + assert self.m_coord.call_args_list == expected def test_resolve(self): result = self.resolve.cube(self.data) - self.assertEqual(self.cube_metadata, result.metadata) + assert result.metadata == self.cube_metadata self._check() - self.assertIsNot(self.resolve.lhs_cube, result) + assert self.resolve.lhs_cube is not result def test_resolve__inplace(self): result = self.resolve.cube(self.data, in_place=True) - self.assertEqual(self.cube_metadata, result.metadata) + assert result.metadata == self.cube_metadata self._check() - self.assertIs(self.resolve.lhs_cube, result) - - -if __name__ == "__main__": - tests.main() + assert self.resolve.lhs_cube is result diff --git a/lib/iris/tests/unit/concatenate/test__CubeSignature.py b/lib/iris/tests/unit/concatenate/test__CubeSignature.py index a148b6fdbd..7c8eaa4c2f 100644 --- a/lib/iris/tests/unit/concatenate/test__CubeSignature.py +++ b/lib/iris/tests/unit/concatenate/test__CubeSignature.py @@ -6,10 +6,11 @@ # import iris tests first so that some things can be initialised # before importing anything else. -import iris.tests as tests # isort:skip +from dataclasses import dataclass from cf_units import Unit import numpy as np +import pytest from iris._concatenate import _CubeSignature as CubeSignature from iris.coords import DimCoord @@ -17,65 +18,73 @@ from iris.util import new_axis -class Test__coordinate_dim_metadata_equality(tests.IrisTest): - def setUp(self): +class Test__coordinate_dim_metadata_equality: + @pytest.fixture() + def sample_data(self): + # Return a standard set of test items, wrapped in a data object + @dataclass + class SampleData: + series_inc: CubeSignature = None + series_inc_cube: Cube = None + series_dec: CubeSignature = None + series_dec_cube: Cube = None + scalar_cube: Cube = None + + data = SampleData() + nt = 10 - data = np.arange(nt, dtype=np.float32) - cube = Cube(data, standard_name="air_temperature", units="K") + cube_data = np.arange(nt, dtype=np.float32) + cube = Cube(cube_data, standard_name="air_temperature", units="K") # Temporal coordinate. t_units = Unit("hours since 1970-01-01 00:00:00", calendar="standard") t_coord = DimCoord(points=np.arange(nt), standard_name="time", units=t_units) cube.add_dim_coord(t_coord, 0) - # Increasing 1D time-series cube. - self.series_inc_cube = cube - self.series_inc = CubeSignature(self.series_inc_cube) + data.series_inc_cube = cube + data.series_inc = CubeSignature(data.series_inc_cube) # Decreasing 1D time-series cube. - self.series_dec_cube = self.series_inc_cube.copy() - self.series_dec_cube.remove_coord("time") + data.series_dec_cube = data.series_inc_cube.copy() + data.series_dec_cube.remove_coord("time") t_tmp = DimCoord( points=t_coord.points[::-1], standard_name="time", units=t_units ) - self.series_dec_cube.add_dim_coord(t_tmp, 0) - self.series_dec = CubeSignature(self.series_dec_cube) + data.series_dec_cube.add_dim_coord(t_tmp, 0) + data.series_dec = CubeSignature(data.series_dec_cube) # Scalar 0D time-series cube with scalar time coordinate. cube = Cube(0, standard_name="air_temperature", units="K") cube.add_aux_coord(DimCoord(points=nt, standard_name="time", units=t_units)) - self.scalar_cube = cube + data.scalar_cube = cube + return data - def test_scalar_non_common_axis(self): - scalar = CubeSignature(self.scalar_cube) - self.assertNotEqual(self.series_inc.dim_metadata, scalar.dim_metadata) - self.assertNotEqual(self.series_dec.dim_metadata, scalar.dim_metadata) + def test_scalar_non_common_axis(self, sample_data): + scalar = CubeSignature(sample_data.scalar_cube) + assert sample_data.series_inc.dim_metadata != scalar.dim_metadata + assert sample_data.series_dec.dim_metadata != scalar.dim_metadata - def test_1d_single_value_common_axis(self): + def test_1d_single_value_common_axis(self, sample_data): # Manually promote scalar time cube to be a 1d cube. - single = CubeSignature(new_axis(self.scalar_cube, "time")) - self.assertEqual(self.series_inc.dim_metadata, single.dim_metadata) - self.assertEqual(self.series_dec.dim_metadata, single.dim_metadata) + single = CubeSignature(new_axis(sample_data.scalar_cube, "time")) + assert sample_data.series_inc.dim_metadata == single.dim_metadata + assert sample_data.series_dec.dim_metadata == single.dim_metadata - def test_increasing_common_axis(self): - series_inc = self.series_inc - series_dec = self.series_dec - self.assertEqual(series_inc.dim_metadata, series_inc.dim_metadata) - self.assertNotEqual(series_inc.dim_metadata, series_dec.dim_metadata) + def test_increasing_common_axis(self, sample_data): + series_inc = sample_data.series_inc + series_dec = sample_data.series_dec + assert series_inc.dim_metadata == series_inc.dim_metadata + assert series_inc.dim_metadata != series_dec.dim_metadata - def test_decreasing_common_axis(self): - series_inc = self.series_inc - series_dec = self.series_dec - self.assertNotEqual(series_dec.dim_metadata, series_inc.dim_metadata) - self.assertEqual(series_dec.dim_metadata, series_dec.dim_metadata) + def test_decreasing_common_axis(self, sample_data): + series_inc = sample_data.series_inc + series_dec = sample_data.series_dec + assert series_dec.dim_metadata != series_inc.dim_metadata + assert series_dec.dim_metadata == series_dec.dim_metadata - def test_circular(self): - series_inc = self.series_inc - circular_cube = self.series_inc_cube.copy() + def test_circular(self, sample_data): + series_inc = sample_data.series_inc + circular_cube = sample_data.series_inc_cube.copy() circular_cube.coord("time").circular = True circular = CubeSignature(circular_cube) - self.assertNotEqual(circular.dim_metadata, series_inc.dim_metadata) - self.assertEqual(circular.dim_metadata, circular.dim_metadata) - - -if __name__ == "__main__": - tests.main() + assert circular.dim_metadata != series_inc.dim_metadata + assert circular.dim_metadata == circular.dim_metadata diff --git a/lib/iris/tests/unit/concatenate/test_concatenate.py b/lib/iris/tests/unit/concatenate/test_concatenate.py index 59312a542d..448ffb5e7b 100644 --- a/lib/iris/tests/unit/concatenate/test_concatenate.py +++ b/lib/iris/tests/unit/concatenate/test_concatenate.py @@ -4,13 +4,10 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris._concatenate.concatenate.py`.""" -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - import cf_units import numpy as np import numpy.ma as ma +import pytest from iris._concatenate import concatenate from iris._lazy_data import as_lazy_data @@ -18,12 +15,19 @@ import iris.coords import iris.cube from iris.exceptions import ConcatenateError +import iris.warnings -class TestEpoch(tests.IrisTest): - def simple_1d_time_cubes(self, reftimes, coords_points): - cubes = [] +class TestEpoch: + @pytest.fixture() + def simple_1d_time_cubes(self): + reftimes = [ + "hours since 1970-01-01 00:00:00", + "hours since 1970-01-01 00:00:00", + ] + coords_points = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]] data_points = [273, 275, 278, 277, 274] + cubes = [] for reftime, coord_points in zip(reftimes, coords_points): cube = iris.cube.Cube( np.array(data_points, dtype=np.float32), @@ -40,20 +44,21 @@ def simple_1d_time_cubes(self, reftimes, coords_points): cubes.append(cube) return cubes - def test_concat_1d_with_same_time_units(self): - reftimes = [ - "hours since 1970-01-01 00:00:00", - "hours since 1970-01-01 00:00:00", - ] - coords_points = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]] - cubes = self.simple_1d_time_cubes(reftimes, coords_points) - result = concatenate(cubes) - self.assertEqual(len(result), 1) - self.assertEqual(result[0].shape, (10,)) + def test_concat_1d_with_same_time_units(self, simple_1d_time_cubes): + result = concatenate(simple_1d_time_cubes) + assert len(result) == 1 + assert result[0].shape == (10,) + +class _MessagesMixin: + @pytest.fixture() + def placeholder(self): + # Shim to allow sample_cubes to have identical signature in both parent and subclasses + return [] -class _MessagesMixin(tests.IrisTest): - def setUp(self): + @pytest.fixture() + def sample_cubes(self, placeholder): + # Construct and return a pair of identical cubes data = np.arange(24, dtype=np.float32).reshape(2, 3, 4) cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") # Time coord @@ -106,127 +111,103 @@ def setUp(self): cube.add_aux_coord(sigma, ()) cube.add_aux_coord(orog, ()) cube.add_aux_factory(HybridHeightFactory(delta, sigma, orog)) - self.cube = cube + # Return a list with two identical cubes + return [cube, cube.copy()] + def test_definition_difference_message(self, sample_cubes): + sample_cubes[1].units = "1" + exc_regexp = "Cube metadata differs for phenomenon:" + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) -class TestMessages(_MessagesMixin): - def setUp(self): - super().setUp() - def test_dim_coords_same_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() +class TestMessages(_MessagesMixin): + def test_dim_coords_same_message(self, sample_cubes): exc_regexp = "Cannot find an axis to concatenate over for phenomenon *" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_definition_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.units = "1" + def test_definition_difference_message(self, sample_cubes): + sample_cubes[1].units = "1" exc_regexp = "Cube metadata differs for phenomenon: *" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_dimensions_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_coord("latitude") + def test_dimensions_difference_message(self, sample_cubes): + sample_cubes[1].remove_coord("latitude") exc_regexp = "Dimension coordinates differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_dimensions_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.coord("latitude").long_name = "bob" + def test_dimensions_metadata_difference_message(self, sample_cubes): + sample_cubes[1].coord("latitude").long_name = "bob" exc_regexp = "Dimension coordinates metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_aux_coords_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_coord("foo") + def test_aux_coords_difference_message(self, sample_cubes): + sample_cubes[1].remove_coord("foo") exc_regexp = "Auxiliary coordinates differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_aux_coords_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.coord("foo").units = "m" + def test_aux_coords_metadata_difference_message(self, sample_cubes): + sample_cubes[1].coord("foo").units = "m" exc_regexp = "Auxiliary coordinates metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_scalar_coords_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_coord("height") + def test_scalar_coords_difference_message(self, sample_cubes): + sample_cubes[1].remove_coord("height") exc_regexp = "Scalar coordinates differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_scalar_coords_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.coord("height").long_name = "alice" + def test_scalar_coords_metadata_difference_message(self, sample_cubes): + sample_cubes[1].coord("height").long_name = "alice" exc_regexp = "Scalar coordinates values or metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_cell_measure_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_cell_measure("bar") + def test_cell_measure_difference_message(self, sample_cubes): + sample_cubes[1].remove_cell_measure("bar") exc_regexp = "Cell measures differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_cell_measure_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.cell_measure("bar").units = "m" + def test_cell_measure_metadata_difference_message(self, sample_cubes): + sample_cubes[1].cell_measure("bar").units = "m" exc_regexp = "Cell measures metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_ancillary_variable_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_ancillary_variable("baz") + def test_ancillary_variable_difference_message(self, sample_cubes): + sample_cubes[1].remove_ancillary_variable("baz") exc_regexp = "Ancillary variables differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_ancillary_variable_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.ancillary_variable("baz").units = "m" + def test_ancillary_variable_metadata_difference_message(self, sample_cubes): + sample_cubes[1].ancillary_variable("baz").units = "m" exc_regexp = "Ancillary variables metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_derived_coord_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_aux_factory(cube_2.aux_factories[0]) + def test_derived_coord_difference_message(self, sample_cubes): + sample_cubes[1].remove_aux_factory(sample_cubes[1].aux_factories[0]) exc_regexp = "Derived coordinates differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_derived_coord_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.aux_factories[0].units = "km" + def test_derived_coord_metadata_difference_message(self, sample_cubes): + sample_cubes[1].aux_factories[0].units = "km" exc_regexp = "Derived coordinates metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_ndim_difference_message(self): - cube_1 = self.cube - cube_2 = iris.cube.Cube( + def test_ndim_difference_message(self, sample_cubes): + # Replace cube#2 with an entirely different thing + sample_cubes[1] = iris.cube.Cube( np.arange(5, dtype=np.float32), standard_name="air_temperature", units="K", @@ -236,73 +217,71 @@ def test_ndim_difference_message(self): standard_name="longitude", units="degrees", ) - cube_2.add_dim_coord(x_coord, 0) + sample_cubes[1].add_dim_coord(x_coord, 0) exc_regexp = "Data dimensions differ: [0-9] != [0-9]" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_datatype_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.data.dtype = np.float64 + def test_datatype_difference_message(self, sample_cubes): + sample_cubes[1].data.dtype = np.float64 exc_regexp = "Data types differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) - def test_dim_coords_overlap_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.coord("time").points = np.arange(1, 3, dtype=np.float32) + def test_dim_coords_overlap_message(self, sample_cubes): + sample_cubes[1].coord("time").points = np.arange(1, 3, dtype=np.float32) exc_regexp = "Found cubes with overlap on concatenate axis" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) class TestNonMetadataMessages(_MessagesMixin): - def setUp(self): - super().setUp() - cube_2 = self.cube.copy() - cube_2.coord("time").points = cube_2.coord("time").points + 2 - self.cube_2 = cube_2 + parent_cubes = _MessagesMixin.sample_cubes + + @pytest.fixture() + def sample_cubes(self, parent_cubes): + coord = parent_cubes[1].coord("time") + parent_cubes[1].replace_coord(coord.copy(points=coord.points + 2)) + return parent_cubes - def test_aux_coords_diff_message(self): - self.cube_2.coord("foo").points = [3, 4, 5] + def test_aux_coords_diff_message(self, sample_cubes): + sample_cubes[1].coord("foo").points = [3, 4, 5] exc_regexp = "Auxiliary coordinates are unequal for phenomenon * " - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([self.cube, self.cube_2], True) - with self.assertWarnsRegex(iris.warnings.IrisUserWarning, exc_regexp): - _ = concatenate([self.cube, self.cube_2], False) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) + with pytest.warns(iris.warnings.IrisUserWarning, match=exc_regexp): + _ = concatenate(sample_cubes, False) - def test_cell_measures_diff_message(self): - self.cube_2.cell_measure("bar").data = [3, 4, 5] + def test_cell_measures_diff_message(self, sample_cubes): + sample_cubes[1].cell_measure("bar").data = [3, 4, 5] exc_regexp = "Cell measures are unequal for phenomenon * " - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([self.cube, self.cube_2], True) - with self.assertWarnsRegex(iris.warnings.IrisUserWarning, exc_regexp): - _ = concatenate([self.cube, self.cube_2], False) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) + with pytest.warns(iris.warnings.IrisUserWarning, match=exc_regexp): + _ = concatenate(sample_cubes, False) - def test_ancillary_variable_diff_message(self): - self.cube_2.ancillary_variable("baz").data = [3, 4, 5] + def test_ancillary_variable_diff_message(self, sample_cubes): + sample_cubes[1].ancillary_variable("baz").data = [3, 4, 5] exc_regexp = "Ancillary variables are unequal for phenomenon * " - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([self.cube, self.cube_2], True) - with self.assertWarnsRegex(iris.warnings.IrisUserWarning, exc_regexp): - _ = concatenate([self.cube, self.cube_2], False) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) + with pytest.warns(iris.warnings.IrisUserWarning, match=exc_regexp): + _ = concatenate(sample_cubes, False) - def test_derived_coords_diff_message(self): - self.cube_2.aux_factories[0].update(self.cube_2.coord("sigma"), None) + def test_derived_coords_diff_message(self, sample_cubes): + sample_cubes[1].aux_factories[0].update(sample_cubes[1].coord("sigma"), None) exc_regexp = "Derived coordinates are unequal for phenomenon * " - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([self.cube, self.cube_2], True) - with self.assertWarnsRegex(iris.warnings.IrisUserWarning, exc_regexp): - _ = concatenate([self.cube, self.cube_2], False) + with pytest.raises(ConcatenateError, match=exc_regexp): + _ = concatenate(sample_cubes, True) + with pytest.warns(iris.warnings.IrisUserWarning, match=exc_regexp): + _ = concatenate(sample_cubes, False) -class TestOrder(tests.IrisTest): +class TestOrder: def _make_cube(self, points, bounds=None): nx = 4 data = np.arange(len(points) * nx).reshape(len(points), nx) @@ -317,147 +296,154 @@ def test_asc_points(self): top = self._make_cube([10, 30, 50, 70, 90]) bottom = self._make_cube([-90, -70, -50, -30, -10]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_asc_bounds(self): top = self._make_cube([22.5, 67.5], [[0, 45], [45, 90]]) bottom = self._make_cube([-67.5, -22.5], [[-90, -45], [-45, 0]]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_asc_points_with_singleton_ordered(self): top = self._make_cube([5]) bottom = self._make_cube([15, 25]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_asc_points_with_singleton_unordered(self): top = self._make_cube([25]) bottom = self._make_cube([5, 15]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_asc_bounds_with_singleton_ordered(self): top = self._make_cube([5], [[0, 10]]) bottom = self._make_cube([15, 25], [[10, 20], [20, 30]]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_asc_bounds_with_singleton_unordered(self): top = self._make_cube([25], [[20, 30]]) bottom = self._make_cube([5, 15], [[0, 10], [10, 20]]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_desc_points(self): top = self._make_cube([90, 70, 50, 30, 10]) bottom = self._make_cube([-10, -30, -50, -70, -90]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_desc_bounds(self): top = self._make_cube([67.5, 22.5], [[90, 45], [45, 0]]) bottom = self._make_cube([-22.5, -67.5], [[0, -45], [-45, -90]]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_desc_points_with_singleton_ordered(self): top = self._make_cube([25]) bottom = self._make_cube([15, 5]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_desc_points_with_singleton_unordered(self): top = self._make_cube([5]) bottom = self._make_cube([25, 15]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_desc_bounds_with_singleton_ordered(self): top = self._make_cube([25], [[30, 20]]) bottom = self._make_cube([15, 5], [[20, 10], [10, 0]]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_desc_bounds_with_singleton_unordered(self): top = self._make_cube([5], [[10, 0]]) bottom = self._make_cube([25, 15], [[30, 20], [20, 10]]) result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) + assert len(result) == 1 def test_points_all_singleton(self): top = self._make_cube([5]) bottom = self._make_cube([15]) result1 = concatenate([top, bottom]) result2 = concatenate([bottom, top]) - self.assertEqual(len(result1), 1) - self.assertEqual(len(result2), 1) - self.assertEqual(result1, result2) + assert len(result1) == 1 + assert result1 == result2 def test_asc_bounds_all_singleton(self): top = self._make_cube([5], [0, 10]) bottom = self._make_cube([15], [10, 20]) result1 = concatenate([top, bottom]) result2 = concatenate([bottom, top]) - self.assertEqual(len(result1), 1) - self.assertEqual(len(result2), 1) - self.assertEqual(result1, result2) + assert len(result1) == 1 + assert result1 == result2 def test_desc_bounds_all_singleton(self): top = self._make_cube([5], [10, 0]) bottom = self._make_cube([15], [20, 10]) result1 = concatenate([top, bottom]) result2 = concatenate([bottom, top]) - self.assertEqual(len(result1), 1) - self.assertEqual(len(result2), 1) - self.assertEqual(result1, result2) - - -class TestConcatenate__dask(tests.IrisTest): - def build_lazy_cube(self, points, bounds=None, nx=4, aux_coords=False): - data = np.arange(len(points) * nx).reshape(len(points), nx) - data = as_lazy_data(data) - cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") - lat = iris.coords.DimCoord(points, "latitude", bounds=bounds) - lon = iris.coords.DimCoord(np.arange(nx), "longitude") - cube.add_dim_coord(lat, 0) - cube.add_dim_coord(lon, 1) - if aux_coords: - bounds = np.arange(len(points) * nx * 4).reshape(len(points), nx, 4) - bounds = as_lazy_data(bounds) - aux_coord = iris.coords.AuxCoord(data, var_name="aux_coord", bounds=bounds) - cube.add_aux_coord(aux_coord, (0, 1)) - return cube + assert len(result1) == 1 + assert result1 == result2 + + +class TestConcatenate__dask: + @pytest.fixture() + def sample_lazy_cubes(self): + # Make a pair of concatenatable cubes, with dim points [1, 2] and [3, 4, 5] + def build_lazy_cube(points): + nx = 4 + data = np.arange(len(points) * nx).reshape(len(points), nx) + data = as_lazy_data(data) + cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") + lat = iris.coords.DimCoord(points, "latitude") + lon = iris.coords.DimCoord(np.arange(nx), "longitude") + cube.add_dim_coord(lat, 0) + cube.add_dim_coord(lon, 1) + return cube + + c1 = build_lazy_cube([1, 2]) + c2 = build_lazy_cube([3, 4, 5]) + return c1, c2 + + @staticmethod + def add_sample_auxcoord(cube): + # Augment a test cube by adding an aux-coord on the concatenation dimension + n_points, nx = cube.shape + bounds = np.arange(n_points * nx * 4).reshape(n_points, nx, 4) + bounds = as_lazy_data(bounds) + aux_coord = iris.coords.AuxCoord( + cube.core_data(), + bounds=bounds, + var_name="aux_coord", + ) + cube.add_aux_coord(aux_coord, (0, 1)) - def test_lazy_concatenate(self): - c1 = self.build_lazy_cube([1, 2]) - c2 = self.build_lazy_cube([3, 4, 5]) - (cube,) = concatenate([c1, c2]) - self.assertTrue(cube.has_lazy_data()) - self.assertFalse(ma.isMaskedArray(cube.data)) + def test_lazy_concatenate(self, sample_lazy_cubes): + (cube,) = concatenate(sample_lazy_cubes) + assert cube.has_lazy_data() + assert not ma.isMaskedArray(cube.data) - def test_lazy_concatenate_aux_coords(self): - c1 = self.build_lazy_cube([1, 2], aux_coords=True) - c2 = self.build_lazy_cube([3, 4, 5], aux_coords=True) + def test_lazy_concatenate_aux_coords(self, sample_lazy_cubes): + c1, c2 = sample_lazy_cubes + for cube in (c1, c2): + self.add_sample_auxcoord(cube) (result,) = concatenate([c1, c2]) - self.assertTrue(c1.coord("aux_coord").has_lazy_points()) - self.assertTrue(c1.coord("aux_coord").has_lazy_bounds()) + assert c1.coord("aux_coord").has_lazy_points() + assert c1.coord("aux_coord").has_lazy_bounds() - self.assertTrue(c2.coord("aux_coord").has_lazy_points()) - self.assertTrue(c2.coord("aux_coord").has_lazy_bounds()) + assert c2.coord("aux_coord").has_lazy_points() + assert c2.coord("aux_coord").has_lazy_bounds() - self.assertTrue(result.coord("aux_coord").has_lazy_points()) - self.assertTrue(result.coord("aux_coord").has_lazy_bounds()) + assert result.coord("aux_coord").has_lazy_points() + assert result.coord("aux_coord").has_lazy_bounds() - def test_lazy_concatenate_masked_array_mixed_deferred(self): - c1 = self.build_lazy_cube([1, 2]) - c2 = self.build_lazy_cube([3, 4, 5]) + def test_lazy_concatenate_masked_array_mixed_deferred(self, sample_lazy_cubes): + c1, c2 = sample_lazy_cubes c2.data = np.ma.masked_greater(c2.data, 3) (cube,) = concatenate([c1, c2]) - self.assertTrue(cube.has_lazy_data()) - self.assertTrue(ma.isMaskedArray(cube.data)) - - -if __name__ == "__main__": - tests.main() + assert cube.has_lazy_data() + assert ma.isMaskedArray(cube.data) diff --git a/lib/iris/tests/unit/config/test_NetCDF.py b/lib/iris/tests/unit/config/test_NetCDF.py index 5b691a1dc3..7469ca8c79 100644 --- a/lib/iris/tests/unit/config/test_NetCDF.py +++ b/lib/iris/tests/unit/config/test_NetCDF.py @@ -4,41 +4,40 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.config.NetCDF` class.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - +import re import warnings +import pytest + import iris.config -class Test(tests.IrisTest): - def setUp(self): - self.options = iris.config.NetCDF() +@pytest.fixture +def options(): + return iris.config.NetCDF() + + +def test_basic(options): + assert not options.conventions_override - def test_basic(self): - self.assertFalse(self.options.conventions_override) - def test_enabled(self): - self.options.conventions_override = True - self.assertTrue(self.options.conventions_override) +def test_enabled(options): + options.conventions_override = True + assert options.conventions_override - def test_bad_value(self): - # A bad value should be ignored and replaced with the default value. - bad_value = "wibble" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.options.conventions_override = bad_value - self.assertFalse(self.options.conventions_override) - exp_wmsg = "Attempting to set invalid value {!r}".format(bad_value) - self.assertRegex(str(w[0].message), exp_wmsg) - def test__contextmgr(self): - with self.options.context(conventions_override=True): - self.assertTrue(self.options.conventions_override) - self.assertFalse(self.options.conventions_override) +def test_bad_value(options): + # A bad value should be ignored and replaced with the default value. + bad_value = "wibble" + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + options.conventions_override = bad_value + assert not options.conventions_override + exp_wmsg = "Attempting to set invalid value {!r}".format(bad_value) + assert re.match(exp_wmsg, str(w[0].message)) -if __name__ == "__main__": - tests.main() +def test__contextmgr(options): + with options.context(conventions_override=True): + assert options.conventions_override + assert not options.conventions_override diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 8c36240fb6..1f01efd90f 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -35,43 +35,44 @@ CoordinateNotFoundError, UnitConversionError, ) +from iris.tests import _shared_utils import iris.tests.stock as stock from iris.tests.stock.mesh import sample_mesh, sample_mesh_cube, sample_meshcoord from iris.warnings import IrisUserWarning, IrisVagueMetadataWarning -class Test___init___data(tests.IrisTest): +class Test___init___data: def test_ndarray(self): # np.ndarray should be allowed through data = np.arange(12).reshape(3, 4) cube = Cube(data) - self.assertEqual(type(cube.data), np.ndarray) - self.assertArrayEqual(cube.data, data) + assert type(cube.data) is np.ndarray + _shared_utils.assert_array_equal(cube.data, data) def test_masked(self): # ma.MaskedArray should be allowed through data = ma.masked_greater(np.arange(12).reshape(3, 4), 1) cube = Cube(data) - self.assertEqual(type(cube.data), ma.MaskedArray) - self.assertMaskedArrayEqual(cube.data, data) + assert type(cube.data) is ma.MaskedArray + _shared_utils.assert_masked_array_equal(cube.data, data) def test_masked_no_mask(self): # ma.MaskedArray should be allowed through even if it has no mask data = ma.masked_array(np.arange(12).reshape(3, 4), False) cube = Cube(data) - self.assertEqual(type(cube.data), ma.MaskedArray) - self.assertMaskedArrayEqual(cube.data, data) + assert type(cube.data) is ma.MaskedArray + _shared_utils.assert_masked_array_equal(cube.data, data) def test_matrix(self): # Subclasses of np.ndarray should be coerced back to np.ndarray. # (Except for np.ma.MaskedArray.) data = np.matrix([[1, 2, 3], [4, 5, 6]]) cube = Cube(data) - self.assertEqual(type(cube.data), np.ndarray) - self.assertArrayEqual(cube.data, data) + assert type(cube.data) is np.ndarray + _shared_utils.assert_array_equal(cube.data, data) -class Test_data_dtype_fillvalue(tests.IrisTest): +class Test_data_dtype_fillvalue: def _sample_data(self, dtype=("f4"), masked=False, fill_value=None, lazy=False): data = np.arange(6).reshape((2, 3)) dtype = np.dtype(dtype) @@ -94,74 +95,74 @@ def _sample_cube(self, dtype=("f4"), masked=False, fill_value=None, lazy=False): def test_realdata_change(self): # Check re-assigning real data. cube = self._sample_cube() - self.assertEqual(cube.dtype, np.float32) + assert cube.dtype == np.float32 new_dtype = np.dtype("i4") new_data = self._sample_data(dtype=new_dtype) cube.data = new_data - self.assertIs(cube.core_data(), new_data) - self.assertEqual(cube.dtype, new_dtype) + assert cube.core_data() is new_data + assert cube.dtype == new_dtype def test_realmaskdata_change(self): # Check re-assigning real masked data. cube = self._sample_cube(masked=True, fill_value=1234) - self.assertEqual(cube.dtype, np.float32) + assert cube.dtype == np.float32 new_dtype = np.dtype("i4") new_fill_value = 4321 new_data = self._sample_data( masked=True, fill_value=new_fill_value, dtype=new_dtype ) cube.data = new_data - self.assertIs(cube.core_data(), new_data) - self.assertEqual(cube.dtype, new_dtype) - self.assertEqual(cube.data.fill_value, new_fill_value) + assert cube.core_data() is new_data + assert cube.dtype == new_dtype + assert cube.data.fill_value == new_fill_value def test_lazydata_change(self): # Check re-assigning lazy data. cube = self._sample_cube(lazy=True) - self.assertEqual(cube.core_data().dtype, np.float32) + assert cube.core_data().dtype == np.float32 new_dtype = np.dtype("f8") new_data = self._sample_data(new_dtype, lazy=True) cube.data = new_data - self.assertIs(cube.core_data(), new_data) - self.assertEqual(cube.dtype, new_dtype) + assert cube.core_data() is new_data + assert cube.dtype == new_dtype def test_lazymaskdata_change(self): # Check re-assigning lazy masked data. cube = self._sample_cube(masked=True, fill_value=1234, lazy=True) - self.assertEqual(cube.core_data().dtype, np.float32) + assert cube.core_data().dtype == np.float32 new_dtype = np.dtype("f8") new_fill_value = 4321 new_data = self._sample_data( dtype=new_dtype, masked=True, fill_value=new_fill_value, lazy=True ) cube.data = new_data - self.assertIs(cube.core_data(), new_data) - self.assertEqual(cube.dtype, new_dtype) - self.assertEqual(cube.data.fill_value, new_fill_value) + assert cube.core_data() is new_data + assert cube.dtype == new_dtype + assert cube.data.fill_value == new_fill_value def test_lazydata_realise(self): # Check touching lazy data. cube = self._sample_cube(lazy=True) data = cube.data - self.assertIs(cube.core_data(), data) - self.assertEqual(cube.dtype, np.float32) + assert cube.core_data() is data + assert cube.dtype == np.float32 def test_lazymaskdata_realise(self): # Check touching masked lazy data. fill_value = 27.3 cube = self._sample_cube(masked=True, fill_value=fill_value, lazy=True) data = cube.data - self.assertIs(cube.core_data(), data) - self.assertEqual(cube.dtype, np.float32) - self.assertEqual(data.fill_value, np.float32(fill_value)) + assert cube.core_data() is data + assert cube.dtype == np.float32 + assert data.fill_value == np.float32(fill_value) def test_realmaskedconstantint_realise(self): masked_data = ma.masked_array([666], mask=True) masked_constant = masked_data[0] cube = Cube(masked_constant) data = cube.data - self.assertTrue(ma.isMaskedArray(data)) - self.assertNotIsInstance(data, ma.core.MaskedConstant) + assert ma.isMaskedArray(data) + assert not isinstance(data, ma.core.MaskedConstant) def test_lazymaskedconstantint_realise(self): dtype = np.dtype("i2") @@ -170,8 +171,8 @@ def test_lazymaskedconstantint_realise(self): masked_constant_lazy = as_lazy_data(masked_constant) cube = Cube(masked_constant_lazy) data = cube.data - self.assertTrue(ma.isMaskedArray(data)) - self.assertNotIsInstance(data, ma.core.MaskedConstant) + assert ma.isMaskedArray(data) + assert not isinstance(data, ma.core.MaskedConstant) def test_lazydata___getitem__dtype(self): fill_value = 1234 @@ -185,24 +186,24 @@ def test_lazydata___getitem__dtype(self): lazy_masked_array = as_lazy_data(masked_array) cube = Cube(lazy_masked_array) subcube = cube[3:] - self.assertEqual(subcube.dtype, dtype) - self.assertEqual(subcube.data.fill_value, fill_value) + assert subcube.dtype == dtype + assert subcube.data.fill_value == fill_value -class Test_extract(tests.IrisTest): +class Test_extract: def test_scalar_cube_exists(self): # Ensure that extract is able to extract a scalar cube. constraint = iris.Constraint(name="a1") cube = Cube(1, long_name="a1") res = cube.extract(constraint) - self.assertIs(res, cube) + assert res is cube def test_scalar_cube_noexists(self): # Ensure that extract does not return a non-matching scalar cube. constraint = iris.Constraint(name="a2") cube = Cube(1, long_name="a1") res = cube.extract(constraint) - self.assertIs(res, None) + assert res is None def test_scalar_cube_coord_match(self): # Ensure that extract is able to extract a scalar cube according to @@ -212,7 +213,7 @@ def test_scalar_cube_coord_match(self): coord = iris.coords.AuxCoord(0, long_name="scalar_coord") cube.add_aux_coord(coord, None) res = cube.extract(constraint) - self.assertIs(res, cube) + assert res is cube def test_scalar_cube_coord_nomatch(self): # Ensure that extract is not extracting a scalar cube with scalar @@ -222,30 +223,30 @@ def test_scalar_cube_coord_nomatch(self): coord = iris.coords.AuxCoord(0, long_name="scalar_coord") cube.add_aux_coord(coord, None) res = cube.extract(constraint) - self.assertIs(res, None) + assert res is None def test_1d_cube_exists(self): # Ensure that extract is able to extract from a 1d cube. constraint = iris.Constraint(name="a1") cube = Cube([1], long_name="a1") res = cube.extract(constraint) - self.assertIs(res, cube) + assert res is cube def test_1d_cube_noexists(self): # Ensure that extract does not return a non-matching 1d cube. constraint = iris.Constraint(name="a2") cube = Cube([1], long_name="a1") res = cube.extract(constraint) - self.assertIs(res, None) + assert res is None -class Test_xml(tests.IrisTest): - def test_checksum_ignores_masked_values(self): +class Test_xml: + def test_checksum_ignores_masked_values(self, request): # Mask out an single element. data = ma.arange(12).reshape(3, 4) data[1, 2] = ma.masked cube = Cube(data) - self.assertCML(cube) + _shared_utils.assert_CML(request, cube) # If we change the underlying value before masking it, the # checksum should be unaffected. @@ -253,21 +254,21 @@ def test_checksum_ignores_masked_values(self): data[1, 2] = 42 data[1, 2] = ma.masked cube = Cube(data) - self.assertCML(cube) + _shared_utils.assert_CML(request, cube) def test_byteorder_default(self): cube = Cube(np.arange(3)) - self.assertIn("byteorder", cube.xml()) + assert "byteorder" in cube.xml() def test_byteorder_false(self): cube = Cube(np.arange(3)) - self.assertNotIn("byteorder", cube.xml(byteorder=False)) + assert "byteorder" not in cube.xml(byteorder=False) def test_byteorder_true(self): cube = Cube(np.arange(3)) - self.assertIn("byteorder", cube.xml(byteorder=True)) + assert "byteorder" in cube.xml(byteorder=True) - def test_cell_measures(self): + def test_cell_measures(self, request): cube = stock.simple_3d_w_multidim_coords() cm_a = iris.coords.CellMeasure( np.zeros(cube.shape[-2:]), measure="area", units="1" @@ -280,19 +281,20 @@ def test_cell_measures(self): units="m3", ) cube.add_cell_measure(cm_v, (0, 1, 2)) - self.assertCML(cube) + _shared_utils.assert_CML(request, cube) - def test_ancils(self): + def test_ancils(self, request): cube = stock.simple_2d_w_multidim_coords() av = iris.coords.AncillaryVariable( np.zeros(cube.shape), long_name="xy", var_name="vxy", units="1" ) cube.add_ancillary_variable(av, (0, 1)) - self.assertCML(cube) + _shared_utils.assert_CML(request, cube) -class Test_collapsed__lazy(tests.IrisTest): - def setUp(self): +class Test_collapsed__lazy: + @pytest.fixture(autouse=True) + def _setup(self): self.data = np.arange(6.0).reshape((2, 3)) self.lazydata = as_lazy_data(self.data) cube = Cube(self.lazydata) @@ -304,50 +306,51 @@ def setUp(self): def test_dim0_lazy(self): cube_collapsed = self.cube.collapsed("y", MEAN) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual(cube_collapsed.data, [1.5, 2.5, 3.5]) - self.assertFalse(cube_collapsed.has_lazy_data()) + assert cube_collapsed.has_lazy_data() + _shared_utils.assert_array_almost_equal(cube_collapsed.data, [1.5, 2.5, 3.5]) + assert not cube_collapsed.has_lazy_data() def test_dim0_lazy_weights_none(self): cube_collapsed = self.cube.collapsed("y", MEAN, weights=None) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual(cube_collapsed.data, [1.5, 2.5, 3.5]) - self.assertFalse(cube_collapsed.has_lazy_data()) + assert cube_collapsed.has_lazy_data() + _shared_utils.assert_array_almost_equal(cube_collapsed.data, [1.5, 2.5, 3.5]) + assert not cube_collapsed.has_lazy_data() def test_dim1_lazy(self): cube_collapsed = self.cube.collapsed("x", MEAN) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual(cube_collapsed.data, [1.0, 4.0]) - self.assertFalse(cube_collapsed.has_lazy_data()) + assert cube_collapsed.has_lazy_data() + _shared_utils.assert_array_almost_equal(cube_collapsed.data, [1.0, 4.0]) + assert not cube_collapsed.has_lazy_data() def test_dim1_lazy_weights_none(self): cube_collapsed = self.cube.collapsed("x", MEAN, weights=None) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual(cube_collapsed.data, [1.0, 4.0]) - self.assertFalse(cube_collapsed.has_lazy_data()) + assert cube_collapsed.has_lazy_data() + _shared_utils.assert_array_almost_equal(cube_collapsed.data, [1.0, 4.0]) + assert not cube_collapsed.has_lazy_data() def test_multidims(self): # Check that MEAN works with multiple dims. cube_collapsed = self.cube.collapsed(("x", "y"), MEAN) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAllClose(cube_collapsed.data, 2.5) + assert cube_collapsed.has_lazy_data() + _shared_utils.assert_array_all_close(cube_collapsed.data, 2.5) def test_multidims_weights_none(self): # Check that MEAN works with multiple dims. cube_collapsed = self.cube.collapsed(("x", "y"), MEAN, weights=None) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAllClose(cube_collapsed.data, 2.5) + assert cube_collapsed.has_lazy_data() + _shared_utils.assert_array_all_close(cube_collapsed.data, 2.5) def test_non_lazy_aggregator(self): # An aggregator which doesn't have a lazy function should still work. dummy_agg = Aggregator("custom_op", lambda x, axis=None: np.mean(x, axis=axis)) result = self.cube.collapsed("x", dummy_agg) - self.assertFalse(result.has_lazy_data()) - self.assertArrayEqual(result.data, np.mean(self.data, axis=1)) + assert not result.has_lazy_data() + _shared_utils.assert_array_equal(result.data, np.mean(self.data, axis=1)) -class Test_collapsed__multidim_weighted_with_arr(tests.IrisTest): - def setUp(self): +class Test_collapsed__multidim_weighted_with_arr: + @pytest.fixture(autouse=True) + def _multidim_arr_setup(self): self.data = np.arange(6.0).reshape((2, 3)) self.lazydata = as_lazy_data(self.data) # Test cubes with (same-valued) real and lazy data @@ -376,86 +379,102 @@ def test_weighted_fullweights_real_y(self): cube_collapsed = self.cube_real.collapsed( "y", MEAN, weights=self.full_weights_y ) - self.assertArrayAlmostEqual(cube_collapsed.data, self.expected_result_y) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") - self.assertEqual(cube_collapsed.units.origin, "kg m-2 s-1") + _shared_utils.assert_array_almost_equal( + cube_collapsed.data, self.expected_result_y + ) + assert cube_collapsed.units == "kg m-2 s-1" + assert cube_collapsed.units.origin == "kg m-2 s-1" def test_weighted_fullweights_lazy_y(self): # Full-shape weights, lazy data : Check lazy result, same values as real calc. cube_collapsed = self.cube_lazy.collapsed( "y", MEAN, weights=self.full_weights_y ) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual(cube_collapsed.data, self.expected_result_y) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") + assert cube_collapsed.has_lazy_data() + _shared_utils.assert_array_almost_equal( + cube_collapsed.data, self.expected_result_y + ) + assert cube_collapsed.units == "kg m-2 s-1" def test_weighted_1dweights_real_y(self): # 1-D weights, real data : Check same results as full-shape. cube_collapsed = self.cube_real.collapsed("y", MEAN, weights=self.y_weights) - self.assertArrayAlmostEqual(cube_collapsed.data, self.expected_result_y) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") - self.assertEqual(cube_collapsed.units.origin, "kg m-2 s-1") + _shared_utils.assert_array_almost_equal( + cube_collapsed.data, self.expected_result_y + ) + assert cube_collapsed.units == "kg m-2 s-1" + assert cube_collapsed.units.origin == "kg m-2 s-1" def test_weighted_1dweights_lazy_y(self): # 1-D weights, lazy data : Check lazy result, same values as real calc. cube_collapsed = self.cube_lazy.collapsed("y", MEAN, weights=self.y_weights) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual(cube_collapsed.data, self.expected_result_y) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") + assert cube_collapsed.has_lazy_data() + _shared_utils.assert_array_almost_equal( + cube_collapsed.data, self.expected_result_y + ) + assert cube_collapsed.units == "kg m-2 s-1" def test_weighted_fullweights_real_x(self): # Full weights, real data, ** collapse X ** : as for 'y' case above cube_collapsed = self.cube_real.collapsed( "x", MEAN, weights=self.full_weights_x ) - self.assertArrayAlmostEqual(cube_collapsed.data, self.expected_result_x) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") - self.assertEqual(cube_collapsed.units.origin, "kg m-2 s-1") + _shared_utils.assert_array_almost_equal( + cube_collapsed.data, self.expected_result_x + ) + assert cube_collapsed.units == "kg m-2 s-1" + assert cube_collapsed.units.origin == "kg m-2 s-1" def test_weighted_fullweights_lazy_x(self): # Full weights, lazy data, ** collapse X ** : as for 'y' case above cube_collapsed = self.cube_lazy.collapsed( "x", MEAN, weights=self.full_weights_x ) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual(cube_collapsed.data, self.expected_result_x) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") - self.assertEqual(cube_collapsed.units.origin, "kg m-2 s-1") + assert cube_collapsed.has_lazy_data() + _shared_utils.assert_array_almost_equal( + cube_collapsed.data, self.expected_result_x + ) + assert cube_collapsed.units == "kg m-2 s-1" + assert cube_collapsed.units.origin == "kg m-2 s-1" def test_weighted_1dweights_real_x(self): # 1-D weights, real data, ** collapse X ** : as for 'y' case above cube_collapsed = self.cube_real.collapsed("x", MEAN, weights=self.x_weights) - self.assertArrayAlmostEqual(cube_collapsed.data, self.expected_result_x) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") - self.assertEqual(cube_collapsed.units.origin, "kg m-2 s-1") + _shared_utils.assert_array_almost_equal( + cube_collapsed.data, self.expected_result_x + ) + assert cube_collapsed.units == "kg m-2 s-1" + assert cube_collapsed.units.origin == "kg m-2 s-1" def test_weighted_1dweights_lazy_x(self): # 1-D weights, lazy data, ** collapse X ** : as for 'y' case above cube_collapsed = self.cube_lazy.collapsed("x", MEAN, weights=self.x_weights) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual(cube_collapsed.data, self.expected_result_x) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") - self.assertEqual(cube_collapsed.units.origin, "kg m-2 s-1") + assert cube_collapsed.has_lazy_data() + _shared_utils.assert_array_almost_equal( + cube_collapsed.data, self.expected_result_x + ) + assert cube_collapsed.units == "kg m-2 s-1" + assert cube_collapsed.units.origin == "kg m-2 s-1" def test_weighted_sum_fullweights_adapt_units_real_y(self): # Check that units are adapted correctly (kg m-2 s-1 * 1 = kg m-2 s-1) cube_collapsed = self.cube_real.collapsed("y", SUM, weights=self.full_weights_y) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") - self.assertEqual(cube_collapsed.units.origin, "kg m-2 s-1") + assert cube_collapsed.units == "kg m-2 s-1" + assert cube_collapsed.units.origin == "kg m-2 s-1" def test_weighted_sum_fullweights_adapt_units_lazy_y(self): # Check that units are adapted correctly (kg m-2 s-1 * 1 = kg m-2 s-1) cube_collapsed = self.cube_lazy.collapsed("y", SUM, weights=self.full_weights_y) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") - self.assertEqual(cube_collapsed.units.origin, "kg m-2 s-1") + assert cube_collapsed.units == "kg m-2 s-1" + assert cube_collapsed.units.origin == "kg m-2 s-1" def test_weighted_sum_1dweights_adapt_units_real_y(self): # Check that units are adapted correctly (kg m-2 s-1 * 1 = kg m-2 s-1) # Note: the same test with lazy data fails: # https://github.com/SciTools/iris/issues/5083 cube_collapsed = self.cube_real.collapsed("y", SUM, weights=self.y_weights) - self.assertEqual(cube_collapsed.units, "kg m-2 s-1") - self.assertEqual(cube_collapsed.units.origin, "kg m-2 s-1") + assert cube_collapsed.units == "kg m-2 s-1" + assert cube_collapsed.units.origin == "kg m-2 s-1" def test_weighted_sum_with_unknown_units_real_y(self): # Check that units are adapted correctly ('unknown' * '1' = 'unknown') @@ -467,7 +486,7 @@ def test_weighted_sum_with_unknown_units_real_y(self): SUM, weights=self.full_weights_y, ) - self.assertEqual(cube_collapsed.units, "unknown") + assert cube_collapsed.units == "unknown" def test_weighted_sum_with_unknown_units_lazy_y(self): # Check that units are adapted correctly ('unknown' * '1' = 'unknown') @@ -479,7 +498,7 @@ def test_weighted_sum_with_unknown_units_lazy_y(self): SUM, weights=self.full_weights_y, ) - self.assertEqual(cube_collapsed.units, "unknown") + assert cube_collapsed.units == "unknown" # Simply redo the tests of Test_collapsed__multidim_weighted_with_arr with @@ -489,9 +508,8 @@ def test_weighted_sum_with_unknown_units_lazy_y(self): class Test_collapsed__multidim_weighted_with_cube( Test_collapsed__multidim_weighted_with_arr ): - def setUp(self): - super().setUp() - + @pytest.fixture(autouse=True) + def _multidim_cube_setup(self, _multidim_arr_setup): self.y_weights_original = self.y_weights self.full_weights_y_original = self.full_weights_y self.x_weights_original = self.x_weights @@ -507,27 +525,26 @@ def setUp(self): def test_weighted_sum_fullweights_adapt_units_real_y(self): # Check that units are adapted correctly (kg m-2 s-1 * m2 = kg s-1) cube_collapsed = self.cube_real.collapsed("y", SUM, weights=self.full_weights_y) - self.assertEqual(cube_collapsed.units, "kg s-1") + assert cube_collapsed.units == "kg s-1" def test_weighted_sum_fullweights_adapt_units_lazy_y(self): # Check that units are adapted correctly (kg m-2 s-1 * m2 = kg s-1) cube_collapsed = self.cube_lazy.collapsed("y", SUM, weights=self.full_weights_y) - self.assertEqual(cube_collapsed.units, "kg s-1") + assert cube_collapsed.units == "kg s-1" def test_weighted_sum_1dweights_adapt_units_real_y(self): # Check that units are adapted correctly (kg m-2 s-1 * m2 = kg s-1) # Note: the same test with lazy data fails: # https://github.com/SciTools/iris/issues/5083 cube_collapsed = self.cube_real.collapsed("y", SUM, weights=self.y_weights) - self.assertEqual(cube_collapsed.units, "kg s-1") + assert cube_collapsed.units == "kg s-1" class Test_collapsed__multidim_weighted_with_str( Test_collapsed__multidim_weighted_with_cube ): - def setUp(self): - super().setUp() - + @pytest.fixture(autouse=True) + def _multidim_str_setup(self, _multidim_cube_setup): self.full_weights_y = "full_y" self.full_weights_x = "full_x" self.y_weights = "y" @@ -561,17 +578,17 @@ def setUp(self): class Test_collapsed__multidim_weighted_with_dim_metadata( Test_collapsed__multidim_weighted_with_str ): - def setUp(self): - super().setUp() - + @pytest.fixture(autouse=True) + def _setup(self, _multidim_str_setup): self.full_weights_y = self.dim_metadata_full_y self.full_weights_x = self.dim_metadata_full_x self.y_weights = self.dim_metadata_1d_y self.x_weights = self.dim_metadata_1d_x -class Test_collapsed__cellmeasure_ancils(tests.IrisTest): - def setUp(self): +class Test_collapsed__cellmeasure_ancils: + @pytest.fixture(autouse=True) + def _setup(self): cube = Cube(np.arange(6.0).reshape((2, 3))) for i_dim, name in enumerate(("y", "x")): npts = cube.shape[i_dim] @@ -585,19 +602,19 @@ def setUp(self): def test_ancillary_variables_and_cell_measures_kept(self): cube_collapsed = self.cube.collapsed("x", MEAN) - self.assertEqual( - cube_collapsed.ancillary_variables(), [self.ancillary_variable] - ) - self.assertEqual(cube_collapsed.cell_measures(), [self.cell_measure]) + assert cube_collapsed.ancillary_variables() == [self.ancillary_variable] + + assert cube_collapsed.cell_measures() == [self.cell_measure] def test_ancillary_variables_and_cell_measures_removed(self): cube_collapsed = self.cube.collapsed("y", MEAN) - self.assertEqual(cube_collapsed.ancillary_variables(), []) - self.assertEqual(cube_collapsed.cell_measures(), []) + assert cube_collapsed.ancillary_variables() == [] + assert cube_collapsed.cell_measures() == [] -class Test_collapsed__warning(tests.IrisTest): - def setUp(self): +class Test_collapsed__warning: + @pytest.fixture(autouse=True) + def _setup(self): self.cube = Cube([[1, 2], [1, 2]]) lat = DimCoord([1, 2], standard_name="latitude") lon = DimCoord([1, 2], standard_name="longitude") @@ -624,79 +641,80 @@ def _assert_warn_collapse_without_weight(self, coords, warn): # Ensure that warning is raised. msg = "Collapsing spatial coordinate {!r} without weighting" for coord in coords: - self.assertIn( - mock.call(msg.format(coord), category=IrisUserWarning), - warn.call_args_list, + assert ( + mock.call(msg.format(coord), category=IrisUserWarning) + in warn.call_args_list ) def _assert_nowarn_collapse_without_weight(self, coords, warn): # Ensure that warning is not raised. msg = "Collapsing spatial coordinate {!r} without weighting" for coord in coords: - self.assertNotIn(mock.call(msg.format(coord)), warn.call_args_list) + assert not mock.call(msg.format(coord)) in warn.call_args_list - def test_lat_lon_noweighted_aggregator(self): + def test_lat_lon_noweighted_aggregator(self, mocker): # Collapse latitude coordinate with unweighted aggregator. aggregator = mock.Mock(spec=Aggregator, lazy_func=None) aggregator.cell_method = None coords = ["latitude", "longitude"] - with mock.patch("warnings.warn") as warn: - self.cube.collapsed(coords, aggregator, somekeyword="bla") + warn = mocker.patch("warnings.warn") + self.cube.collapsed(coords, aggregator, somekeyword="bla") self._assert_nowarn_collapse_without_weight(coords, warn) - def test_lat_lon_weighted_aggregator(self): + def test_lat_lon_weighted_aggregator(self, mocker): # Collapse latitude coordinate with weighted aggregator without # providing weights. aggregator = self._aggregator(False) coords = ["latitude", "longitude"] - with mock.patch("warnings.warn") as warn: - self.cube.collapsed(coords, aggregator) + warn = mocker.patch("warnings.warn") + self.cube.collapsed(coords, aggregator) coords = [coord for coord in coords if "latitude" in coord] self._assert_warn_collapse_without_weight(coords, warn) - def test_lat_lon_weighted_aggregator_with_weights(self): + def test_lat_lon_weighted_aggregator_with_weights(self, mocker): # Collapse latitude coordinate with a weighted aggregators and # providing suitable weights. weights = np.array([[0.1, 0.5], [0.3, 0.2]]) aggregator = self._aggregator(True) coords = ["latitude", "longitude"] - with mock.patch("warnings.warn") as warn: - self.cube.collapsed(coords, aggregator, weights=weights) + warn = mocker.patch("warnings.warn") + self.cube.collapsed(coords, aggregator, weights=weights) self._assert_nowarn_collapse_without_weight(coords, warn) - def test_lat_lon_weighted_aggregator_alt(self): + def test_lat_lon_weighted_aggregator_alt(self, mocker): # Collapse grid_latitude coordinate with weighted aggregator without # providing weights. Tests coordinate matching logic. aggregator = self._aggregator(False) coords = ["grid_latitude", "grid_longitude"] - with mock.patch("warnings.warn") as warn: - self.cube.collapsed(coords, aggregator) + warn = mocker.patch("warnings.warn") + self.cube.collapsed(coords, aggregator) coords = [coord for coord in coords if "latitude" in coord] self._assert_warn_collapse_without_weight(coords, warn) - def test_no_lat_weighted_aggregator_mixed(self): + def test_no_lat_weighted_aggregator_mixed(self, mocker): # Collapse grid_latitude and an unmatched coordinate (not lat/lon) # with weighted aggregator without providing weights. # Tests coordinate matching logic. aggregator = self._aggregator(False) coords = ["wibble"] - with mock.patch("warnings.warn") as warn: - self.cube.collapsed(coords, aggregator) + warn = mocker.patch("warnings.warn") + self.cube.collapsed(coords, aggregator) self._assert_nowarn_collapse_without_weight(coords, warn) -class Test_collapsed_coord_with_3_bounds(tests.IrisTest): - def setUp(self): +class Test_collapsed_coord_with_3_bounds: + @pytest.fixture(autouse=True) + def _setup(self): self.cube = Cube([1, 2]) bounds = [[0.0, 1.0, 2.0], [2.0, 3.0, 4.0]] @@ -716,59 +734,59 @@ def _assert_warn_cannot_check_contiguity(self, warn): f"bounds. Metadata may not be fully descriptive for " f"'{coord}'. Ignoring bounds." ) - self.assertIn( - mock.call(msg, category=IrisVagueMetadataWarning), - warn.call_args_list, + assert ( + mock.call(msg, category=IrisVagueMetadataWarning) in warn.call_args_list ) def _assert_cube_as_expected(self, cube): """Ensure that cube data and coordinates are as expected.""" - self.assertArrayEqual(cube.data, np.array(3)) + _shared_utils.assert_array_equal(cube.data, np.array(3)) lat = cube.coord("latitude") - self.assertArrayAlmostEqual(lat.points, np.array([1.5])) - self.assertArrayAlmostEqual(lat.bounds, np.array([[1.0, 2.0]])) + _shared_utils.assert_array_almost_equal(lat.points, np.array([1.5])) + _shared_utils.assert_array_almost_equal(lat.bounds, np.array([[1.0, 2.0]])) lon = cube.coord("longitude") - self.assertArrayAlmostEqual(lon.points, np.array([1.5])) - self.assertArrayAlmostEqual(lon.bounds, np.array([[1.0, 2.0]])) + _shared_utils.assert_array_almost_equal(lon.points, np.array([1.5])) + _shared_utils.assert_array_almost_equal(lon.bounds, np.array([[1.0, 2.0]])) - def test_collapsed_lat_with_3_bounds(self): + def test_collapsed_lat_with_3_bounds(self, mocker): """Collapse latitude with 3 bounds.""" - with mock.patch("warnings.warn") as warn: - collapsed_cube = self.cube.collapsed("latitude", SUM) + warn = mocker.patch("warnings.warn") + collapsed_cube = self.cube.collapsed("latitude", SUM) self._assert_warn_cannot_check_contiguity(warn) self._assert_cube_as_expected(collapsed_cube) - def test_collapsed_lon_with_3_bounds(self): + def test_collapsed_lon_with_3_bounds(self, mocker): """Collapse longitude with 3 bounds.""" - with mock.patch("warnings.warn") as warn: - collapsed_cube = self.cube.collapsed("longitude", SUM) + warn = mocker.patch("warnings.warn") + collapsed_cube = self.cube.collapsed("longitude", SUM) self._assert_warn_cannot_check_contiguity(warn) self._assert_cube_as_expected(collapsed_cube) - def test_collapsed_lat_lon_with_3_bounds(self): + def test_collapsed_lat_lon_with_3_bounds(self, mocker): """Collapse latitude and longitude with 3 bounds.""" - with mock.patch("warnings.warn") as warn: - collapsed_cube = self.cube.collapsed(["latitude", "longitude"], SUM) + warn = mocker.patch("warnings.warn") + collapsed_cube = self.cube.collapsed(["latitude", "longitude"], SUM) self._assert_warn_cannot_check_contiguity(warn) self._assert_cube_as_expected(collapsed_cube) -class Test_summary(tests.IrisTest): - def setUp(self): +class Test_summary: + @pytest.fixture(autouse=True) + def _setup(self): self.cube = Cube(0) def test_cell_datetime_objects(self): self.cube.add_aux_coord(AuxCoord(42, units="hours since epoch")) summary = self.cube.summary() - self.assertIn("1970-01-02 18:00:00", summary) + assert "1970-01-02 18:00:00" in summary def test_scalar_str_coord(self): str_value = "foo" self.cube.add_aux_coord(AuxCoord(str_value)) summary = self.cube.summary() - self.assertIn(str_value, summary) + assert str_value in summary def test_ancillary_variable(self): cube = Cube(np.arange(6).reshape(2, 3)) @@ -779,7 +797,7 @@ def test_ancillary_variable(self): " Ancillary variables:\n" " status_flag x -" ) - self.assertEqual(expected_summary, cube.summary()) + assert expected_summary == cube.summary() def test_similar_coords(self): coord1 = AuxCoord(42, long_name="foo", attributes=dict(bar=np.array([2, 5]))) @@ -787,7 +805,7 @@ def test_similar_coords(self): coord2.attributes = dict(bar="baz") for coord in [coord1, coord2]: self.cube.add_aux_coord(coord) - self.assertIn("baz", self.cube.summary()) + assert "baz" in self.cube.summary() def test_long_components(self): # Check that components with long names 'stretch' the printout correctly. @@ -821,7 +839,7 @@ def test_long_components(self): # For lines with any columns : check that columns are where expected for col_ind in colon_inds: # Chop out chars before+after each expected column. - self.assertEqual(line[col_ind - 1 : col_ind + 2], " x ") + assert line[col_ind - 1 : col_ind + 2] == " x " # Finally also: compare old with new, but replacing new name and ignoring spacing differences def collapse_space(string): @@ -830,37 +848,38 @@ def collapse_space(string): string = string.replace(" ", " ") return string - self.assertEqual( - collapse_space(new_summary).replace(long_name, old_name), - collapse_space(original_summary), - ) + assert collapse_space(new_summary).replace( + long_name, old_name + ) == collapse_space(original_summary) -class Test_is_compatible(tests.IrisTest): - def setUp(self): +class Test_is_compatible: + @pytest.fixture(autouse=True) + def _setup(self): self.test_cube = Cube([1.0]) self.other_cube = self.test_cube.copy() def test_noncommon_array_attrs_compatible(self): # Non-common array attributes should be ok. self.test_cube.attributes["array_test"] = np.array([1.0, 2, 3]) - self.assertTrue(self.test_cube.is_compatible(self.other_cube)) + assert self.test_cube.is_compatible(self.other_cube) def test_matching_array_attrs_compatible(self): # Matching array attributes should be ok. self.test_cube.attributes["array_test"] = np.array([1.0, 2, 3]) self.other_cube.attributes["array_test"] = np.array([1.0, 2, 3]) - self.assertTrue(self.test_cube.is_compatible(self.other_cube)) + assert self.test_cube.is_compatible(self.other_cube) def test_different_array_attrs_incompatible(self): # Differing array attributes should make the cubes incompatible. self.test_cube.attributes["array_test"] = np.array([1.0, 2, 3]) self.other_cube.attributes["array_test"] = np.array([1.0, 2, 777.7]) - self.assertFalse(self.test_cube.is_compatible(self.other_cube)) + assert not self.test_cube.is_compatible(self.other_cube) -class Test_rolling_window(tests.IrisTest): - def setUp(self): +class Test_rolling_window: + @pytest.fixture(autouse=True) + def _setup(self): self.cube = Cube(np.arange(6), units="kg") self.multi_dim_cube = Cube(np.arange(36).reshape(6, 6)) val_coord = DimCoord([0, 1, 2, 3, 4, 5], long_name="val", units="s") @@ -902,8 +921,8 @@ def test_string_coord(self): ), long_name="month", ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("month"), month_coord) + assert res_cube.coord("val") == val_coord + assert res_cube.coord("month") == month_coord def test_kwargs(self): # Rolling window with missing data not tolerated @@ -917,7 +936,7 @@ def test_kwargs(self): mask=[True, False, False, True, True], dtype=np.float64, ) - self.assertMaskedArrayEqual(expected_result, res_cube.data) + _shared_utils.assert_masked_array_equal(expected_result, res_cube.data) def test_lazy(self): window = 2 @@ -925,58 +944,59 @@ def test_lazy(self): self.cube.data, mask=([True, False, False, False, True, False]) ) res_cube = self.cube.rolling_window("val", iris.analysis.MEAN, window, mdtol=0) - self.assertTrue(self.cube.has_lazy_data()) - self.assertTrue(res_cube.has_lazy_data()) + assert self.cube.has_lazy_data() + assert res_cube.has_lazy_data() expected_result = ma.array( [-99.0, 1.5, 2.5, -99.0, -99.0], mask=[True, False, False, True, True], dtype=np.float64, ) - self.assertMaskedArrayEqual(expected_result, res_cube.data) + _shared_utils.assert_masked_array_equal(expected_result, res_cube.data) def test_ancillary_variables_and_cell_measures_kept(self): res_cube = self.multi_dim_cube.rolling_window("val", self.mock_agg, 3) - self.assertEqual(res_cube.ancillary_variables(), [self.ancillary_variable]) - self.assertEqual(res_cube.cell_measures(), [self.cell_measure]) + assert res_cube.ancillary_variables() == [self.ancillary_variable] + assert res_cube.cell_measures() == [self.cell_measure] def test_ancillary_variables_and_cell_measures_removed(self): res_cube = self.multi_dim_cube.rolling_window("extra", self.mock_agg, 3) - self.assertEqual(res_cube.ancillary_variables(), []) - self.assertEqual(res_cube.cell_measures(), []) + assert res_cube.ancillary_variables() == [] + assert res_cube.cell_measures() == [] def test_weights_arr(self): weights = np.array([0, 0, 1, 0, 2]) res_cube = self.cube.rolling_window("val", SUM, 5, weights=weights) - np.testing.assert_array_equal(res_cube.data, [10, 13]) - self.assertEqual(res_cube.units, "kg") + _shared_utils.assert_array_equal(res_cube.data, [10, 13]) + assert res_cube.units == "kg" def test_weights_cube(self): weights = Cube([0, 0, 1, 0, 2], units="m2") res_cube = self.cube.rolling_window("val", SUM, 5, weights=weights) - np.testing.assert_array_equal(res_cube.data, [10, 13]) - self.assertEqual(res_cube.units, "kg m2") + _shared_utils.assert_array_equal(res_cube.data, [10, 13]) + assert res_cube.units == "kg m2" def test_weights_str(self): weights = "val" res_cube = self.cube.rolling_window("val", SUM, 6, weights=weights) - np.testing.assert_array_equal(res_cube.data, [55]) - self.assertEqual(res_cube.units, "kg s") + _shared_utils.assert_array_equal(res_cube.data, [55]) + assert res_cube.units == "kg s" def test_weights_dim_coord(self): weights = self.cube.coord("val") res_cube = self.cube.rolling_window("val", SUM, 6, weights=weights) - np.testing.assert_array_equal(res_cube.data, [55]) - self.assertEqual(res_cube.units, "kg s") + _shared_utils.assert_array_equal(res_cube.data, [55]) + assert res_cube.units == "kg s" -class Test_slices_dim_order(tests.IrisTest): +class Test_slices_dim_order: """Test the capability of iris.cube.Cube.slices(). Test the capability of iris.cube.Cube.slices(), including its ability to correctly re-order the dimensions. """ - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): """Setup a 4D iris cube, each dimension is length 1. The dimensions are; dim1: time @@ -1041,16 +1061,17 @@ def check_order(self, dim1, dim2, dim3, dim_to_remove): sliced_cube = next(self.cube.slices([dim1, dim2, dim3])) sliced_cube.remove_coord(dim_to_remove) expected_cube = self.expected_cube_setup(dim1, dim2, dim3) - self.assertEqual(sliced_cube, expected_cube) + assert sliced_cube == expected_cube def test_all_permutations(self): for perm in permutations(["time", "height", "latitude", "longitude"]): self.check_order(*perm) -@tests.skip_data -class Test_slices_over(tests.IrisTest): - def setUp(self): +@_shared_utils.skip_data +class Test_slices_over: + @pytest.fixture(autouse=True) + def _setup(self): self.cube = stock.realistic_4d()[:, :7, :10, :10] # Define expected iterators for 1D and 2D test cases. self.exp_iter_1d = range(len(self.cube.coord("model_level_number").points)) @@ -1063,30 +1084,30 @@ def test_1d_slice_coord_given(self): res = self.cube.slices_over(self.cube.coord("model_level_number")) for i, res_cube in zip(self.exp_iter_1d, res): expected = self.cube[:, i] - self.assertEqual(res_cube, expected) + assert res_cube == expected def test_1d_slice_nonexistent_coord_given(self): - with self.assertRaises(CoordinateNotFoundError): + with pytest.raises(CoordinateNotFoundError): _ = self.cube.slices_over(self.cube.coord("wibble")) def test_1d_slice_coord_name_given(self): res = self.cube.slices_over("model_level_number") for i, res_cube in zip(self.exp_iter_1d, res): expected = self.cube[:, i] - self.assertEqual(res_cube, expected) + assert res_cube == expected def test_1d_slice_nonexistent_coord_name_given(self): - with self.assertRaises(CoordinateNotFoundError): + with pytest.raises(CoordinateNotFoundError): _ = self.cube.slices_over("wibble") def test_1d_slice_dimension_given(self): res = self.cube.slices_over(1) for i, res_cube in zip(self.exp_iter_1d, res): expected = self.cube[:, i] - self.assertEqual(res_cube, expected) + assert res_cube == expected def test_1d_slice_nonexistent_dimension_given(self): - with self.assertRaisesRegex(ValueError, "iterator over a dimension"): + with pytest.raises(ValueError, match="iterator over a dimension"): _ = self.cube.slices_over(self.cube.ndim + 1) def test_2d_slice_coord_given(self): @@ -1100,10 +1121,10 @@ def test_2d_slice_coord_given(self): # Replace the dimensions not iterated over with spanning slices. indices[2] = indices[3] = slice(None) expected = self.cube[tuple(indices)] - self.assertEqual(next(res), expected) + assert next(res) == expected def test_2d_slice_nonexistent_coord_given(self): - with self.assertRaises(CoordinateNotFoundError): + with pytest.raises(CoordinateNotFoundError): _ = self.cube.slices_over( [self.cube.coord("time"), self.cube.coord("wibble")] ) @@ -1117,10 +1138,10 @@ def test_2d_slice_coord_name_given(self): # Replace the dimensions not iterated over with spanning slices. indices[2] = indices[3] = slice(None) expected = self.cube[tuple(indices)] - self.assertEqual(next(res), expected) + assert next(res) == expected def test_2d_slice_nonexistent_coord_name_given(self): - with self.assertRaises(CoordinateNotFoundError): + with pytest.raises(CoordinateNotFoundError): _ = self.cube.slices_over(["time", "wibble"]) def test_2d_slice_dimension_given(self): @@ -1132,7 +1153,7 @@ def test_2d_slice_dimension_given(self): # Replace the dimensions not iterated over with spanning slices. indices[2] = indices[3] = slice(None) expected = self.cube[tuple(indices)] - self.assertEqual(next(res), expected) + assert next(res) == expected def test_2d_slice_reversed_dimension_given(self): # Confirm that reversing the order of the dimensions returns the same @@ -1143,10 +1164,10 @@ def test_2d_slice_reversed_dimension_given(self): # Replace the dimensions not iterated over with spanning slices. indices[2] = indices[3] = slice(None) expected = self.cube[tuple(indices)] - self.assertEqual(next(res), expected) + assert next(res) == expected def test_2d_slice_nonexistent_dimension_given(self): - with self.assertRaisesRegex(ValueError, "iterator over a dimension"): + with pytest.raises(ValueError, match="iterator over a dimension"): _ = self.cube.slices_over([0, self.cube.ndim + 1]) def test_multidim_slice_coord_given(self): @@ -1160,24 +1181,24 @@ def test_multidim_slice_coord_given(self): # Replace the dimensions not iterated over with spanning slices. indices[0] = indices[1] = slice(None) expected = self.cube[tuple(indices)] - self.assertEqual(next(res), expected) + assert next(res) == expected def test_duplicate_coordinate_given(self): res = self.cube.slices_over([1, 1]) for i, res_cube in zip(self.exp_iter_1d, res): expected = self.cube[:, i] - self.assertEqual(res_cube, expected) + assert res_cube == expected def test_non_orthogonal_coordinates_given(self): res = self.cube.slices_over(["model_level_number", "sigma"]) for i, res_cube in zip(self.exp_iter_1d, res): expected = self.cube[:, i] - self.assertEqual(res_cube, expected) + assert res_cube == expected def test_nodimension(self): # Slicing over no dimension should return the whole cube. res = self.cube.slices_over([]) - self.assertEqual(next(res), self.cube) + assert next(res) == self.cube def create_cube(lon_min, lon_max, bounds=False): @@ -1244,178 +1265,200 @@ def create_cube(lon_min, lon_max, bounds=False): # Ensure all the other coordinates and factories are correctly preserved. -class Test_intersection__Metadata(tests.IrisTest): - def test_metadata(self): +class Test_intersection__Metadata: + def test_metadata(self, request): cube = create_cube(0, 360) result = cube.intersection(longitude=(170, 190)) - self.assertCMLApproxData(result) + _shared_utils.assert_CML_approx_data(request, result) - def test_metadata_wrapped(self): + def test_metadata_wrapped(self, request): cube = create_cube(-180, 180) result = cube.intersection(longitude=(170, 190)) - self.assertCMLApproxData(result) + _shared_utils.assert_CML_approx_data(request, result) # Explicitly check the handling of `circular` on the result. -class Test_intersection__Circular(tests.IrisTest): +class Test_intersection__Circular: def test_regional(self): cube = create_cube(0, 360) result = cube.intersection(longitude=(170, 190)) - self.assertFalse(result.coord("longitude").circular) + assert not result.coord("longitude").circular def test_regional_wrapped(self): cube = create_cube(-180, 180) result = cube.intersection(longitude=(170, 190)) - self.assertFalse(result.coord("longitude").circular) + assert not result.coord("longitude").circular def test_global(self): cube = create_cube(-180, 180) result = cube.intersection(longitude=(-180, 180)) - self.assertTrue(result.coord("longitude").circular) + assert result.coord("longitude").circular def test_global_wrapped(self): cube = create_cube(-180, 180) result = cube.intersection(longitude=(10, 370)) - self.assertTrue(result.coord("longitude").circular) + assert result.coord("longitude").circular # Check the various error conditions. -class Test_intersection__Invalid(tests.IrisTest): +class Test_intersection__Invalid: def test_reversed_min_max(self): cube = create_cube(0, 360) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): cube.intersection(longitude=(30, 10)) def test_dest_too_large(self): cube = create_cube(0, 360) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): cube.intersection(longitude=(30, 500)) def test_src_too_large(self): cube = create_cube(0, 400) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): cube.intersection(longitude=(10, 30)) def test_missing_coord(self): cube = create_cube(0, 360) - with self.assertRaises(iris.exceptions.CoordinateNotFoundError): + with pytest.raises(iris.exceptions.CoordinateNotFoundError): cube.intersection(parrots=(10, 30)) def test_multi_dim_coord(self): cube = create_cube(0, 360) - with self.assertRaises(iris.exceptions.CoordinateMultiDimError): + with pytest.raises(iris.exceptions.CoordinateMultiDimError): cube.intersection(surface_altitude=(10, 30)) def test_null_region(self): # 10 <= v < 10 cube = create_cube(0, 360) - with self.assertRaises(IndexError): + with pytest.raises(IndexError): cube.intersection(longitude=(10, 10, False, False)) -class Test_intersection__Lazy(tests.IrisTest): +class Test_intersection__Lazy: def test_real_data(self): cube = create_cube(0, 360) cube.data result = cube.intersection(longitude=(170, 190)) - self.assertFalse(result.has_lazy_data()) - self.assertArrayEqual(result.coord("longitude").points, np.arange(170, 191)) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) + assert not result.has_lazy_data() + _shared_utils.assert_array_equal( + result.coord("longitude").points, np.arange(170, 191) + ) + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 190 def test_real_data_wrapped(self): cube = create_cube(-180, 180) cube.data result = cube.intersection(longitude=(170, 190)) - self.assertFalse(result.has_lazy_data()) - self.assertArrayEqual(result.coord("longitude").points, np.arange(170, 191)) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) + assert not result.has_lazy_data() + _shared_utils.assert_array_equal( + result.coord("longitude").points, np.arange(170, 191) + ) + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 def test_lazy_data(self): cube = create_cube(0, 360) result = cube.intersection(longitude=(170, 190)) - self.assertTrue(result.has_lazy_data()) - self.assertArrayEqual(result.coord("longitude").points, np.arange(170, 191)) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) + assert result.has_lazy_data() + _shared_utils.assert_array_equal( + result.coord("longitude").points, np.arange(170, 191) + ) + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 190 def test_lazy_data_wrapped(self): cube = create_cube(-180, 180) result = cube.intersection(longitude=(170, 190)) - self.assertTrue(result.has_lazy_data()) - self.assertArrayEqual(result.coord("longitude").points, np.arange(170, 191)) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) + assert result.has_lazy_data() + _shared_utils.assert_array_equal( + result.coord("longitude").points, np.arange(170, 191) + ) + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 -class Test_intersection_Points(tests.IrisTest): +class Test_intersection_Points: def test_ignore_bounds(self): cube = create_cube(0, 30, bounds=True) result = cube.intersection(longitude=(9.5, 12.5), ignore_bounds=True) - self.assertArrayEqual(result.coord("longitude").points, np.arange(10, 13)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [9.5, 10.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [11.5, 12.5]) + _shared_utils.assert_array_equal( + result.coord("longitude").points, np.arange(10, 13) + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [9.5, 10.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [11.5, 12.5] + ) # Check what happens with a regional, points-only circular intersection # coordinate. -class Test_intersection__RegionalSrcModulus(tests.IrisTest): +class Test_intersection__RegionalSrcModulus: def test_request_subset(self): cube = create_cube(40, 60) result = cube.intersection(longitude=(45, 50)) - self.assertArrayEqual(result.coord("longitude").points, np.arange(45, 51)) - self.assertArrayEqual(result.data[0, 0], np.arange(5, 11)) + _shared_utils.assert_array_equal( + result.coord("longitude").points, np.arange(45, 51) + ) + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(5, 11)) def test_request_left(self): cube = create_cube(40, 60) result = cube.intersection(longitude=(35, 45)) - self.assertArrayEqual(result.coord("longitude").points, np.arange(40, 46)) - self.assertArrayEqual(result.data[0, 0], np.arange(0, 6)) + _shared_utils.assert_array_equal( + result.coord("longitude").points, np.arange(40, 46) + ) + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 6)) def test_request_right(self): cube = create_cube(40, 60) result = cube.intersection(longitude=(55, 65)) - self.assertArrayEqual(result.coord("longitude").points, np.arange(55, 60)) - self.assertArrayEqual(result.data[0, 0], np.arange(15, 20)) + _shared_utils.assert_array_equal( + result.coord("longitude").points, np.arange(55, 60) + ) + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(15, 20)) def test_request_superset(self): cube = create_cube(40, 60) result = cube.intersection(longitude=(35, 65)) - self.assertArrayEqual(result.coord("longitude").points, np.arange(40, 60)) - self.assertArrayEqual(result.data[0, 0], np.arange(0, 20)) + _shared_utils.assert_array_equal( + result.coord("longitude").points, np.arange(40, 60) + ) + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 20)) def test_request_subset_modulus(self): cube = create_cube(40, 60) result = cube.intersection(longitude=(45 + 360, 50 + 360)) - self.assertArrayEqual( + _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(45 + 360, 51 + 360) ) - self.assertArrayEqual(result.data[0, 0], np.arange(5, 11)) + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(5, 11)) def test_request_left_modulus(self): cube = create_cube(40, 60) result = cube.intersection(longitude=(35 + 360, 45 + 360)) - self.assertArrayEqual( + _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(40 + 360, 46 + 360) ) - self.assertArrayEqual(result.data[0, 0], np.arange(0, 6)) + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 6)) def test_request_right_modulus(self): cube = create_cube(40, 60) result = cube.intersection(longitude=(55 + 360, 65 + 360)) - self.assertArrayEqual( + _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(55 + 360, 60 + 360) ) - self.assertArrayEqual(result.data[0, 0], np.arange(15, 20)) + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(15, 20)) def test_request_superset_modulus(self): cube = create_cube(40, 60) result = cube.intersection(longitude=(35 + 360, 65 + 360)) - self.assertArrayEqual( + _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(40 + 360, 60 + 360) ) - self.assertArrayEqual(result.data[0, 0], np.arange(0, 20)) + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 20)) def test_tolerance_f4(self): cube = create_cube(0, 5) @@ -1423,10 +1466,10 @@ def test_tolerance_f4(self): [0.0, 3.74999905, 7.49999809, 11.24999714, 14.99999619], dtype="f4" ) result = cube.intersection(longitude=(0, 5)) - self.assertArrayAlmostEqual( + _shared_utils.assert_array_almost_equal( result.coord("longitude").points, np.array([0.0, 3.74999905]) ) - self.assertArrayEqual(result.data[0, 0], np.array([0, 1])) + _shared_utils.assert_array_equal(result.data[0, 0], np.array([0, 1])) def test_tolerance_f8(self): cube = create_cube(0, 5) @@ -1434,15 +1477,15 @@ def test_tolerance_f8(self): [0.0, 3.74999905, 7.49999809, 11.24999714, 14.99999619], dtype="f8" ) result = cube.intersection(longitude=(0, 5)) - self.assertArrayAlmostEqual( + _shared_utils.assert_array_almost_equal( result.coord("longitude").points, np.array([0.0, 3.74999905]) ) - self.assertArrayEqual(result.data[0, 0], np.array([0, 1])) + _shared_utils.assert_array_equal(result.data[0, 0], np.array([0, 1])) # Check what happens with a global, points-only circular intersection # coordinate. -class Test_intersection__GlobalSrcModulus(tests.IrisTest): +class Test_intersection__GlobalSrcModulus: def test_global_wrapped_extreme_increasing_base_period(self): # Ensure that we can correctly handle points defined at (base + period) cube = create_cube(-180.0, 180.0) @@ -1450,7 +1493,7 @@ def test_global_wrapped_extreme_increasing_base_period(self): # Redefine longitude so that points at (base + period) lons.points = np.linspace(-180.0, 180, lons.points.size) result = cube.intersection(longitude=(lons.points.min(), lons.points.max())) - self.assertArrayEqual(result.data, cube.data) + _shared_utils.assert_array_equal(result.data, cube.data) def test_global_wrapped_extreme_decreasing_base_period(self): # Ensure that we can correctly handle points defined at (base + period) @@ -1459,41 +1502,41 @@ def test_global_wrapped_extreme_decreasing_base_period(self): # Redefine longitude so that points at (base + period) lons.points = np.linspace(180.0, -180.0, lons.points.size) result = cube.intersection(longitude=(lons.points.min(), lons.points.max())) - self.assertArrayEqual(result.data, cube.data) + _shared_utils.assert_array_equal(result.data, cube.data) def test_global(self): cube = create_cube(0, 360) result = cube.intersection(longitude=(0, 360)) - self.assertEqual(result.coord("longitude").points[0], 0) - self.assertEqual(result.coord("longitude").points[-1], 359) - self.assertEqual(result.data[0, 0, 0], 0) - self.assertEqual(result.data[0, 0, -1], 359) + assert result.coord("longitude").points[0] == 0 + assert result.coord("longitude").points[-1] == 359 + assert result.data[0, 0, 0] == 0 + assert result.data[0, 0, -1] == 359 def test_global_wrapped(self): cube = create_cube(0, 360) result = cube.intersection(longitude=(-180, 180)) - self.assertEqual(result.coord("longitude").points[0], -180) - self.assertEqual(result.coord("longitude").points[-1], 179) - self.assertEqual(result.data[0, 0, 0], 180) - self.assertEqual(result.data[0, 0, -1], 179) + assert result.coord("longitude").points[0] == -180 + assert result.coord("longitude").points[-1] == 179 + assert result.data[0, 0, 0] == 180 + assert result.data[0, 0, -1] == 179 def test_aux_coord(self): cube = create_cube(0, 360) cube.replace_coord(iris.coords.AuxCoord.from_coord(cube.coord("longitude"))) result = cube.intersection(longitude=(0, 360)) - self.assertEqual(result.coord("longitude").points[0], 0) - self.assertEqual(result.coord("longitude").points[-1], 359) - self.assertEqual(result.data[0, 0, 0], 0) - self.assertEqual(result.data[0, 0, -1], 359) + assert result.coord("longitude").points[0] == 0 + assert result.coord("longitude").points[-1] == 359 + assert result.data[0, 0, 0] == 0 + assert result.data[0, 0, -1] == 359 def test_aux_coord_wrapped(self): cube = create_cube(0, 360) cube.replace_coord(iris.coords.AuxCoord.from_coord(cube.coord("longitude"))) result = cube.intersection(longitude=(-180, 180)) - self.assertEqual(result.coord("longitude").points[0], 0) - self.assertEqual(result.coord("longitude").points[-1], -1) - self.assertEqual(result.data[0, 0, 0], 0) - self.assertEqual(result.data[0, 0, -1], 359) + assert result.coord("longitude").points[0] == 0 + assert result.coord("longitude").points[-1] == -1 + assert result.data[0, 0, 0] == 0 + assert result.data[0, 0, -1] == 359 def test_aux_coord_non_contiguous_wrapped(self): cube = create_cube(0, 360) @@ -1501,103 +1544,103 @@ def test_aux_coord_non_contiguous_wrapped(self): coord.points = (coord.points * 1.5) % 360 cube.replace_coord(coord) result = cube.intersection(longitude=(-90, 90)) - self.assertEqual(result.coord("longitude").points[0], 0) - self.assertEqual(result.coord("longitude").points[-1], 90) - self.assertEqual(result.data[0, 0, 0], 0) - self.assertEqual(result.data[0, 0, -1], 300) + assert result.coord("longitude").points[0] == 0 + assert result.coord("longitude").points[-1] == 90 + assert result.data[0, 0, 0] == 0 + assert result.data[0, 0, -1] == 300 def test_decrementing(self): cube = create_cube(360, 0) result = cube.intersection(longitude=(40, 60)) - self.assertEqual(result.coord("longitude").points[0], 60) - self.assertEqual(result.coord("longitude").points[-1], 40) - self.assertEqual(result.data[0, 0, 0], 300) - self.assertEqual(result.data[0, 0, -1], 320) + assert result.coord("longitude").points[0] == 60 + assert result.coord("longitude").points[-1] == 40 + assert result.data[0, 0, 0] == 300 + assert result.data[0, 0, -1] == 320 def test_decrementing_wrapped(self): cube = create_cube(360, 0) result = cube.intersection(longitude=(-10, 10)) - self.assertEqual(result.coord("longitude").points[0], 10) - self.assertEqual(result.coord("longitude").points[-1], -10) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) + assert result.coord("longitude").points[0] == 10 + assert result.coord("longitude").points[-1] == -10 + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 def test_no_wrap_after_modulus(self): cube = create_cube(0, 360) result = cube.intersection(longitude=(170 + 360, 190 + 360)) - self.assertEqual(result.coord("longitude").points[0], 170 + 360) - self.assertEqual(result.coord("longitude").points[-1], 190 + 360) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) + assert result.coord("longitude").points[0] == 170 + 360 + assert result.coord("longitude").points[-1] == 190 + 360 + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 190 def test_wrap_after_modulus(self): cube = create_cube(-180, 180) result = cube.intersection(longitude=(170 + 360, 190 + 360)) - self.assertEqual(result.coord("longitude").points[0], 170 + 360) - self.assertEqual(result.coord("longitude").points[-1], 190 + 360) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) + assert result.coord("longitude").points[0] == 170 + 360 + assert result.coord("longitude").points[-1] == 190 + 360 + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 def test_select_by_coord(self): cube = create_cube(0, 360) coord = iris.coords.DimCoord(0, "longitude", units="degrees") result = cube.intersection(iris.coords.CoordExtent(coord, 10, 30)) - self.assertEqual(result.coord("longitude").points[0], 10) - self.assertEqual(result.coord("longitude").points[-1], 30) - self.assertEqual(result.data[0, 0, 0], 10) - self.assertEqual(result.data[0, 0, -1], 30) + assert result.coord("longitude").points[0] == 10 + assert result.coord("longitude").points[-1] == 30 + assert result.data[0, 0, 0] == 10 + assert result.data[0, 0, -1] == 30 def test_inclusive_exclusive(self): cube = create_cube(0, 360) result = cube.intersection(longitude=(170, 190, True, False)) - self.assertEqual(result.coord("longitude").points[0], 170) - self.assertEqual(result.coord("longitude").points[-1], 189) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 189) + assert result.coord("longitude").points[0] == 170 + assert result.coord("longitude").points[-1] == 189 + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 189 def test_exclusive_inclusive(self): cube = create_cube(0, 360) result = cube.intersection(longitude=(170, 190, False)) - self.assertEqual(result.coord("longitude").points[0], 171) - self.assertEqual(result.coord("longitude").points[-1], 190) - self.assertEqual(result.data[0, 0, 0], 171) - self.assertEqual(result.data[0, 0, -1], 190) + assert result.coord("longitude").points[0] == 171 + assert result.coord("longitude").points[-1] == 190 + assert result.data[0, 0, 0] == 171 + assert result.data[0, 0, -1] == 190 def test_exclusive_exclusive(self): cube = create_cube(0, 360) result = cube.intersection(longitude=(170, 190, False, False)) - self.assertEqual(result.coord("longitude").points[0], 171) - self.assertEqual(result.coord("longitude").points[-1], 189) - self.assertEqual(result.data[0, 0, 0], 171) - self.assertEqual(result.data[0, 0, -1], 189) + assert result.coord("longitude").points[0] == 171 + assert result.coord("longitude").points[-1] == 189 + assert result.data[0, 0, 0] == 171 + assert result.data[0, 0, -1] == 189 def test_single_point(self): # 10 <= v <= 10 cube = create_cube(0, 360) result = cube.intersection(longitude=(10, 10)) - self.assertEqual(result.coord("longitude").points[0], 10) - self.assertEqual(result.coord("longitude").points[-1], 10) - self.assertEqual(result.data[0, 0, 0], 10) - self.assertEqual(result.data[0, 0, -1], 10) + assert result.coord("longitude").points[0] == 10 + assert result.coord("longitude").points[-1] == 10 + assert result.data[0, 0, 0] == 10 + assert result.data[0, 0, -1] == 10 def test_two_points(self): # -1.5 <= v <= 0.5 cube = create_cube(0, 360) result = cube.intersection(longitude=(-1.5, 0.5)) - self.assertEqual(result.coord("longitude").points[0], -1) - self.assertEqual(result.coord("longitude").points[-1], 0) - self.assertEqual(result.data[0, 0, 0], 359) - self.assertEqual(result.data[0, 0, -1], 0) + assert result.coord("longitude").points[0] == -1 + assert result.coord("longitude").points[-1] == 0 + assert result.data[0, 0, 0] == 359 + assert result.data[0, 0, -1] == 0 def test_wrap_radians(self): cube = create_cube(0, 360) cube.coord("longitude").convert_units("radians") result = cube.intersection(longitude=(-1, 0.5)) - self.assertArrayAllClose( + _shared_utils.assert_array_all_close( result.coord("longitude").points, np.arange(-57, 29) * np.pi / 180 ) - self.assertEqual(result.data[0, 0, 0], 303) - self.assertEqual(result.data[0, 0, -1], 28) + assert result.data[0, 0, 0] == 303 + assert result.data[0, 0, -1] == 28 def test_tolerance_bug(self): # Floating point changes introduced by wrapping mean @@ -1606,7 +1649,7 @@ def test_tolerance_bug(self): cube = create_cube(0, 400) cube.coord("longitude").points = np.linspace(-179.55, 179.55, 400) result = cube.intersection(longitude=(125, 145)) - self.assertArrayAlmostEqual( + _shared_utils.assert_array_almost_equal( result.coord("longitude").points, cube.coord("longitude").points[339:361], ) @@ -1620,138 +1663,194 @@ def test_tolerance_bug_wrapped(self): cube.coord("longitude").points[389:] - 360.0, cube.coord("longitude").points[:11], ) - self.assertArrayAlmostEqual(result.coord("longitude").points, expected) + _shared_utils.assert_array_almost_equal( + result.coord("longitude").points, expected + ) # Check what happens with a global, points-and-bounds circular # intersection coordinate. -class Test_intersection__ModulusBounds(tests.IrisTest): +class Test_intersection__ModulusBounds: def test_global_wrapped_extreme_increasing_base_period(self): # Ensure that we can correctly handle bounds defined at (base + period) cube = create_cube(-180.0, 180.0, bounds=True) lons = cube.coord("longitude") result = cube.intersection(longitude=(lons.bounds.min(), lons.bounds.max())) - self.assertArrayEqual(result.data, cube.data) + _shared_utils.assert_array_equal(result.data, cube.data) def test_global_wrapped_extreme_decreasing_base_period(self): # Ensure that we can correctly handle bounds defined at (base + period) cube = create_cube(180.0, -180.0, bounds=True) lons = cube.coord("longitude") result = cube.intersection(longitude=(lons.bounds.min(), lons.bounds.max())) - self.assertArrayEqual(result.data, cube.data) + _shared_utils.assert_array_equal(result.data, cube.data) def test_misaligned_points_inside(self): cube = create_cube(0, 360, bounds=True) result = cube.intersection(longitude=(169.75, 190.25)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [169.5, 170.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [189.5, 190.5]) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [169.5, 170.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [189.5, 190.5] + ) + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 190 def test_misaligned_points_outside(self): cube = create_cube(0, 360, bounds=True) result = cube.intersection(longitude=(170.25, 189.75)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [169.5, 170.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [189.5, 190.5]) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [169.5, 170.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [189.5, 190.5] + ) + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 190 def test_misaligned_bounds(self): cube = create_cube(-180, 180, bounds=True) result = cube.intersection(longitude=(0, 360)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [-0.5, 0.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [358.5, 359.5]) - self.assertEqual(result.data[0, 0, 0], 180) - self.assertEqual(result.data[0, 0, -1], 179) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [-0.5, 0.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [358.5, 359.5] + ) + assert result.data[0, 0, 0] == 180 + assert result.data[0, 0, -1] == 179 def test_misaligned_bounds_decreasing(self): cube = create_cube(180, -180, bounds=True) result = cube.intersection(longitude=(0, 360)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [359.5, 358.5]) - self.assertArrayEqual(result.coord("longitude").points[-1], 0) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [0.5, -0.5]) - self.assertEqual(result.data[0, 0, 0], 181) - self.assertEqual(result.data[0, 0, -1], 180) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [359.5, 358.5] + ) + _shared_utils.assert_array_equal(result.coord("longitude").points[-1], 0) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [0.5, -0.5] + ) + assert result.data[0, 0, 0] == 181 + assert result.data[0, 0, -1] == 180 def test_aligned_inclusive(self): cube = create_cube(0, 360, bounds=True) result = cube.intersection(longitude=(170.5, 189.5)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [169.5, 170.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [189.5, 190.5]) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [169.5, 170.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [189.5, 190.5] + ) + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 190 def test_aligned_exclusive(self): cube = create_cube(0, 360, bounds=True) result = cube.intersection(longitude=(170.5, 189.5, False, False)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [170.5, 171.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [188.5, 189.5]) - self.assertEqual(result.data[0, 0, 0], 171) - self.assertEqual(result.data[0, 0, -1], 189) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [170.5, 171.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [188.5, 189.5] + ) + assert result.data[0, 0, 0] == 171 + assert result.data[0, 0, -1] == 189 def test_aligned_bounds_at_modulus(self): cube = create_cube(-179.5, 180.5, bounds=True) result = cube.intersection(longitude=(0, 360)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0, 1]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [359, 360]) - self.assertEqual(result.data[0, 0, 0], 180) - self.assertEqual(result.data[0, 0, -1], 179) + _shared_utils.assert_array_equal(result.coord("longitude").bounds[0], [0, 1]) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [359, 360] + ) + assert result.data[0, 0, 0] == 180 + assert result.data[0, 0, -1] == 179 def test_negative_aligned_bounds_at_modulus(self): cube = create_cube(0.5, 360.5, bounds=True) result = cube.intersection(longitude=(-180, 180)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [-180, -179]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [179, 180]) - self.assertEqual(result.data[0, 0, 0], 180) - self.assertEqual(result.data[0, 0, -1], 179) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [-180, -179] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [179, 180] + ) + assert result.data[0, 0, 0] == 180 + assert result.data[0, 0, -1] == 179 def test_negative_misaligned_points_inside(self): cube = create_cube(0, 360, bounds=True) result = cube.intersection(longitude=(-10.25, 10.25)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [-10.5, -9.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [9.5, 10.5]) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [-10.5, -9.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [9.5, 10.5] + ) + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 def test_negative_misaligned_points_outside(self): cube = create_cube(0, 360, bounds=True) result = cube.intersection(longitude=(-9.75, 9.75)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [-10.5, -9.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [9.5, 10.5]) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [-10.5, -9.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [9.5, 10.5] + ) + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 def test_negative_aligned_inclusive(self): cube = create_cube(0, 360, bounds=True) result = cube.intersection(longitude=(-10.5, 10.5)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [-11.5, -10.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [10.5, 11.5]) - self.assertEqual(result.data[0, 0, 0], 349) - self.assertEqual(result.data[0, 0, -1], 11) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [-11.5, -10.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [10.5, 11.5] + ) + assert result.data[0, 0, 0] == 349 + assert result.data[0, 0, -1] == 11 def test_negative_aligned_exclusive(self): cube = create_cube(0, 360, bounds=True) result = cube.intersection(longitude=(-10.5, 10.5, False, False)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [-10.5, -9.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [9.5, 10.5]) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [-10.5, -9.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [9.5, 10.5] + ) + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 def test_decrementing(self): cube = create_cube(360, 0, bounds=True) result = cube.intersection(longitude=(40, 60)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [60.5, 59.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [40.5, 39.5]) - self.assertEqual(result.data[0, 0, 0], 300) - self.assertEqual(result.data[0, 0, -1], 320) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [60.5, 59.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [40.5, 39.5] + ) + assert result.data[0, 0, 0] == 300 + assert result.data[0, 0, -1] == 320 def test_decrementing_wrapped(self): cube = create_cube(360, 0, bounds=True) result = cube.intersection(longitude=(-10, 10)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [10.5, 9.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [-9.5, -10.5]) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [10.5, 9.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [-9.5, -10.5] + ) + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 def test_numerical_tolerance(self): # test the tolerance on the coordinate value is not causing a @@ -1759,13 +1858,13 @@ def test_numerical_tolerance(self): cube = create_cube(28.5, 68.5, bounds=True) result = cube.intersection(longitude=(27.74, 68.61)) result_lons = result.coord("longitude") - self.assertAlmostEqual(result_lons.points[0], 28.5) - self.assertAlmostEqual(result_lons.points[-1], 67.5) + _shared_utils.assert_array_almost_equal(result_lons.points[0], 28.5) + _shared_utils.assert_array_almost_equal(result_lons.points[-1], 67.5) dtype = result_lons.dtype - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( result_lons.bounds[0], np.array([28.0, 29.0], dtype=dtype) ) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( result_lons.bounds[-1], np.array([67.0, 68.0], dtype=dtype) ) @@ -1778,13 +1877,13 @@ def test_numerical_tolerance_wrapped(self): lons.bounds = lons.bounds / 10 result = cube.intersection(longitude=(-60, 60)) result_lons = result.coord("longitude") - self.assertAlmostEqual(result_lons.points[0], -60.05) - self.assertAlmostEqual(result_lons.points[-1], 60.05) + _shared_utils.assert_array_almost_equal(result_lons.points[0], -60.05) + _shared_utils.assert_array_almost_equal(result_lons.points[-1], 60.05) dtype = result_lons.dtype - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( result_lons.bounds[0], np.array([-60.1, -60.0], dtype=dtype) ) - np.testing.assert_array_almost_equal( + _shared_utils.assert_array_almost_equal( result_lons.bounds[-1], np.array([60.0, 60.1], dtype=dtype) ) @@ -1793,55 +1892,79 @@ def test_ignore_bounds_wrapped(self): cube = create_cube(0, 360, bounds=True) result = cube.intersection(longitude=(10.25, 370.25), ignore_bounds=True) # Expect points 11..370 not bounds [9.5, 10.5] .. [368.5, 369.5] - self.assertArrayEqual(result.coord("longitude").bounds[0], [10.5, 11.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [369.5, 370.5]) - self.assertEqual(result.data[0, 0, 0], 11) - self.assertEqual(result.data[0, 0, -1], 10) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [10.5, 11.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [369.5, 370.5] + ) + assert result.data[0, 0, 0] == 11 + assert result.data[0, 0, -1] == 10 def test_within_cell(self): # Test cell is included when it entirely contains the requested range cube = create_cube(0, 10, bounds=True) result = cube.intersection(longitude=(0.7, 0.8)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0.5, 1.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [0.5, 1.5]) - self.assertEqual(result.data[0, 0, 0], 1) - self.assertEqual(result.data[0, 0, -1], 1) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [0.5, 1.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [0.5, 1.5] + ) + assert result.data[0, 0, 0] == 1 + assert result.data[0, 0, -1] == 1 def test_threshold_half(self): cube = create_cube(0, 10, bounds=True) result = cube.intersection(longitude=(1, 6.999), threshold=0.5) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0.5, 1.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [5.5, 6.5]) - self.assertEqual(result.data[0, 0, 0], 1) - self.assertEqual(result.data[0, 0, -1], 6) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [0.5, 1.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [5.5, 6.5] + ) + assert result.data[0, 0, 0] == 1 + assert result.data[0, 0, -1] == 6 def test_threshold_full(self): cube = create_cube(0, 10, bounds=True) result = cube.intersection(longitude=(0.5, 7.499), threshold=1) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0.5, 1.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [5.5, 6.5]) - self.assertEqual(result.data[0, 0, 0], 1) - self.assertEqual(result.data[0, 0, -1], 6) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [0.5, 1.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [5.5, 6.5] + ) + assert result.data[0, 0, 0] == 1 + assert result.data[0, 0, -1] == 6 def test_threshold_wrapped(self): # Test that a cell is wrapped to `maximum` if required to exceed # the threshold cube = create_cube(-180, 180, bounds=True) result = cube.intersection(longitude=(0.4, 360.4), threshold=0.2) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0.5, 1.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [359.5, 360.5]) - self.assertEqual(result.data[0, 0, 0], 181) - self.assertEqual(result.data[0, 0, -1], 180) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [0.5, 1.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [359.5, 360.5] + ) + assert result.data[0, 0, 0] == 181 + assert result.data[0, 0, -1] == 180 def test_threshold_wrapped_gap(self): # Test that a cell is wrapped to `maximum` if required to exceed # the threshold (even with a gap in the range) cube = create_cube(-180, 180, bounds=True) result = cube.intersection(longitude=(0.4, 360.35), threshold=0.2) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0.5, 1.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [359.5, 360.5]) - self.assertEqual(result.data[0, 0, 0], 181) - self.assertEqual(result.data[0, 0, -1], 180) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[0], [0.5, 1.5] + ) + _shared_utils.assert_array_equal( + result.coord("longitude").bounds[-1], [359.5, 360.5] + ) + assert result.data[0, 0, 0] == 181 + assert result.data[0, 0, -1] == 180 def unrolled_cube(): @@ -1859,29 +1982,34 @@ def unrolled_cube(): # Check what happens with a "unrolled" scatter-point data with a circular # intersection coordinate. -class Test_intersection__ScatterModulus(tests.IrisTest): +class Test_intersection__ScatterModulus: def test_subset(self): cube = unrolled_cube() result = cube.intersection(longitude=(5, 8)) - self.assertArrayEqual(result.coord("longitude").points, [5, 8, 5]) - self.assertArrayEqual(result.data, [0, 2, 3]) + _shared_utils.assert_array_equal(result.coord("longitude").points, [5, 8, 5]) + _shared_utils.assert_array_equal(result.data, [0, 2, 3]) def test_subset_wrapped(self): cube = unrolled_cube() result = cube.intersection(longitude=(5 + 360, 8 + 360)) - self.assertArrayEqual(result.coord("longitude").points, [365, 368, 365]) - self.assertArrayEqual(result.data, [0, 2, 3]) + _shared_utils.assert_array_equal( + result.coord("longitude").points, [365, 368, 365] + ) + _shared_utils.assert_array_equal(result.data, [0, 2, 3]) def test_superset(self): cube = unrolled_cube() result = cube.intersection(longitude=(0, 15)) - self.assertArrayEqual(result.coord("longitude").points, [5, 10, 8, 5, 3]) - self.assertArrayEqual(result.data, np.arange(5)) + _shared_utils.assert_array_equal( + result.coord("longitude").points, [5, 10, 8, 5, 3] + ) + _shared_utils.assert_array_equal(result.data, np.arange(5)) # Test the API of the cube interpolation method. -class Test_interpolate(tests.IrisTest): - def setUp(self): +class Test_interpolate: + @pytest.fixture(autouse=True) + def _setup(self): self.cube = stock.simple_2d() self.scheme = mock.Mock(name="interpolation scheme") @@ -1897,10 +2025,10 @@ def test_api(self): self.interpolator.assert_called_once_with( (0.5, 0.6), collapse_scalar=self.collapse_coord ) - self.assertIs(result, mock.sentinel.RESULT) + assert result is mock.sentinel.RESULT -class Test_regrid(tests.IrisTest): +class Test_regrid: def test(self): # Test that Cube.regrid() just defers to the regridder of the # given scheme. @@ -1922,21 +2050,21 @@ def regridder(self, src, target): cube = Cube(0) scheme = FakeScheme() result = cube.regrid(mock.sentinel.TARGET, scheme) - self.assertEqual(result, (scheme, cube, mock.sentinel.TARGET, cube)) + assert result == (scheme, cube, mock.sentinel.TARGET, cube) -class Test_copy(tests.IrisTest): +class Test_copy: def _check_copy(self, cube, cube_copy): - self.assertIsNot(cube_copy, cube) - self.assertEqual(cube_copy, cube) - self.assertIsNot(cube_copy.core_data(), cube.core_data()) + assert cube_copy is not cube + assert cube_copy == cube + assert cube_copy.core_data() is not cube.core_data() if ma.isMaskedArray(cube.data): - self.assertMaskedArrayEqual(cube_copy.data, cube.data) + _shared_utils.assert_masked_array_equal(cube_copy.data, cube.data) if cube.data.mask is not ma.nomask: # "No mask" is a constant : all other cases must be distinct. - self.assertIsNot(cube_copy.core_data().mask, cube.core_data().mask) + assert cube_copy.core_data().mask is not cube.core_data().mask else: - self.assertArrayEqual(cube_copy.data, cube.data) + _shared_utils.assert_array_equal(cube_copy.data, cube.data) def test(self): cube = stock.simple_3d() @@ -2003,7 +2131,7 @@ def _add_test_meshcube(self, nomesh=False, n_z=2, **meshcoord_kwargs): self.cube = cube -class Test_coords__mesh_coords(tests.IrisTest): +class Test_coords__mesh_coords: """Checking *only* the new "mesh_coords" keyword of the coord/coords methods. This is *not* attached to the existing tests for this area, as they are @@ -2011,7 +2139,8 @@ class Test_coords__mesh_coords(tests.IrisTest): """ - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): # Create a standard test cube with a variety of types of coord. _add_test_meshcube(self) @@ -2028,7 +2157,7 @@ def sortkey(item): items_a = sorted(items_a, key=sortkey) items_b = sorted(items_b, key=sortkey) - self.assertEqual(items_a, items_b) + assert items_a == items_b def test_coords__all__meshcoords_default(self): # coords() includes mesh-coords along with the others. @@ -2051,12 +2180,12 @@ def test_coords__all__meshcoords_omitted(self): def test_coords__axis__meshcoords(self): # Coord (singular) with axis + mesh_coords=True result = self.cube.coord(axis="x", mesh_coords=True) - self.assertIs(result, self.meshco_x) + assert result is self.meshco_x def test_coords__dimcoords__meshcoords(self): # dim_coords and mesh_coords should be mutually exclusive. result = self.cube.coords(dim_coords=True, mesh_coords=True) - self.assertEqual(result, []) + assert result == [] def test_coords__nodimcoords__meshcoords(self): # When mesh_coords=True, dim_coords=False should have no effect. @@ -2065,24 +2194,26 @@ def test_coords__nodimcoords__meshcoords(self): self._assert_lists_equal(expected, result) -class Test_mesh(tests.IrisTest): - def setUp(self): +class Test_mesh: + @pytest.fixture(autouse=True) + def _setup(self): # Create a standard test cube with a variety of types of coord. _add_test_meshcube(self) def test_mesh(self): result = self.cube.mesh - self.assertIs(result, self.mesh) + assert result is self.mesh def test_no_mesh(self): # Replace standard setUp cube with a no-mesh version. _add_test_meshcube(self, nomesh=True) result = self.cube.mesh - self.assertIsNone(result) + assert result is None -class Test_location(tests.IrisTest): - def setUp(self): +class Test_location: + @pytest.fixture(autouse=True) + def _setup(self): # Create a standard test cube with a variety of types of coord. _add_test_meshcube(self) @@ -2090,23 +2221,24 @@ def test_no_mesh(self): # Replace standard setUp cube with a no-mesh version. _add_test_meshcube(self, nomesh=True) result = self.cube.location - self.assertIsNone(result) + assert result is None def test_mesh(self): cube = self.cube result = cube.location - self.assertEqual(result, self.meshco_x.location) + assert result == self.meshco_x.location def test_alternate_location(self): # Replace standard setUp cube with an edge-based version. _add_test_meshcube(self, location="edge") cube = self.cube result = cube.location - self.assertEqual(result, "edge") + assert result == "edge" -class Test_mesh_dim(tests.IrisTest): - def setUp(self): +class Test_mesh_dim: + @pytest.fixture(autouse=True) + def _setup(self): # Create a standard test cube with a variety of types of coord. _add_test_meshcube(self) @@ -2114,12 +2246,12 @@ def test_no_mesh(self): # Replace standard setUp cube with a no-mesh version. _add_test_meshcube(self, nomesh=True) result = self.cube.mesh_dim() - self.assertIsNone(result) + assert result is None def test_mesh(self): cube = self.cube result = cube.mesh_dim() - self.assertEqual(result, 1) + assert result == 1 def test_alternate(self): # Replace standard setUp cube with an edge-based version. @@ -2128,16 +2260,17 @@ def test_alternate(self): # Transpose the cube : the mesh dim is then 0 cube.transpose() result = cube.mesh_dim() - self.assertEqual(result, 0) + assert result == 0 -class Test__init__mesh(tests.IrisTest): +class Test__init__mesh: """Test that creation with mesh-coords functions, and prevents a cube having incompatible mesh-coords. """ - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): # Create a standard test mesh and other useful components. mesh = sample_mesh() meshco = sample_meshcoord(mesh=mesh) @@ -2157,7 +2290,7 @@ def test_mesh(self): dim_coords_and_dims=[(dimco_z, 0), (dimco_mesh, 1)], aux_coords_and_dims=[(meshco, 1)], ) - self.assertEqual(cube.mesh, meshco.mesh) + assert cube.mesh == meshco.mesh def test_fail_dim_meshcoord(self): # As "test_mesh", but attempt to use the meshcoord as a dim-coord. @@ -2165,7 +2298,7 @@ def test_fail_dim_meshcoord(self): nz, n_faces = self.nz, self.n_faces dimco_z = DimCoord(np.arange(nz), long_name="z") meshco = self.meshco - with self.assertRaisesRegex(ValueError, "may not be an AuxCoord"): + with pytest.raises(ValueError, match="may not be an AuxCoord"): Cube( np.zeros((nz, n_faces)), dim_coords_and_dims=[(dimco_z, 0), (meshco, 1)], @@ -2179,7 +2312,7 @@ def test_multi_meshcoords(self): np.zeros(n_faces), aux_coords_and_dims=[(meshco_x, 0), (meshco_y, 0)], ) - self.assertEqual(cube.mesh, meshco_x.mesh) + assert cube.mesh == meshco_x.mesh def test_multi_meshcoords_same_axis(self): # *Not* an error, as long as the coords are distinguishable. @@ -2194,7 +2327,7 @@ def test_multi_meshcoords_same_axis(self): np.zeros(n_faces), aux_coords_and_dims=[(meshco_1, 0), (meshco_2, 0)], ) - self.assertEqual(cube.mesh, meshco_1.mesh) + assert cube.mesh == meshco_1.mesh def test_fail_meshcoords_different_locations(self): # Same as successful 'multi_mesh', but different locations. @@ -2203,10 +2336,10 @@ def test_fail_meshcoords_different_locations(self): meshco_1 = sample_meshcoord(axis="x", mesh=mesh, location="face") meshco_2 = sample_meshcoord(axis="y", mesh=mesh, location="edge") # They should still have the same *shape* (or would fail anyway) - self.assertEqual(meshco_1.shape, meshco_2.shape) + assert meshco_1.shape == meshco_2.shape n_faces = meshco_1.shape[0] msg = "does not match existing cube location" - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): Cube( np.zeros(n_faces), aux_coords_and_dims=[(meshco_1, 0), (meshco_2, 0)], @@ -2226,7 +2359,7 @@ def test_fail_meshcoords_different_meshes(self): meshco_y = sample_meshcoord(axis="y") # Own (different) mesh meshco_y.mesh.long_name = "new_name" n_faces = meshco_x.shape[0] - with self.assertRaisesRegex(ValueError, "MeshXY.* does not match"): + with pytest.raises(ValueError, match="MeshXY.* does not match"): Cube( np.zeros(n_faces), aux_coords_and_dims=[(meshco_x, 0), (meshco_y, 0)], @@ -2240,20 +2373,21 @@ def test_fail_meshcoords_different_dims(self): meshco_x = sample_meshcoord(mesh=mesh, axis="x") meshco_y = sample_meshcoord(mesh=mesh, axis="y") msg = "does not match existing cube mesh dimension" - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): Cube( np.zeros((n_z, n_faces)), aux_coords_and_dims=[(meshco_x, 1), (meshco_y, 0)], ) -class Test__add_aux_coord__mesh(tests.IrisTest): +class Test__add_aux_coord__mesh: """Test that "Cube.add_aux_coord" functions with a mesh-coord, and prevents a cube having incompatible mesh-coords. """ - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): _add_test_meshcube(self) # Remove the existing "meshco_y", so we can add similar ones without # needing to distinguish from the existing. @@ -2264,7 +2398,7 @@ def test_add_compatible(self): meshco_y = self.meshco_y # Add the y-meshco back into the cube. cube.add_aux_coord(meshco_y, 1) - self.assertIn(meshco_y, cube.coords(mesh_coords=True)) + assert meshco_y in cube.coords(mesh_coords=True) def test_add_multiple(self): # Show that we can add extra mesh coords. @@ -2276,7 +2410,7 @@ def test_add_multiple(self): new_meshco_y = meshco_y.copy() new_meshco_y.rename("alternative") cube.add_aux_coord(new_meshco_y, 1) - self.assertEqual(len(cube.coords(mesh_coords=True)), 3) + assert len(cube.coords(mesh_coords=True)) == 3 def test_add_equal_mesh(self): # Make a duplicate y-meshco, and rename so it can add into the cube. @@ -2284,7 +2418,7 @@ def test_add_equal_mesh(self): # Create 'meshco_y' duplicate, but a new mesh meshco_y = sample_meshcoord(axis="y") cube.add_aux_coord(meshco_y, 1) - self.assertIn(meshco_y, cube.coords(mesh_coords=True)) + assert meshco_y in cube.coords(mesh_coords=True) def test_fail_different_mesh(self): # Make a duplicate y-meshco, and rename so it can add into the cube. @@ -2293,7 +2427,7 @@ def test_fail_different_mesh(self): meshco_y = sample_meshcoord(axis="y") meshco_y.mesh.long_name = "new_name" msg = "does not match existing cube mesh" - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): cube.add_aux_coord(meshco_y, 1) def test_fail_different_location(self): @@ -2306,7 +2440,7 @@ def test_fail_different_location(self): # Create a new meshco_y, same mesh but based on edges. meshco_y = sample_meshcoord(axis="y", mesh=self.mesh, location="edge") msg = "does not match existing cube location" - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): cube.add_aux_coord(meshco_y, 1) def test_fail_different_dimension(self): @@ -2319,11 +2453,11 @@ def test_fail_different_dimension(self): # Attempt to re-attach the 'y' meshcoord, to a different cube dimension. msg = "does not match existing cube mesh dimension" - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): cube.add_aux_coord(meshco_y, 0) -class Test__add_dim_coord__mesh(tests.IrisTest): +class Test__add_dim_coord__mesh: """Test that "Cube.add_dim_coord" cannot work with a mesh-coord.""" def test(self): @@ -2331,11 +2465,11 @@ def test(self): mesh = sample_mesh(n_faces=2) meshco = sample_meshcoord(mesh=mesh) cube = Cube([0, 1]) - with self.assertRaisesRegex(ValueError, "may not be an AuxCoord"): + with pytest.raises(ValueError, match="may not be an AuxCoord"): cube.add_dim_coord(meshco, 0) -class Test__eq__mesh(tests.IrisTest): +class Test__eq__mesh: """Check that cubes with meshes support == as expected. Note: there is no special code for this in iris.cube.Cube : it is @@ -2343,21 +2477,22 @@ class Test__eq__mesh(tests.IrisTest): """ - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): # Create a 'standard' test cube. _add_test_meshcube(self) def test_copied_cube_match(self): cube = self.cube cube2 = cube.copy() - self.assertEqual(cube, cube2) + assert cube == cube2 def test_equal_mesh_match(self): cube1 = self.cube # re-create an identical cube, using the same mesh. _add_test_meshcube(self) cube2 = self.cube - self.assertEqual(cube1, cube2) + assert cube1 == cube2 def test_new_mesh_different(self): cube1 = self.cube @@ -2365,11 +2500,12 @@ def test_new_mesh_different(self): _add_test_meshcube(self) self.cube.mesh.long_name = "new_name" cube2 = self.cube - self.assertNotEqual(cube1, cube2) + assert cube1 != cube2 -class Test_dtype(tests.IrisTest): - def setUp(self): +class Test_dtype: + @pytest.fixture(autouse=True) + def _setup(self): self.dtypes = ( np.dtype("int"), np.dtype("uint"), @@ -2381,56 +2517,56 @@ def test_real_data(self): for dtype in self.dtypes: data = np.array([0, 1], dtype=dtype) cube = Cube(data) - self.assertEqual(cube.dtype, dtype) + assert cube.dtype == dtype def test_real_data_masked__mask_unset(self): for dtype in self.dtypes: data = ma.array([0, 1], dtype=dtype) cube = Cube(data) - self.assertEqual(cube.dtype, dtype) + assert cube.dtype == dtype def test_real_data_masked__mask_set(self): for dtype in self.dtypes: data = ma.array([0, 1], dtype=dtype) data[0] = ma.masked cube = Cube(data) - self.assertEqual(cube.dtype, dtype) + assert cube.dtype == dtype def test_lazy_data(self): for dtype in self.dtypes: data = np.array([0, 1], dtype=dtype) cube = Cube(as_lazy_data(data)) - self.assertEqual(cube.dtype, dtype) + assert cube.dtype == dtype # Check that accessing the dtype does not trigger loading # of the data. - self.assertTrue(cube.has_lazy_data()) + assert cube.has_lazy_data() def test_lazy_data_masked__mask_unset(self): for dtype in self.dtypes: data = ma.array([0, 1], dtype=dtype) cube = Cube(as_lazy_data(data)) - self.assertEqual(cube.dtype, dtype) + assert cube.dtype == dtype # Check that accessing the dtype does not trigger loading # of the data. - self.assertTrue(cube.has_lazy_data()) + assert cube.has_lazy_data() def test_lazy_data_masked__mask_set(self): for dtype in self.dtypes: data = ma.array([0, 1], dtype=dtype) data[0] = ma.masked cube = Cube(as_lazy_data(data)) - self.assertEqual(cube.dtype, dtype) + assert cube.dtype == dtype # Check that accessing the dtype does not trigger loading # of the data. - self.assertTrue(cube.has_lazy_data()) + assert cube.has_lazy_data() -class TestSubset(tests.IrisTest): +class TestSubset: def test_scalar_coordinate(self): cube = Cube(0, long_name="apricot", units="1") cube.add_aux_coord(DimCoord([0], long_name="banana", units="1")) result = cube.subset(cube.coord("banana")) - self.assertEqual(cube, result) + assert cube == result def test_dimensional_coordinate(self): cube = Cube(np.zeros((4)), long_name="tinned_peach", units="1") @@ -2439,52 +2575,52 @@ def test_dimensional_coordinate(self): 0, ) result = cube.subset(cube.coord("sixteen_ton_weight")) - self.assertEqual(cube, result) + assert cube == result def test_missing_coordinate(self): cube = Cube(0, long_name="raspberry", units="1") cube.add_aux_coord(DimCoord([0], long_name="loganberry", units="1")) bad_coord = DimCoord([0], long_name="tiger", units="1") - self.assertRaises(CoordinateNotFoundError, cube.subset, bad_coord) + pytest.raises(CoordinateNotFoundError, cube.subset, bad_coord) def test_different_coordinate(self): cube = Cube(0, long_name="raspberry", units="1") cube.add_aux_coord(DimCoord([0], long_name="loganberry", units="1")) different_coord = DimCoord([2], long_name="loganberry", units="1") result = cube.subset(different_coord) - self.assertEqual(result, None) + assert result is None def test_different_coordinate_vector(self): cube = Cube([0, 1], long_name="raspberry", units="1") cube.add_dim_coord(DimCoord([0, 1], long_name="loganberry", units="1"), 0) different_coord = DimCoord([2], long_name="loganberry", units="1") result = cube.subset(different_coord) - self.assertEqual(result, None) + assert result is None def test_not_coordinate(self): cube = Cube(0, long_name="peach", units="1") cube.add_aux_coord(DimCoord([0], long_name="crocodile", units="1")) - self.assertRaises(ValueError, cube.subset, "Pointed Stick") + pytest.raises(ValueError, cube.subset, "Pointed Stick") -class Test_add_metadata(tests.IrisTest): +class Test_add_metadata: def test_add_dim_coord(self): cube = Cube(np.arange(3)) x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") cube.add_dim_coord(x_coord, 0) - self.assertEqual(cube.coord("x"), x_coord) + assert cube.coord("x") == x_coord def test_add_aux_coord(self): cube = Cube(np.arange(6).reshape(2, 3)) x_coord = AuxCoord(points=np.arange(6).reshape(2, 3), long_name="x") cube.add_aux_coord(x_coord, [0, 1]) - self.assertEqual(cube.coord("x"), x_coord) + assert cube.coord("x") == x_coord def test_add_cell_measure(self): cube = Cube(np.arange(6).reshape(2, 3)) a_cell_measure = CellMeasure(np.arange(6).reshape(2, 3), long_name="area") cube.add_cell_measure(a_cell_measure, [0, 1]) - self.assertEqual(cube.cell_measure("area"), a_cell_measure) + assert cube.cell_measure("area") == a_cell_measure def test_add_ancillary_variable(self): cube = Cube(np.arange(6).reshape(2, 3)) @@ -2492,9 +2628,7 @@ def test_add_ancillary_variable(self): data=np.arange(6).reshape(2, 3), long_name="detection quality" ) cube.add_ancillary_variable(ancillary_variable, [0, 1]) - self.assertEqual( - cube.ancillary_variable("detection quality"), ancillary_variable - ) + assert cube.ancillary_variable("detection quality") == ancillary_variable def test_add_valid_aux_factory(self): cube = Cube(np.arange(8).reshape(2, 2, 2)) @@ -2505,7 +2639,7 @@ def test_add_valid_aux_factory(self): cube.add_aux_coord(sigma, 0) cube.add_aux_coord(orog, (1, 2)) factory = HybridHeightFactory(delta=delta, sigma=sigma, orography=orog) - self.assertIsNone(cube.add_aux_factory(factory)) + assert cube.add_aux_factory(factory) is None def test_error_for_add_invalid_aux_factory(self): cube = Cube(np.arange(8).reshape(2, 2, 2), long_name="bar") @@ -2517,12 +2651,13 @@ def test_error_for_add_invalid_aux_factory(self): # Note orography is not added to the cube here factory = HybridHeightFactory(delta=delta, sigma=sigma, orography=orog) expected_error = "foo coordinate for factory is not present on cube bar" - with self.assertRaisesRegex(ValueError, expected_error): + with pytest.raises(ValueError, match=expected_error): cube.add_aux_factory(factory) -class Test_remove_metadata(tests.IrisTest): - def setUp(self): +class Test_remove_metadata: + @pytest.fixture(autouse=True) + def _setup(self): cube = Cube(np.arange(6).reshape(2, 3)) x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") cube.add_dim_coord(x_coord, 1) @@ -2542,45 +2677,42 @@ def setUp(self): def test_remove_dim_coord(self): self.cube.remove_coord(self.cube.coord("x")) - self.assertEqual(self.cube.coords("x"), []) + assert self.cube.coords("x") == [] def test_remove_aux_coord(self): self.cube.remove_coord(self.cube.coord("z")) - self.assertEqual(self.cube.coords("z"), []) + assert self.cube.coords("z") == [] def test_remove_cell_measure(self): self.cube.remove_cell_measure(self.cube.cell_measure("area")) - self.assertEqual( - self.cube._cell_measures_and_dims, [(self.b_cell_measure, (0, 1))] - ) + assert self.cube._cell_measures_and_dims == [(self.b_cell_measure, (0, 1))] def test_remove_cell_measure_by_name(self): self.cube.remove_cell_measure("area") - self.assertEqual( - self.cube._cell_measures_and_dims, [(self.b_cell_measure, (0, 1))] - ) + assert self.cube._cell_measures_and_dims == [(self.b_cell_measure, (0, 1))] def test_fail_remove_cell_measure_by_name(self): - with self.assertRaises(CellMeasureNotFoundError): + with pytest.raises(CellMeasureNotFoundError): self.cube.remove_cell_measure("notarea") def test_remove_ancilliary_variable(self): self.cube.remove_ancillary_variable( self.cube.ancillary_variable("Quality of Detection") ) - self.assertEqual(self.cube._ancillary_variables_and_dims, []) + assert self.cube._ancillary_variables_and_dims == [] def test_remove_ancilliary_variable_by_name(self): self.cube.remove_ancillary_variable("Quality of Detection") - self.assertEqual(self.cube._ancillary_variables_and_dims, []) + assert self.cube._ancillary_variables_and_dims == [] def test_fail_remove_ancilliary_variable_by_name(self): - with self.assertRaises(AncillaryVariableNotFoundError): + with pytest.raises(AncillaryVariableNotFoundError): self.cube.remove_ancillary_variable("notname") -class TestCoords(tests.IrisTest): - def setUp(self): +class TestCoords: + @pytest.fixture(autouse=True) + def _setup(self): cube = Cube(np.arange(6).reshape(2, 3)) x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") cube.add_dim_coord(x_coord, 1) @@ -2594,19 +2726,20 @@ def test_bad_coord(self): "Expected to find exactly 1 coordinate matching the given " "'x' coordinate's metadata, but found none." ) - with self.assertRaisesRegex(CoordinateNotFoundError, re): + with pytest.raises(CoordinateNotFoundError, match=re): _ = self.cube.coord(bad_coord) -class Test_coord_division_units(tests.IrisTest): +class Test_coord_division_units: def test(self): aux = AuxCoord(1, long_name="length", units="metres") cube = Cube(1, units="seconds") - self.assertEqual((aux / cube).units, "m.s-1") + assert (aux / cube).units == "m.s-1" -class Test__getitem_CellMeasure(tests.IrisTest): - def setUp(self): +class Test__getitem_CellMeasure: + @pytest.fixture(autouse=True) + def _setup(self): cube = Cube(np.arange(6).reshape(2, 3)) x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") cube.add_dim_coord(x_coord, 1) @@ -2620,17 +2753,18 @@ def setUp(self): def test_cell_measure_2d(self): result = self.cube[0:2, 0:2] - self.assertEqual(len(result.cell_measures()), 1) - self.assertEqual(result.shape, result.cell_measures()[0].data.shape) + assert len(result.cell_measures()) == 1 + assert result.shape == result.cell_measures()[0].data.shape def test_cell_measure_1d(self): result = self.cube[0, 0:2] - self.assertEqual(len(result.cell_measures()), 1) - self.assertEqual(result.shape, result.cell_measures()[0].data.shape) + assert len(result.cell_measures()) == 1 + assert result.shape == result.cell_measures()[0].data.shape -class Test__getitem_AncillaryVariables(tests.IrisTest): - def setUp(self): +class Test__getitem_AncillaryVariables: + @pytest.fixture(autouse=True) + def _setup(self): cube = Cube(np.arange(6).reshape(2, 3)) x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") cube.add_dim_coord(x_coord, 1) @@ -2646,17 +2780,18 @@ def setUp(self): def test_ancillary_variables_2d(self): result = self.cube[0:2, 0:2] - self.assertEqual(len(result.ancillary_variables()), 1) - self.assertEqual(result.shape, result.ancillary_variables()[0].data.shape) + assert len(result.ancillary_variables()) == 1 + assert result.shape == result.ancillary_variables()[0].data.shape def test_ancillary_variables_1d(self): result = self.cube[0, 0:2] - self.assertEqual(len(result.ancillary_variables()), 1) - self.assertEqual(result.shape, result.ancillary_variables()[0].data.shape) + assert len(result.ancillary_variables()) == 1 + assert result.shape == result.ancillary_variables()[0].data.shape -class TestAncillaryVariables(tests.IrisTest): - def setUp(self): +class TestAncillaryVariables: + @pytest.fixture(autouse=True) + def _setup(self): cube = Cube(10 * np.arange(6).reshape(2, 3)) self.ancill_var = AncillaryVariable( np.arange(6).reshape(2, 3), @@ -2668,49 +2803,50 @@ def setUp(self): def test_get_ancillary_variable(self): ancill_var = self.cube.ancillary_variable("number_of_observations") - self.assertEqual(ancill_var, self.ancill_var) + assert ancill_var == self.ancill_var def test_get_ancillary_variables(self): ancill_vars = self.cube.ancillary_variables("number_of_observations") - self.assertEqual(len(ancill_vars), 1) - self.assertEqual(ancill_vars[0], self.ancill_var) + assert len(ancill_vars) == 1 + assert ancill_vars[0] == self.ancill_var def test_get_ancillary_variable_obj(self): ancill_vars = self.cube.ancillary_variables(self.ancill_var) - self.assertEqual(len(ancill_vars), 1) - self.assertEqual(ancill_vars[0], self.ancill_var) + assert len(ancill_vars) == 1 + assert ancill_vars[0] == self.ancill_var def test_fail_get_ancillary_variables(self): - with self.assertRaises(AncillaryVariableNotFoundError): + with pytest.raises(AncillaryVariableNotFoundError): self.cube.ancillary_variable("other_ancill_var") def test_fail_get_ancillary_variables_obj(self): ancillary_variable = self.ancill_var.copy() ancillary_variable.long_name = "Number of observations at site" - with self.assertRaises(AncillaryVariableNotFoundError): + with pytest.raises(AncillaryVariableNotFoundError): self.cube.ancillary_variable(ancillary_variable) def test_ancillary_variable_dims(self): ancill_var_dims = self.cube.ancillary_variable_dims(self.ancill_var) - self.assertEqual(ancill_var_dims, (0, 1)) + assert ancill_var_dims == (0, 1) def test_fail_ancill_variable_dims(self): ancillary_variable = self.ancill_var.copy() ancillary_variable.long_name = "Number of observations at site" - with self.assertRaises(AncillaryVariableNotFoundError): + with pytest.raises(AncillaryVariableNotFoundError): self.cube.ancillary_variable_dims(ancillary_variable) def test_ancillary_variable_dims_by_name(self): ancill_var_dims = self.cube.ancillary_variable_dims("number_of_observations") - self.assertEqual(ancill_var_dims, (0, 1)) + assert ancill_var_dims == (0, 1) def test_fail_ancillary_variable_dims_by_name(self): - with self.assertRaises(AncillaryVariableNotFoundError): + with pytest.raises(AncillaryVariableNotFoundError): self.cube.ancillary_variable_dims("notname") -class TestCellMeasures(tests.IrisTest): - def setUp(self): +class TestCellMeasures: + @pytest.fixture(autouse=True) + def _setup(self): cube = Cube(np.arange(6).reshape(2, 3)) x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") cube.add_dim_coord(x_coord, 1) @@ -2724,49 +2860,50 @@ def setUp(self): def test_get_cell_measure(self): cm = self.cube.cell_measure("area") - self.assertEqual(cm, self.a_cell_measure) + assert cm == self.a_cell_measure def test_get_cell_measures(self): cms = self.cube.cell_measures() - self.assertEqual(len(cms), 1) - self.assertEqual(cms[0], self.a_cell_measure) + assert len(cms) == 1 + assert cms[0] == self.a_cell_measure def test_get_cell_measures_obj(self): cms = self.cube.cell_measures(self.a_cell_measure) - self.assertEqual(len(cms), 1) - self.assertEqual(cms[0], self.a_cell_measure) + assert len(cms) == 1 + assert cms[0] == self.a_cell_measure def test_fail_get_cell_measure(self): - with self.assertRaises(CellMeasureNotFoundError): + with pytest.raises(CellMeasureNotFoundError): _ = self.cube.cell_measure("notarea") def test_fail_get_cell_measures_obj(self): a_cell_measure = self.a_cell_measure.copy() a_cell_measure.units = "km2" - with self.assertRaises(CellMeasureNotFoundError): + with pytest.raises(CellMeasureNotFoundError): _ = self.cube.cell_measure(a_cell_measure) def test_cell_measure_dims(self): cm_dims = self.cube.cell_measure_dims(self.a_cell_measure) - self.assertEqual(cm_dims, (0, 1)) + assert cm_dims == (0, 1) def test_fail_cell_measure_dims(self): a_cell_measure = self.a_cell_measure.copy() a_cell_measure.units = "km2" - with self.assertRaises(CellMeasureNotFoundError): + with pytest.raises(CellMeasureNotFoundError): _ = self.cube.cell_measure_dims(a_cell_measure) def test_cell_measure_dims_by_name(self): cm_dims = self.cube.cell_measure_dims("area") - self.assertEqual(cm_dims, (0, 1)) + assert cm_dims == (0, 1) def test_fail_cell_measure_dims_by_name(self): - with self.assertRaises(CellMeasureNotFoundError): + with pytest.raises(CellMeasureNotFoundError): self.cube.cell_measure_dims("notname") -class Test_transpose(tests.IrisTest): - def setUp(self): +class Test_transpose: + @pytest.fixture(autouse=True) + def _setup(self): self.data = np.arange(24).reshape(3, 2, 4) self.cube = Cube(self.data) self.lazy_cube = Cube(as_lazy_data(self.data)) @@ -2774,28 +2911,28 @@ def setUp(self): def test_lazy_data(self): cube = self.lazy_cube cube.transpose() - self.assertTrue(cube.has_lazy_data()) - self.assertArrayEqual(self.data.T, cube.data) + assert cube.has_lazy_data() + _shared_utils.assert_array_equal(self.data.T, cube.data) def test_real_data(self): self.cube.transpose() - self.assertFalse(self.cube.has_lazy_data()) - self.assertIs(self.data.base, self.cube.data.base) - self.assertArrayEqual(self.data.T, self.cube.data) + assert not self.cube.has_lazy_data() + assert self.data.base is self.cube.data.base + _shared_utils.assert_array_equal(self.data.T, self.cube.data) def test_real_data__new_order(self): new_order = [2, 0, 1] self.cube.transpose(new_order) - self.assertFalse(self.cube.has_lazy_data()) - self.assertIs(self.data.base, self.cube.data.base) - self.assertArrayEqual(self.data.transpose(new_order), self.cube.data) + assert not self.cube.has_lazy_data() + assert self.data.base is self.cube.data.base + _shared_utils.assert_array_equal(self.data.transpose(new_order), self.cube.data) def test_lazy_data__new_order(self): new_order = [2, 0, 1] cube = self.lazy_cube cube.transpose(new_order) - self.assertTrue(cube.has_lazy_data()) - self.assertArrayEqual(self.data.transpose(new_order), cube.data) + assert cube.has_lazy_data() + _shared_utils.assert_array_equal(self.data.transpose(new_order), cube.data) def test_lazy_data__transpose_order_ndarray(self): # Check that a transpose order supplied as an array does not trip up @@ -2803,31 +2940,31 @@ def test_lazy_data__transpose_order_ndarray(self): new_order = np.array([2, 0, 1]) cube = self.lazy_cube cube.transpose(new_order) - self.assertTrue(cube.has_lazy_data()) - self.assertArrayEqual(self.data.transpose(new_order), cube.data) + assert cube.has_lazy_data() + _shared_utils.assert_array_equal(self.data.transpose(new_order), cube.data) def test_bad_transpose_order(self): exp_emsg = "Incorrect number of dimensions" - with self.assertRaisesRegex(ValueError, exp_emsg): + with pytest.raises(ValueError, match=exp_emsg): self.cube.transpose([1]) def test_dim_coords(self): x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") self.cube.add_dim_coord(x_coord, 0) self.cube.transpose() - self.assertEqual(self.cube._dim_coords_and_dims, [(x_coord, 2)]) + assert self.cube._dim_coords_and_dims == [(x_coord, 2)] def test_aux_coords(self): x_coord = AuxCoord(points=np.array([[2, 3], [8, 4], [7, 9]]), long_name="x") self.cube.add_aux_coord(x_coord, (0, 1)) self.cube.transpose() - self.assertEqual(self.cube._aux_coords_and_dims, [(x_coord, (2, 1))]) + assert self.cube._aux_coords_and_dims == [(x_coord, (2, 1))] def test_cell_measures(self): area_cm = CellMeasure(np.arange(12).reshape(3, 4), long_name="area of cells") self.cube.add_cell_measure(area_cm, (0, 2)) self.cube.transpose() - self.assertEqual(self.cube._cell_measures_and_dims, [(area_cm, (2, 0))]) + assert self.cube._cell_measures_and_dims == [(area_cm, (2, 0))] def test_ancillary_variables(self): ancill_var = AncillaryVariable( @@ -2835,19 +2972,17 @@ def test_ancillary_variables(self): ) self.cube.add_ancillary_variable(ancill_var, (1, 2)) self.cube.transpose() - self.assertEqual( - self.cube._ancillary_variables_and_dims, [(ancill_var, (1, 0))] - ) + assert self.cube._ancillary_variables_and_dims == [(ancill_var, (1, 0))] -class Test_convert_units(tests.IrisTest): +class Test_convert_units: def test_convert_unknown_units(self): cube = iris.cube.Cube(1) emsg = ( "Cannot convert from unknown units. " 'The "cube.units" attribute may be set directly.' ) - with self.assertRaisesRegex(UnitConversionError, emsg): + with pytest.raises(UnitConversionError, match=emsg): cube.convert_units("mm day-1") def test_preserves_lazy(self): @@ -2856,8 +2991,8 @@ def test_preserves_lazy(self): cube = iris.cube.Cube(lazy_data, units="m") real_data_ft = Unit("m").convert(real_data, "ft") cube.convert_units("ft") - self.assertTrue(cube.has_lazy_data()) - self.assertArrayAllClose(cube.data, real_data_ft) + assert cube.has_lazy_data() + _shared_utils.assert_array_all_close(cube.data, real_data_ft) def test_unit_multiply(self): _client = Client() @@ -2868,22 +3003,22 @@ def test_unit_multiply(self): _client.close() -class Test__eq__data(tests.IrisTest): +class Test__eq__data: """Partial cube equality testing, for data type only.""" def test_cube_identical_to_itself(self): cube = Cube([1.0]) - self.assertTrue(cube == cube) + assert cube == cube def test_data_float_eq(self): cube1 = Cube([1.0]) cube2 = Cube([1.0]) - self.assertTrue(cube1 == cube2) + assert cube1 == cube2 def test_data_float_nan_eq(self): cube1 = Cube([np.nan, 1.0]) cube2 = Cube([np.nan, 1.0]) - self.assertTrue(cube1 == cube2) + assert cube1 == cube2 def test_data_float_eqtol(self): val1 = np.array(1.0, dtype=np.float32) @@ -2892,45 +3027,45 @@ def test_data_float_eqtol(self): val2 = np.array(1.0 + 1.0e-6, dtype=np.float32) cube1 = Cube([val1]) cube2 = Cube([val2]) - self.assertNotEqual(val1, val2) - self.assertTrue(cube1 == cube2) + assert val1 != val2 + assert cube1 == cube2 def test_data_float_not_eq(self): val1 = 1.0 val2 = 1.0 + 1.0e-4 cube1 = Cube([1.0, val1]) cube2 = Cube([1.0, val2]) - self.assertFalse(cube1 == cube2) + assert cube1 != cube2 def test_data_int_eq(self): cube1 = Cube([1, 2, 3]) cube2 = Cube([1, 2, 3]) - self.assertTrue(cube1 == cube2) + assert cube1 == cube2 def test_data_int_not_eq(self): cube1 = Cube([1, 2, 3]) cube2 = Cube([1, 2, 0]) - self.assertFalse(cube1 == cube2) + assert cube1 != cube2 # NOTE: since numpy v1.18, boolean array subtract is deprecated. def test_data_bool_eq(self): cube1 = Cube([True, False]) cube2 = Cube([True, False]) - self.assertTrue(cube1 == cube2) + assert cube1 == cube2 def test_data_bool_not_eq(self): cube1 = Cube([True, False]) cube2 = Cube([True, True]) - self.assertFalse(cube1 == cube2) + assert cube1 != cube2 -class Test__eq__meta(tests.IrisTest): +class Test__eq__meta: def test_ancillary_fail(self): cube1 = Cube([0, 1]) cube2 = Cube([0, 1]) avr = AncillaryVariable([2, 3], long_name="foo") cube2.add_ancillary_variable(avr, 0) - self.assertFalse(cube1 == cube2) + assert cube1 != cube2 def test_ancillary_reorder(self): cube1 = Cube([0, 1]) @@ -2943,7 +3078,7 @@ def test_ancillary_reorder(self): cube1.add_ancillary_variable(avr2, 0) cube2.add_ancillary_variable(avr2, 0) cube2.add_ancillary_variable(avr1, 0) - self.assertTrue(cube1 == cube2) + assert cube1 == cube2 def test_ancillary_diff_data(self): cube1 = Cube([0, 1]) @@ -2952,14 +3087,14 @@ def test_ancillary_diff_data(self): avr2 = AncillaryVariable([4, 5], long_name="foo") cube1.add_ancillary_variable(avr1, 0) cube2.add_ancillary_variable(avr2, 0) - self.assertFalse(cube1 == cube2) + assert cube1 != cube2 def test_cell_measure_fail(self): cube1 = Cube([0, 1]) cube2 = Cube([0, 1]) cms = CellMeasure([2, 3], long_name="foo") cube2.add_cell_measure(cms, 0) - self.assertFalse(cube1 == cube2) + assert cube1 != cube2 def test_cell_measure_reorder(self): cube1 = Cube([0, 1]) @@ -2972,7 +3107,7 @@ def test_cell_measure_reorder(self): cube1.add_cell_measure(cms2, 0) cube2.add_cell_measure(cms2, 0) cube2.add_cell_measure(cms1, 0) - self.assertTrue(cube1 == cube2) + assert cube1 == cube2 def test_cell_measure_diff_data(self): cube1 = Cube([0, 1]) @@ -2981,14 +3116,14 @@ def test_cell_measure_diff_data(self): cms2 = CellMeasure([4, 5], long_name="foo") cube1.add_cell_measure(cms1, 0) cube2.add_cell_measure(cms2, 0) - self.assertFalse(cube1 == cube2) + assert cube1 != cube2 def test_cell_method_fail(self): cube1 = Cube([0, 1]) cube2 = Cube([0, 1]) cmth = CellMethod("mean", "time", "6hr") cube2.add_cell_method(cmth) - self.assertFalse(cube1 == cube2) + assert cube1 != cube2 # Unlike cell measures, cell methods are order sensitive. def test_cell_method_reorder_fail(self): @@ -3002,7 +3137,7 @@ def test_cell_method_reorder_fail(self): cube1.add_cell_method(cmth2) cube2.add_cell_method(cmth2) cube2.add_cell_method(cmth1) - self.assertFalse(cube1 == cube2) + assert cube1 != cube2 def test_cell_method_correct_order(self): cube1 = Cube([0, 1]) @@ -3015,10 +3150,10 @@ def test_cell_method_correct_order(self): cube1.add_cell_method(cmth2) cube2.add_cell_method(cmth1) cube2.add_cell_method(cmth2) - self.assertTrue(cube1 == cube2) + assert cube1 == cube2 -@pytest.fixture +@pytest.fixture() def simplecube(): return stock.simple_2d_w_cell_measure_ancil_var() @@ -3096,14 +3231,14 @@ class TestReprs: """ # Note: logically this could be a staticmethod, but that seems to upset Pytest - @pytest.fixture - def patched_cubeprinter(self): + @pytest.fixture() + def patched_cubeprinter(self, mocker): target = "iris._representation.cube_printout.CubePrinter" instance_mock = mock.MagicMock( to_string=mock.MagicMock(return_value="") # NB this must return a string ) - with mock.patch(target, return_value=instance_mock) as class_mock: - yield class_mock, instance_mock + class_mock = mocker.patch(target, return_value=instance_mock) + yield class_mock, instance_mock @staticmethod def _check_expected_effects(simplecube, patched_cubeprinter, oneline, padding): @@ -3153,14 +3288,14 @@ class TestHtmlRepr: """ # Note: logically this could be a staticmethod, but that seems to upset Pytest - @pytest.fixture - def patched_cubehtml(self): + @pytest.fixture() + def patched_cubehtml(self, mocker): target = "iris.experimental.representation.CubeRepresentation" instance_mock = mock.MagicMock( repr_html=mock.MagicMock(return_value="") # NB this must return a string ) - with mock.patch(target, return_value=instance_mock) as class_mock: - yield class_mock, instance_mock + class_mock = mocker.patch(target, return_value=instance_mock) + yield class_mock, instance_mock @staticmethod def test__repr_html__effects(simplecube, patched_cubehtml): @@ -3179,7 +3314,7 @@ def test__repr_html__effects(simplecube, patched_cubehtml): class Test__cell_methods: @pytest.fixture(autouse=True) - def cell_measures_testdata(self): + def _setup(self): self.cube = Cube([0]) self.cm = CellMethod("mean", "time", "6hr") self.cm2 = CellMethod("max", "latitude", "4hr") diff --git a/lib/iris/tests/unit/cube/test_CubeAttrsDict.py b/lib/iris/tests/unit/cube/test_CubeAttrsDict.py index 50de4541e0..4ba65913c6 100644 --- a/lib/iris/tests/unit/cube/test_CubeAttrsDict.py +++ b/lib/iris/tests/unit/cube/test_CubeAttrsDict.py @@ -14,7 +14,7 @@ from iris.fileformats.netcdf.saver import _CF_GLOBAL_ATTRS -@pytest.fixture +@pytest.fixture() def sample_attrs() -> CubeAttrsDict: return CubeAttrsDict(locals={"a": 1, "z": "this"}, globals={"b": 2, "z": "that"}) @@ -367,10 +367,10 @@ def test_local_global_masking(self, sample_attrs): sample_attrs.globals["z"] == "other" assert sample_attrs["z"] == "new" - @pytest.mark.parametrize("globals_or_locals", ("globals", "locals")) + @pytest.mark.parametrize("globals_or_locals", ["globals", "locals"]) @pytest.mark.parametrize( "value_type", - ("replace", "emptylist", "emptytuple", "none", "zero", "false"), + ["replace", "emptylist", "emptytuple", "none", "zero", "false"], ) def test_replace_subdict(self, globals_or_locals, value_type): # Writing to attrs.xx always replaces content with a *new* LimitedAttributeDict diff --git a/lib/iris/tests/unit/cube/test_CubeList.py b/lib/iris/tests/unit/cube/test_CubeList.py index 72ca7d2306..62e63e6694 100644 --- a/lib/iris/tests/unit/cube/test_CubeList.py +++ b/lib/iris/tests/unit/cube/test_CubeList.py @@ -4,16 +4,13 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.cube.CubeList` class.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import collections import copy from unittest import mock from cf_units import Unit import numpy as np +import pytest from iris import Constraint import iris.coord_systems @@ -21,31 +18,34 @@ from iris.cube import Cube, CubeList import iris.exceptions from iris.fileformats.pp import STASH +from iris.tests import _shared_utils import iris.tests.stock NOT_CUBE_MSG = "cannot be put in a cubelist, as it is not a Cube." NON_ITERABLE_MSG = "object is not iterable" -class Test_append(tests.IrisTest): - def setUp(self): +class Test_append: + @pytest.fixture(autouse=True) + def _setup(self): self.cubelist = iris.cube.CubeList() self.cube1 = iris.cube.Cube(1, long_name="foo") self.cube2 = iris.cube.Cube(1, long_name="bar") def test_pass(self): self.cubelist.append(self.cube1) - self.assertEqual(self.cubelist[-1], self.cube1) + assert self.cubelist[-1] == self.cube1 self.cubelist.append(self.cube2) - self.assertEqual(self.cubelist[-1], self.cube2) + assert self.cubelist[-1] == self.cube2 def test_fail(self): - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + with pytest.raises(ValueError, match=NOT_CUBE_MSG): self.cubelist.append(None) -class Test_concatenate_cube(tests.IrisTest): - def setUp(self): +class Test_concatenate_cube: + @pytest.fixture(autouse=True) + def _setup(self): self.units = Unit("days since 1970-01-01 00:00:00", calendar="standard") self.cube1 = Cube([1, 2, 3], "air_temperature", units="K") self.cube1.add_dim_coord(DimCoord([0, 1, 2], "time", units=self.units), 0) @@ -54,13 +54,13 @@ def test_pass(self): self.cube2 = Cube([1, 2, 3], "air_temperature", units="K") self.cube2.add_dim_coord(DimCoord([3, 4, 5], "time", units=self.units), 0) result = CubeList([self.cube1, self.cube2]).concatenate_cube() - self.assertIsInstance(result, Cube) + assert isinstance(result, Cube) def test_fail(self): units = Unit("days since 1970-01-02 00:00:00", calendar="standard") cube2 = Cube([1, 2, 3], "air_temperature", units="K") cube2.add_dim_coord(DimCoord([0, 1, 2], "time", units=units), 0) - with self.assertRaises(iris.exceptions.ConcatenateError): + with pytest.raises(iris.exceptions.ConcatenateError): CubeList([self.cube1, cube2]).concatenate_cube() def test_names_differ_fail(self): @@ -69,17 +69,18 @@ def test_names_differ_fail(self): self.cube3 = Cube([1, 2, 3], "air_pressure", units="Pa") self.cube3.add_dim_coord(DimCoord([3, 4, 5], "time", units=self.units), 0) exc_regexp = "Cube names differ: air_temperature != air_pressure" - with self.assertRaisesRegex(iris.exceptions.ConcatenateError, exc_regexp): + with pytest.raises(iris.exceptions.ConcatenateError, match=exc_regexp): CubeList([self.cube1, self.cube2, self.cube3]).concatenate_cube() def test_empty(self): exc_regexp = "can't concatenate an empty CubeList" - with self.assertRaisesRegex(ValueError, exc_regexp): + with pytest.raises(ValueError, match=exc_regexp): CubeList([]).concatenate_cube() -class Test_extend(tests.IrisTest): - def setUp(self): +class Test_extend: + @pytest.fixture(autouse=True) + def _setup(self): self.cube1 = iris.cube.Cube(1, long_name="foo") self.cube2 = iris.cube.Cube(1, long_name="bar") self.cubelist1 = iris.cube.CubeList([self.cube1]) @@ -88,21 +89,22 @@ def setUp(self): def test_pass(self): cubelist = copy.copy(self.cubelist1) cubelist.extend(self.cubelist2) - self.assertEqual(cubelist, self.cubelist1 + self.cubelist2) + assert cubelist == self.cubelist1 + self.cubelist2 cubelist.extend([self.cube2]) - self.assertEqual(cubelist[-1], self.cube2) + assert cubelist[-1] == self.cube2 def test_fail(self): - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + with pytest.raises(TypeError, match=NON_ITERABLE_MSG): self.cubelist1.extend(self.cube1) - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + with pytest.raises(TypeError, match=NON_ITERABLE_MSG): self.cubelist1.extend(None) - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + with pytest.raises(ValueError, match=NOT_CUBE_MSG): self.cubelist1.extend(range(3)) -class Test_extract_overlapping(tests.IrisTest): - def setUp(self): +class Test_extract_overlapping: + @pytest.fixture(autouse=True) + def _setup(self): shape = (6, 14, 19) n_time, n_lat, n_lon = shape n_data = n_time * n_lat * n_lon @@ -133,32 +135,33 @@ def setUp(self): def test_extract_one_str_dim(self): cubes = iris.cube.CubeList([self.cube[2:], self.cube[:4]]) a, b = cubes.extract_overlapping("time") - self.assertEqual(a.coord("time"), self.cube.coord("time")[2:4]) - self.assertEqual(b.coord("time"), self.cube.coord("time")[2:4]) + assert a.coord("time") == self.cube.coord("time")[2:4] + assert b.coord("time") == self.cube.coord("time")[2:4] def test_extract_one_list_dim(self): cubes = iris.cube.CubeList([self.cube[2:], self.cube[:4]]) a, b = cubes.extract_overlapping(["time"]) - self.assertEqual(a.coord("time"), self.cube.coord("time")[2:4]) - self.assertEqual(b.coord("time"), self.cube.coord("time")[2:4]) + assert a.coord("time") == self.cube.coord("time")[2:4] + assert b.coord("time") == self.cube.coord("time")[2:4] def test_extract_two_dims(self): cubes = iris.cube.CubeList([self.cube[2:, 5:], self.cube[:4, :10]]) a, b = cubes.extract_overlapping(["time", "latitude"]) - self.assertEqual(a.coord("time"), self.cube.coord("time")[2:4]) - self.assertEqual(a.coord("latitude"), self.cube.coord("latitude")[5:10]) - self.assertEqual(b.coord("time"), self.cube.coord("time")[2:4]) - self.assertEqual(b.coord("latitude"), self.cube.coord("latitude")[5:10]) + assert a.coord("time") == self.cube.coord("time")[2:4] + assert a.coord("latitude") == self.cube.coord("latitude")[5:10] + assert b.coord("time") == self.cube.coord("time")[2:4] + assert b.coord("latitude") == self.cube.coord("latitude")[5:10] def test_different_orders(self): cubes = iris.cube.CubeList([self.cube[::-1][:4], self.cube[:4]]) a, b = cubes.extract_overlapping("time") - self.assertEqual(a.coord("time"), self.cube[::-1].coord("time")[2:4]) - self.assertEqual(b.coord("time"), self.cube.coord("time")[2:4]) + assert a.coord("time") == self.cube[::-1].coord("time")[2:4] + assert b.coord("time") == self.cube.coord("time")[2:4] -class Test_iadd(tests.IrisTest): - def setUp(self): +class Test_iadd: + @pytest.fixture(autouse=True) + def _setup(self): self.cube1 = iris.cube.Cube(1, long_name="foo") self.cube2 = iris.cube.Cube(1, long_name="bar") self.cubelist1 = iris.cube.CubeList([self.cube1]) @@ -167,36 +170,38 @@ def setUp(self): def test_pass(self): cubelist = copy.copy(self.cubelist1) cubelist += self.cubelist2 - self.assertEqual(cubelist, self.cubelist1 + self.cubelist2) + assert cubelist == self.cubelist1 + self.cubelist2 cubelist += [self.cube2] - self.assertEqual(cubelist[-1], self.cube2) + assert cubelist[-1] == self.cube2 def test_fail(self): - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + with pytest.raises(TypeError, match=NON_ITERABLE_MSG): self.cubelist1 += self.cube1 - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + with pytest.raises(TypeError, match=NON_ITERABLE_MSG): self.cubelist1 += 1.0 - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + with pytest.raises(ValueError, match=NOT_CUBE_MSG): self.cubelist1 += range(3) -class Test_insert(tests.IrisTest): - def setUp(self): +class Test_insert: + @pytest.fixture(autouse=True) + def _setup(self): self.cube1 = iris.cube.Cube(1, long_name="foo") self.cube2 = iris.cube.Cube(1, long_name="bar") self.cubelist = iris.cube.CubeList([self.cube1] * 3) def test_pass(self): self.cubelist.insert(1, self.cube2) - self.assertEqual(self.cubelist[1], self.cube2) + assert self.cubelist[1] == self.cube2 def test_fail(self): - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + with pytest.raises(ValueError, match=NOT_CUBE_MSG): self.cubelist.insert(0, None) -class Test_merge_cube(tests.IrisTest): - def setUp(self): +class Test_merge_cube: + @pytest.fixture(autouse=True) + def _setup(self): self.cube1 = Cube([1, 2, 3], "air_temperature", units="K") self.cube1.add_aux_coord(AuxCoord([0], "height", units="m")) @@ -204,29 +209,33 @@ def test_pass(self): cube2 = self.cube1.copy() cube2.coord("height").points = [1] result = CubeList([self.cube1, cube2]).merge_cube() - self.assertIsInstance(result, Cube) + assert isinstance(result, Cube) def test_fail(self): cube2 = self.cube1.copy() cube2.rename("not air temperature") - with self.assertRaises(iris.exceptions.MergeError): + with pytest.raises(iris.exceptions.MergeError): CubeList([self.cube1, cube2]).merge_cube() def test_empty(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): CubeList([]).merge_cube() def test_single_cube(self): result = CubeList([self.cube1]).merge_cube() - self.assertEqual(result, self.cube1) - self.assertIsNot(result, self.cube1) + assert result == self.cube1 + assert result is not self.cube1 def test_repeated_cube(self): - with self.assertRaises(iris.exceptions.MergeError): + with pytest.raises(iris.exceptions.MergeError): CubeList([self.cube1, self.cube1]).merge_cube() -class Test_merge__time_triple(tests.IrisTest): +class Test_merge__time_triple: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request + @staticmethod def _make_cube(fp, rt, t, realization=None): cube = Cube(np.arange(20).reshape(4, 5)) @@ -259,7 +268,7 @@ def test_orthogonal_with_realization(self): en2_cubes = [self._make_cube(*triple, realization=2) for triple in triples] cubes = CubeList(en1_cubes) + CubeList(en2_cubes) (cube,) = cubes.merge() - self.assertCML(cube, checksum=False) + _shared_utils.assert_CML(self.request, cube, checksum=False) def test_combination_with_realization(self): # => fp, rt, t: 8; realization: 2 @@ -277,7 +286,7 @@ def test_combination_with_realization(self): en2_cubes = [self._make_cube(*triple, realization=2) for triple in triples] cubes = CubeList(en1_cubes) + CubeList(en2_cubes) (cube,) = cubes.merge() - self.assertCML(cube, checksum=False) + _shared_utils.assert_CML(self.request, cube, checksum=False) def test_combination_with_extra_realization(self): # => fp, rt, t, realization: 17 @@ -298,7 +307,7 @@ def test_combination_with_extra_realization(self): en3_cubes = [self._make_cube(0, 10, 2, realization=3)] cubes = CubeList(en1_cubes) + CubeList(en2_cubes) + CubeList(en3_cubes) (cube,) = cubes.merge() - self.assertCML(cube, checksum=False) + _shared_utils.assert_CML(self.request, cube, checksum=False) def test_combination_with_extra_triple(self): # => fp, rt, t, realization: 17 @@ -320,11 +329,12 @@ def test_combination_with_extra_triple(self): ] cubes = CubeList(en1_cubes) + CubeList(en2_cubes) (cube,) = cubes.merge() - self.assertCML(cube, checksum=False) + _shared_utils.assert_CML(self.request, cube, checksum=False) -class Test_setitem(tests.IrisTest): - def setUp(self): +class Test_setitem: + @pytest.fixture(autouse=True) + def _setup(self): self.cube1 = iris.cube.Cube(1, long_name="foo") self.cube2 = iris.cube.Cube(1, long_name="bar") self.cube3 = iris.cube.Cube(1, long_name="boo") @@ -332,41 +342,40 @@ def setUp(self): def test_pass(self): self.cubelist[1] = self.cube2 - self.assertEqual(self.cubelist[1], self.cube2) + assert self.cubelist[1] == self.cube2 self.cubelist[:2] = (self.cube2, self.cube3) - self.assertEqual( - self.cubelist, - iris.cube.CubeList([self.cube2, self.cube3, self.cube1]), - ) + assert self.cubelist == iris.cube.CubeList([self.cube2, self.cube3, self.cube1]) def test_fail(self): - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + with pytest.raises(ValueError, match=NOT_CUBE_MSG): self.cubelist[0] = None - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + with pytest.raises(ValueError, match=NOT_CUBE_MSG): self.cubelist[0:2] = [self.cube3, None] - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + with pytest.raises(TypeError, match=NON_ITERABLE_MSG): self.cubelist[:1] = 2.5 - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + with pytest.raises(TypeError, match=NON_ITERABLE_MSG): self.cubelist[:1] = self.cube1 -class Test_xml(tests.IrisTest): - def setUp(self): +class Test_xml: + @pytest.fixture(autouse=True) + def _setup(self): self.cubes = CubeList([Cube(np.arange(3)), Cube(np.arange(3))]) def test_byteorder_default(self): - self.assertIn("byteorder", self.cubes.xml()) + assert "byteorder" in self.cubes.xml() def test_byteorder_false(self): - self.assertNotIn("byteorder", self.cubes.xml(byteorder=False)) + assert "byteorder" not in self.cubes.xml(byteorder=False) def test_byteorder_true(self): - self.assertIn("byteorder", self.cubes.xml(byteorder=True)) + assert "byteorder" in self.cubes.xml(byteorder=True) -class Test_extract(tests.IrisTest): - def setUp(self): +class Test_extract: + @pytest.fixture(autouse=True) + def _setup(self): self.scalar_cubes = CubeList() for i in range(5): for letter in "abcd": @@ -376,7 +385,7 @@ def test_scalar_cube_name_constraint(self): # Test the name based extraction of a CubeList containing scalar cubes. res = self.scalar_cubes.extract("a") expected = CubeList([Cube(i, long_name="a") for i in range(5)]) - self.assertEqual(res, expected) + assert res == expected def test_scalar_cube_data_constraint(self): # Test the extraction of a CubeList containing scalar cubes @@ -385,7 +394,7 @@ def test_scalar_cube_data_constraint(self): constraint = iris.Constraint(cube_func=lambda c: c.data == val) res = self.scalar_cubes.extract(constraint) expected = CubeList([Cube(val, long_name=letter) for letter in "abcd"]) - self.assertEqual(res, expected) + assert res == expected class ExtractMixin: @@ -393,7 +402,8 @@ class ExtractMixin: # Effectively "abstract" -- inheritor must define this property : # method_name = 'extract_cube' / 'extract_cubes' - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.cube_x = Cube(0, long_name="x") self.cube_y = Cube(0, long_name="y") self.cons_x = Constraint("x") @@ -412,20 +422,18 @@ def check_extract(self, cubes, constraints, expected): cubelist = CubeList(cubes) method = getattr(cubelist, self.method_name) if isinstance(expected, str): - with self.assertRaisesRegex( - iris.exceptions.ConstraintMismatchError, expected - ): + with pytest.raises(iris.exceptions.ConstraintMismatchError, match=expected): method(constraints) else: result = method(constraints) if expected is None: - self.assertIsNone(result) + assert result is None elif isinstance(expected, Cube): - self.assertIsInstance(result, Cube) - self.assertEqual(result, expected) + assert isinstance(result, Cube) + assert result == expected elif isinstance(expected, list): - self.assertIsInstance(result, CubeList) - self.assertEqual(result, expected) + assert isinstance(result, CubeList) + assert result == expected else: msg = ( 'Unhandled usage in "check_extract" call: ' @@ -434,7 +442,7 @@ def check_extract(self, cubes, constraints, expected): raise ValueError(msg.format(type(expected), expected)) -class Test_extract_cube(ExtractMixin, tests.IrisTest): +class Test_extract_cube(ExtractMixin): method_name = "extract_cube" def test_empty(self): @@ -466,7 +474,7 @@ def test_none_as_constraint(self): def test_constraint_in_list__fail(self): # Check that we *cannot* use [constraint] msg = "cannot be cast to a constraint" - with self.assertRaisesRegex(TypeError, msg): + with pytest.raises(TypeError, match=msg): self.check_extract([], [self.cons_x], []) def test_multi_cube_ok(self): @@ -493,7 +501,7 @@ class ExtractCubesMixin(ExtractMixin): method_name = "extract_cubes" -class Test_extract_cubes__noconstraint(ExtractCubesMixin, tests.IrisTest): +class Test_extract_cubes__noconstraint(ExtractCubesMixin): """Test with an empty list of constraints.""" def test_empty(self): @@ -553,23 +561,19 @@ def test_multi_cube__fail_too_many(self): ) -class Test_extract_cubes__bare_single_constraint( - ExtractCubesSingleConstraintMixin, tests.IrisTest -): +class Test_extract_cubes__bare_single_constraint(ExtractCubesSingleConstraintMixin): """Testing with a single constraint as the argument.""" wrap_test_constraint_as_list_of_one = False -class Test_extract_cubes__list_single_constraint( - ExtractCubesSingleConstraintMixin, tests.IrisTest -): +class Test_extract_cubes__list_single_constraint(ExtractCubesSingleConstraintMixin): """Testing with a list of one constraint as the argument.""" wrap_test_constraint_as_list_of_one = True -class Test_extract_cubes__multi_constraints(ExtractCubesMixin, tests.IrisTest): +class Test_extract_cubes__multi_constraints(ExtractCubesMixin): """Testing when the 'constraints' arg is a list of multiple constraints.""" def test_empty(self): @@ -627,61 +631,64 @@ def test_multi_cube__fail_too_many(self): ) -class Test_iteration(tests.IrisTest): - def setUp(self): +class Test_iteration: + @pytest.fixture(autouse=True) + def _setup(self): self.scalar_cubes = CubeList() for i in range(5): for letter in "abcd": self.scalar_cubes.append(Cube(i, long_name=letter)) def test_iterable(self): - self.assertIsInstance(self.scalar_cubes, collections.abc.Iterable) + assert isinstance(self.scalar_cubes, collections.abc.Iterable) def test_iteration(self): letters = "abcd" * 5 for i, cube in enumerate(self.scalar_cubes): - self.assertEqual(cube.long_name, letters[i]) + assert cube.long_name == letters[i] -class TestPrint(tests.IrisTest): - def setUp(self): +class TestPrint: + @pytest.fixture(autouse=True) + def _setup(self): self.cubes = CubeList([iris.tests.stock.lat_lon_cube()]) def test_summary(self): expected = "0: unknown / (unknown) (latitude: 3; longitude: 4)" - self.assertEqual(str(self.cubes), expected) + assert str(self.cubes) == expected def test_summary_name_unit(self): self.cubes[0].long_name = "aname" self.cubes[0].units = "1" expected = "0: aname / (1) (latitude: 3; longitude: 4)" - self.assertEqual(str(self.cubes), expected) + assert str(self.cubes) == expected def test_summary_stash(self): self.cubes[0].attributes["STASH"] = STASH.from_msi("m01s00i004") expected = "0: m01s00i004 / (unknown) (latitude: 3; longitude: 4)" - self.assertEqual(str(self.cubes), expected) + assert str(self.cubes) == expected -class TestRealiseData(tests.IrisTest): - def test_realise_data(self): +class TestRealiseData: + def test_realise_data(self, mocker): # Simply check that calling CubeList.realise_data is calling # _lazy_data.co_realise_cubes. mock_cubes_list = [mock.Mock(ident=count) for count in range(3)] test_cubelist = CubeList(mock_cubes_list) - call_patch = self.patch("iris._lazy_data.co_realise_cubes") + call_patch = mocker.patch("iris._lazy_data.co_realise_cubes") test_cubelist.realise_data() # Check it was called once, passing cubes as *args. - self.assertEqual(call_patch.call_args_list, [mock.call(*mock_cubes_list)]) + assert call_patch.call_args_list == [mock.call(*mock_cubes_list)] -class Test_CubeList_copy(tests.IrisTest): - def setUp(self): +class Test_CubeList_copy: + @pytest.fixture(autouse=True) + def _setup(self): self.cube_list = iris.cube.CubeList() self.copied_cube_list = self.cube_list.copy() def test_copy(self): - self.assertIsInstance(self.copied_cube_list, iris.cube.CubeList) + assert isinstance(self.copied_cube_list, iris.cube.CubeList) class TestHtmlRepr: @@ -696,13 +703,13 @@ class TestHtmlRepr: """ @staticmethod - def test__repr_html_(): + def test__repr_html_(mocker): test_cubelist = CubeList([]) target = "iris.experimental.representation.CubeListRepresentation" - with mock.patch(target) as class_mock: - # Exercise the function-under-test. - test_cubelist._repr_html_() + class_mock = mocker.patch(target) + # Exercise the function-under-test. + test_cubelist._repr_html_() assert class_mock.call_args_list == [ # "CubeListRepresentation()" was called exactly once, with the cubelist as arg @@ -712,7 +719,3 @@ def test__repr_html_(): # "CubeListRepresentation(cubelist).repr_html()" was called exactly once, with no args mock.call() ] - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py b/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py index 878183139a..ae7a1cee7a 100644 --- a/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py +++ b/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py @@ -4,14 +4,11 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.cube.Cube` class aggregated_by method.""" -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - from unittest import mock from cf_units import Unit import numpy as np +import pytest from iris._lazy_data import as_lazy_data import iris.analysis @@ -20,11 +17,13 @@ import iris.coords from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord from iris.cube import Cube +from iris.tests import _shared_utils from iris.tests.stock import realistic_4d -class Test_aggregated_by(tests.IrisTest): - def setUp(self): +class Test_aggregated_by: + @pytest.fixture(autouse=True) + def _setup(self): self.cube = Cube(np.arange(44).reshape(4, 11)) val_coord = AuxCoord([0, 0, 0, 1, 1, 2, 0, 0, 2, 0, 1], long_name="val") @@ -99,13 +98,8 @@ def test_2d_coord_simple_agg(self): res_cube.slices("simple_agg"), self.cube.slices("simple_agg") ): cube_slice_agg = cube_slice.aggregated_by("simple_agg", self.mock_agg) - self.assertEqual( - res_slice.coord("spanning"), cube_slice_agg.coord("spanning") - ) - self.assertEqual( - res_slice.coord("span_label"), - cube_slice_agg.coord("span_label"), - ) + assert res_slice.coord("spanning") == cube_slice_agg.coord("spanning") + assert res_slice.coord("span_label") == cube_slice_agg.coord("span_label") def test_agg_by_label(self): # Aggregate a cube on a string coordinate label where label @@ -122,8 +116,8 @@ def test_agg_by_label(self): long_name="label", units="no_unit", ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) + assert res_cube.coord("val") == val_coord + assert res_cube.coord("label") == label_coord def test_agg_by_label_bounded(self): # Aggregate a cube on a string coordinate label where label @@ -142,8 +136,8 @@ def test_agg_by_label_bounded(self): long_name="label", units="no_unit", ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) + assert res_cube.coord("val") == val_coord + assert res_cube.coord("label") == label_coord def test_2d_agg_by_label(self): res_cube = self.cube.aggregated_by("label", self.mock_agg) @@ -153,9 +147,7 @@ def test_2d_agg_by_label(self): res_cube.slices("val"), self.cube.slices("val") ): cube_slice_agg = cube_slice.aggregated_by("label", self.mock_agg) - self.assertEqual( - res_slice.coord("spanning"), cube_slice_agg.coord("spanning") - ) + assert res_slice.coord("spanning") == cube_slice_agg.coord("spanning") def test_agg_by_val(self): # Aggregate a cube on a numeric coordinate val where label @@ -169,8 +161,8 @@ def test_agg_by_val(self): label_coord = AuxCoord( np.array((exp0, exp1, exp2)), long_name="label", units="no_unit" ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) + assert res_cube.coord("val") == val_coord + assert res_cube.coord("label") == label_coord def test_2d_agg_by_val(self): res_cube = self.cube.aggregated_by("val", self.mock_agg) @@ -180,9 +172,7 @@ def test_2d_agg_by_val(self): res_cube.slices("val"), self.cube.slices("val") ): cube_slice_agg = cube_slice.aggregated_by("val", self.mock_agg) - self.assertEqual( - res_slice.coord("spanning"), cube_slice_agg.coord("spanning") - ) + assert res_slice.coord("spanning") == cube_slice_agg.coord("spanning") def test_single_string_aggregation(self): aux_coords = [ @@ -193,31 +183,31 @@ def test_single_string_aggregation(self): np.arange(12).reshape(3, 4), aux_coords_and_dims=aux_coords ) result = cube.aggregated_by("foo", MEAN) - self.assertEqual(result.shape, (2, 4)) - self.assertEqual(result.coord("bar"), AuxCoord(["a|a", "a"], long_name="bar")) + assert result.shape == (2, 4) + assert result.coord("bar") == AuxCoord(["a|a", "a"], long_name="bar") def test_ancillary_variables_and_cell_measures_kept(self): cube_agg = self.cube.aggregated_by("val", self.mock_agg) - self.assertEqual(cube_agg.ancillary_variables(), [self.ancillary_variable]) - self.assertEqual(cube_agg.cell_measures(), [self.cell_measure]) + assert cube_agg.ancillary_variables() == [self.ancillary_variable] + assert cube_agg.cell_measures() == [self.cell_measure] def test_ancillary_variables_and_cell_measures_removed(self): cube_agg = self.cube.aggregated_by("simple_agg", self.mock_agg) - self.assertEqual(cube_agg.ancillary_variables(), []) - self.assertEqual(cube_agg.cell_measures(), []) + assert cube_agg.ancillary_variables() == [] + assert cube_agg.cell_measures() == [] def test_1d_weights(self): self.cube.aggregated_by( "simple_agg", self.mock_weighted_agg, weights=self.simple_weights ) - self.assertEqual(self.mock_weighted_agg.aggregate.call_count, 2) + assert self.mock_weighted_agg.aggregate.call_count == 2 # A simple mock.assert_called_with does not work due to ValueError: The # truth value of an array with more than one element is ambiguous. Use # a.any() or a.all() call_1 = self.mock_weighted_agg.aggregate.mock_calls[0] - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( call_1.args[0], np.array( [ @@ -226,8 +216,8 @@ def test_1d_weights(self): ] ), ) - self.assertEqual(call_1.kwargs["axis"], 0) - np.testing.assert_array_almost_equal( + assert call_1.kwargs["axis"] == 0 + _shared_utils.assert_array_almost_equal( call_1.kwargs["weights"], np.array( [ @@ -238,7 +228,7 @@ def test_1d_weights(self): ) call_2 = self.mock_weighted_agg.aggregate.mock_calls[1] - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( call_2.args[0], np.array( [ @@ -247,8 +237,8 @@ def test_1d_weights(self): ] ), ) - self.assertEqual(call_2.kwargs["axis"], 0) - np.testing.assert_array_almost_equal( + assert call_2.kwargs["axis"] == 0 + _shared_utils.assert_array_almost_equal( call_2.kwargs["weights"], np.array( [ @@ -261,13 +251,13 @@ def test_1d_weights(self): def test_2d_weights(self): self.cube.aggregated_by("val", self.mock_weighted_agg, weights=self.val_weights) - self.assertEqual(self.mock_weighted_agg.aggregate.call_count, 3) + assert self.mock_weighted_agg.aggregate.call_count == 3 # A simple mock.assert_called_with does not work due to ValueError: The # truth value of an array with more than one element is ambiguous. Use # a.any() or a.all() call_1 = self.mock_weighted_agg.aggregate.mock_calls[0] - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( call_1.args[0], np.array( [ @@ -278,33 +268,39 @@ def test_2d_weights(self): ] ), ) - self.assertEqual(call_1.kwargs["axis"], 1) - np.testing.assert_array_almost_equal(call_1.kwargs["weights"], np.ones((4, 6))) + assert call_1.kwargs["axis"] == 1 + _shared_utils.assert_array_almost_equal( + call_1.kwargs["weights"], np.ones((4, 6)) + ) call_2 = self.mock_weighted_agg.aggregate.mock_calls[1] - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( call_2.args[0], np.array([[3, 4, 10], [14, 15, 21], [25, 26, 32], [36, 37, 43]]), ) - self.assertEqual(call_2.kwargs["axis"], 1) - np.testing.assert_array_almost_equal(call_2.kwargs["weights"], np.ones((4, 3))) + assert call_2.kwargs["axis"] == 1 + _shared_utils.assert_array_almost_equal( + call_2.kwargs["weights"], np.ones((4, 3)) + ) call_3 = self.mock_weighted_agg.aggregate.mock_calls[2] - np.testing.assert_array_equal( + _shared_utils.assert_array_equal( call_3.args[0], np.array([[5, 8], [16, 19], [27, 30], [38, 41]]) ) - self.assertEqual(call_3.kwargs["axis"], 1) - np.testing.assert_array_almost_equal(call_3.kwargs["weights"], np.ones((4, 2))) + assert call_3.kwargs["axis"] == 1 + _shared_utils.assert_array_almost_equal( + call_3.kwargs["weights"], np.ones((4, 2)) + ) def test_returned(self): output = self.cube.aggregated_by( "simple_agg", self.mock_weighted_agg, returned=True ) - self.assertTrue(isinstance(output, tuple)) - self.assertEqual(len(output), 2) - self.assertEqual(output[0].shape, (2, 11)) - self.assertEqual(output[1].shape, (2, 11)) + assert isinstance(output, tuple) + assert len(output) == 2 + assert output[0].shape == (2, 11) + assert output[1].shape == (2, 11) def test_fail_1d_weights_wrong_len(self): wrong_weights = np.array([1.0, 2.0]) @@ -312,7 +308,7 @@ def test_fail_1d_weights_wrong_len(self): r"1D weights must have the same length as the dimension that is " r"aggregated, got 2, expected 11" ) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): self.cube.aggregated_by( "val", self.mock_weighted_agg, weights=wrong_weights ) @@ -323,14 +319,15 @@ def test_fail_weights_wrong_shape(self): r"Weights must either be 1D or have the same shape as the cube, " r"got shape \(42, 1\) for weights, \(4, 11\) for cube" ) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): self.cube.aggregated_by( "val", self.mock_weighted_agg, weights=wrong_weights ) -class Test_aggregated_by__lazy(tests.IrisTest): - def setUp(self): +class Test_aggregated_by__lazy: + @pytest.fixture(autouse=True) + def _setup(self): self.data = np.arange(44).reshape(4, 11) self.lazydata = as_lazy_data(self.data) self.cube = Cube(self.lazydata) @@ -394,11 +391,11 @@ def test_agg_by_label__lazy(self): long_name="label", units="no_unit", ) - self.assertTrue(res_cube.has_lazy_data()) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - self.assertArrayEqual(res_cube.data, self.label_mean) - self.assertFalse(res_cube.has_lazy_data()) + assert res_cube.has_lazy_data() + assert res_cube.coord("val") == val_coord + assert res_cube.coord("label") == label_coord + _shared_utils.assert_array_equal(res_cube.data, self.label_mean) + assert not res_cube.has_lazy_data() def test_agg_by_val__lazy(self): # Aggregate a cube on a numeric coordinate val where label @@ -412,11 +409,11 @@ def test_agg_by_val__lazy(self): label_coord = AuxCoord( np.array((exp0, exp1, exp2)), long_name="label", units="no_unit" ) - self.assertTrue(res_cube.has_lazy_data()) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - self.assertArrayEqual(res_cube.data, self.val_mean) - self.assertFalse(res_cube.has_lazy_data()) + assert res_cube.has_lazy_data() + assert res_cube.coord("val") == val_coord + assert res_cube.coord("label") == label_coord + _shared_utils.assert_array_equal(res_cube.data, self.val_mean) + assert not res_cube.has_lazy_data() def test_single_string_aggregation__lazy(self): aux_coords = [ @@ -429,22 +426,22 @@ def test_single_string_aggregation__lazy(self): ) means = np.array([[4.0, 5.0, 6.0, 7.0], [4.0, 5.0, 6.0, 7.0]]) result = cube.aggregated_by("foo", MEAN) - self.assertTrue(result.has_lazy_data()) - self.assertEqual(result.shape, (2, 4)) - self.assertEqual(result.coord("bar"), AuxCoord(["a|a", "a"], long_name="bar")) - self.assertArrayEqual(result.data, means) - self.assertFalse(result.has_lazy_data()) + assert result.has_lazy_data() + assert result.shape == (2, 4) + assert result.coord("bar") == AuxCoord(["a|a", "a"], long_name="bar") + _shared_utils.assert_array_equal(result.data, means) + assert not result.has_lazy_data() def test_1d_weights__lazy(self): - self.assertTrue(self.cube.has_lazy_data()) + assert self.cube.has_lazy_data() cube_agg = self.cube.aggregated_by( "simple_agg", SUM, weights=self.simple_weights ) - self.assertTrue(self.cube.has_lazy_data()) - self.assertTrue(cube_agg.has_lazy_data()) - self.assertEqual(cube_agg.shape, (2, 11)) + assert self.cube.has_lazy_data() + assert cube_agg.has_lazy_data() + assert cube_agg.shape == (2, 11) row_0 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] row_1 = [ @@ -460,18 +457,18 @@ def test_1d_weights__lazy(self): 146.0, 150.0, ] - np.testing.assert_array_almost_equal(cube_agg.data, np.array([row_0, row_1])) + _shared_utils.assert_array_almost_equal(cube_agg.data, np.array([row_0, row_1])) def test_2d_weights__lazy(self): - self.assertTrue(self.cube.has_lazy_data()) + assert self.cube.has_lazy_data() cube_agg = self.cube.aggregated_by("val", SUM, weights=self.val_weights) - self.assertTrue(self.cube.has_lazy_data()) - self.assertTrue(cube_agg.has_lazy_data()) + assert self.cube.has_lazy_data() + assert cube_agg.has_lazy_data() - self.assertEqual(cube_agg.shape, (4, 3)) - np.testing.assert_array_almost_equal( + assert cube_agg.shape == (4, 3) + _shared_utils.assert_array_almost_equal( cube_agg.data, np.array( [ @@ -484,21 +481,21 @@ def test_2d_weights__lazy(self): ) def test_returned__lazy(self): - self.assertTrue(self.cube.has_lazy_data()) + assert self.cube.has_lazy_data() output = self.cube.aggregated_by( "simple_agg", SUM, weights=self.simple_weights, returned=True ) - self.assertTrue(self.cube.has_lazy_data()) + assert self.cube.has_lazy_data() - self.assertTrue(isinstance(output, tuple)) - self.assertEqual(len(output), 2) + assert isinstance(output, tuple) + assert len(output) == 2 cube = output[0] - self.assertTrue(isinstance(cube, Cube)) - self.assertTrue(cube.has_lazy_data()) - self.assertEqual(cube.shape, (2, 11)) + assert isinstance(cube, Cube) + assert cube.has_lazy_data() + assert cube.shape == (2, 11) row_0 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] row_1 = [ 110.0, @@ -513,11 +510,11 @@ def test_returned__lazy(self): 146.0, 150.0, ] - np.testing.assert_array_almost_equal(cube.data, np.array([row_0, row_1])) + _shared_utils.assert_array_almost_equal(cube.data, np.array([row_0, row_1])) weights = output[1] - self.assertEqual(weights.shape, (2, 11)) - np.testing.assert_array_almost_equal( + assert weights.shape == (2, 11) + _shared_utils.assert_array_almost_equal( weights, np.array( [ @@ -528,8 +525,9 @@ def test_returned__lazy(self): ) -class Test_aggregated_by__climatology(tests.IrisTest): - def setUp(self): +class Test_aggregated_by__climatology: + @pytest.fixture(autouse=True) + def _setup(self): self.data = np.arange(100).reshape(20, 5) self.aggregator = iris.analysis.MEAN @@ -626,15 +624,17 @@ def test_basic(self): result = self.get_result() aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(2)) - self.assertArrayEqual(aligned_coord.bounds, np.array([[0, 18], [1, 19]])) - self.assertTrue(aligned_coord.climatological) - self.assertIn(aligned_coord, result.dim_coords) + _shared_utils.assert_array_equal(aligned_coord.points, np.arange(2)) + _shared_utils.assert_array_equal( + aligned_coord.bounds, np.array([[0, 18], [1, 19]]) + ) + assert aligned_coord.climatological + assert aligned_coord in result.dim_coords categorised_coord = result.coord("cat1") - self.assertArrayEqual(categorised_coord.points, np.arange(2)) - self.assertIsNone(categorised_coord.bounds) - self.assertFalse(categorised_coord.climatological) + _shared_utils.assert_array_equal(categorised_coord.points, np.arange(2)) + assert categorised_coord.bounds is None + assert not categorised_coord.climatological def test_2d_other_coord(self): """Check that we can handle aggregation applying to a 2d AuxCoord that @@ -643,19 +643,21 @@ def test_2d_other_coord(self): result = self.get_result(partially_aligned=True) aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(2)) - self.assertArrayEqual(aligned_coord.bounds, np.array([[0, 18], [1, 19]])) - self.assertTrue(aligned_coord.climatological) + _shared_utils.assert_array_equal(aligned_coord.points, np.arange(2)) + _shared_utils.assert_array_equal( + aligned_coord.bounds, np.array([[0, 18], [1, 19]]) + ) + assert aligned_coord.climatological part_aligned_coord = result.coord("part_aligned") - self.assertArrayEqual( + _shared_utils.assert_array_equal( part_aligned_coord.points, np.arange(46, 56).reshape(2, 5) ) - self.assertArrayEqual( + _shared_utils.assert_array_equal( part_aligned_coord.bounds, np.array([np.arange(1, 11), np.arange(91, 101)]).T.reshape(2, 5, 2), ) - self.assertFalse(part_aligned_coord.climatological) + assert not part_aligned_coord.climatological def test_2d_timelike_other_coord(self): """Check that we can handle aggregation applying to a 2d AuxCoord that @@ -666,64 +668,74 @@ def test_2d_timelike_other_coord(self): ) aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(2)) - self.assertArrayEqual(aligned_coord.bounds, np.array([[0, 18], [1, 19]])) - self.assertTrue(aligned_coord.climatological) + _shared_utils.assert_array_equal(aligned_coord.points, np.arange(2)) + _shared_utils.assert_array_equal( + aligned_coord.bounds, np.array([[0, 18], [1, 19]]) + ) + assert aligned_coord.climatological part_aligned_coord = result.coord("part_aligned") - self.assertArrayEqual(part_aligned_coord.points, np.arange(1, 11).reshape(2, 5)) - self.assertArrayEqual( + _shared_utils.assert_array_equal( + part_aligned_coord.points, np.arange(1, 11).reshape(2, 5) + ) + _shared_utils.assert_array_equal( part_aligned_coord.bounds, np.array([np.arange(1, 11), np.arange(91, 101)]).T.reshape(2, 5, 2), ) - self.assertTrue(part_aligned_coord.climatological) + assert part_aligned_coord.climatological def test_transposed(self): """Check that we can handle the axis of aggregation being a different one.""" result = self.get_result(transpose=True) aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(2)) - self.assertArrayEqual(aligned_coord.bounds, np.array([[0, 18], [1, 19]])) - self.assertTrue(aligned_coord.climatological) + _shared_utils.assert_array_equal(aligned_coord.points, np.arange(2)) + _shared_utils.assert_array_equal( + aligned_coord.bounds, np.array([[0, 18], [1, 19]]) + ) + assert aligned_coord.climatological categorised_coord = result.coord("cat1") - self.assertArrayEqual(categorised_coord.points, np.arange(2)) - self.assertIsNone(categorised_coord.bounds) - self.assertFalse(categorised_coord.climatological) + _shared_utils.assert_array_equal(categorised_coord.points, np.arange(2)) + assert categorised_coord.bounds is None + assert not categorised_coord.climatological def test_bounded(self): """Check that we handle bounds correctly.""" result = self.get_result(bounds=True) aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, [-0.5, 0.5]) - self.assertArrayEqual( + _shared_utils.assert_array_equal(aligned_coord.points, [-0.5, 0.5]) + _shared_utils.assert_array_equal( aligned_coord.bounds, np.array([[-0.5, 18.5], [0.5, 19.5]]) ) - self.assertTrue(aligned_coord.climatological) + assert aligned_coord.climatological def test_multiple_agg_coords(self): """Check that we can aggregate on multiple coords on the same axis.""" result = self.get_result(second_categorised=True) aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(10)) - self.assertArrayEqual( + _shared_utils.assert_array_equal(aligned_coord.points, np.arange(10)) + _shared_utils.assert_array_equal( aligned_coord.bounds, np.array([np.arange(10), np.arange(10, 20)]).T, ) - self.assertTrue(aligned_coord.climatological) + assert aligned_coord.climatological categorised_coord1 = result.coord("cat1") - self.assertArrayEqual(categorised_coord1.points, np.tile(np.arange(2), 5)) - self.assertIsNone(categorised_coord1.bounds) - self.assertFalse(categorised_coord1.climatological) + _shared_utils.assert_array_equal( + categorised_coord1.points, np.tile(np.arange(2), 5) + ) + assert categorised_coord1.bounds is None + assert not categorised_coord1.climatological categorised_coord2 = result.coord("cat2") - self.assertArrayEqual(categorised_coord2.points, np.tile(np.arange(5), 2)) - self.assertIsNone(categorised_coord2.bounds) - self.assertFalse(categorised_coord2.climatological) + _shared_utils.assert_array_equal( + categorised_coord2.points, np.tile(np.arange(5), 2) + ) + assert categorised_coord2.bounds is None + assert not categorised_coord2.climatological def test_non_climatological_units(self): """Check that the failure to set the climatological flag on an incompatible @@ -732,9 +744,11 @@ def test_non_climatological_units(self): result = self.get_result(invalid_units=True) aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(9, 11)) - self.assertArrayEqual(aligned_coord.bounds, np.array([[0, 18], [1, 19]])) - self.assertFalse(aligned_coord.climatological) + _shared_utils.assert_array_equal(aligned_coord.points, np.arange(9, 11)) + _shared_utils.assert_array_equal( + aligned_coord.bounds, np.array([[0, 18], [1, 19]]) + ) + assert not aligned_coord.climatological def test_clim_in_clim_op(self): """Check the least complicated version works (set climatological, set @@ -744,16 +758,16 @@ def test_clim_in_clim_op(self): result = self.get_result(bounds=True, already_climatological=True) aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, [-0.5, 0.5]) - self.assertArrayEqual( + _shared_utils.assert_array_equal(aligned_coord.points, [-0.5, 0.5]) + _shared_utils.assert_array_equal( aligned_coord.bounds, np.array([[-0.5, 18.5], [0.5, 19.5]]) ) - self.assertTrue(aligned_coord.climatological) + assert aligned_coord.climatological categorised_coord = result.coord("cat1") - self.assertArrayEqual(categorised_coord.points, np.arange(2)) - self.assertIsNone(categorised_coord.bounds) - self.assertFalse(categorised_coord.climatological) + _shared_utils.assert_array_equal(categorised_coord.points, np.arange(2)) + assert categorised_coord.bounds is None + assert not categorised_coord.climatological def test_clim_in_no_clim_op(self): """Check the least complicated version works (set climatological, set @@ -765,20 +779,21 @@ def test_clim_in_no_clim_op(self): ) aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(9, 11)) - self.assertArrayEqual( + _shared_utils.assert_array_equal(aligned_coord.points, np.arange(9, 11)) + _shared_utils.assert_array_equal( aligned_coord.bounds, np.array([[-0.5, 18.5], [0.5, 19.5]]) ) - self.assertTrue(aligned_coord.climatological) + assert aligned_coord.climatological categorised_coord = result.coord("cat1") - self.assertArrayEqual(categorised_coord.points, np.arange(2)) - self.assertIsNone(categorised_coord.bounds) - self.assertFalse(categorised_coord.climatological) + _shared_utils.assert_array_equal(categorised_coord.points, np.arange(2)) + assert categorised_coord.bounds is None + assert not categorised_coord.climatological -class Test_aggregated_by__derived(tests.IrisTest): - def setUp(self): +class Test_aggregated_by__derived: + @pytest.fixture(autouse=True) + def _setup(self): self.cube = realistic_4d()[:, :10, :6, :8] self.time_cat_coord = AuxCoord([0, 0, 1, 1, 2, 2], long_name="time_cat") self.cube.add_aux_coord(self.time_cat_coord, 0) @@ -818,7 +833,3 @@ def test_ungrouped_dim(self): assert len(result.aux_factories) == 1 altitude = result.coord("altitude") assert altitude == self.cube.coord("altitude") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/cube/test_Cube__operators.py b/lib/iris/tests/unit/cube/test_Cube__operators.py index 0afd5a9d70..98b5963e3a 100644 --- a/lib/iris/tests/unit/cube/test_Cube__operators.py +++ b/lib/iris/tests/unit/cube/test_Cube__operators.py @@ -4,22 +4,20 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.cube.Cube` class operators.""" -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - import operator import dask.array as da import numpy as np import numpy.ma as ma +import pytest import iris from iris._lazy_data import as_lazy_data from iris.coords import DimCoord +from iris.tests import _shared_utils -class Test_lazy_maths(tests.IrisTest): +class Test_lazy_maths: def build_lazy_cube(self, points, dtype=np.float64, bounds=None, nx=10): data = np.arange(len(points) * nx, dtype=dtype) + 1 # Just avoid 0. data = data.reshape(len(points), nx) @@ -32,23 +30,23 @@ def build_lazy_cube(self, points, dtype=np.float64, bounds=None, nx=10): return cube def check_common(self, base_cube, result): - self.assertTrue(base_cube.has_lazy_data()) - self.assertTrue(result.has_lazy_data()) - self.assertIsInstance(result.lazy_data(), da.core.Array) + assert base_cube.has_lazy_data() + assert result.has_lazy_data() + assert isinstance(result.lazy_data(), da.core.Array) def cube_cube_math_op(self, c1, math_op): result = math_op(c1, c1) self.check_common(c1, result) expected = math_op(c1.data, c1.data) - self.assertArrayAlmostEqual(result.data, expected) + _shared_utils.assert_array_almost_equal(result.data, expected) def cube_scalar_math_op(self, c1, scalar, math_op, commutative=True): result = math_op(c1, scalar) if commutative: - self.assertEqual(math_op(c1, scalar), math_op(scalar, c1)) + assert math_op(c1, scalar) == math_op(scalar, c1) self.check_common(c1, result) expected = math_op(c1.data, scalar) - self.assertArrayAlmostEqual(result.data, expected) + _shared_utils.assert_array_almost_equal(result.data, expected) def test_add_cubes__float(self): c1 = self.build_lazy_cube([1, 2]) @@ -139,12 +137,13 @@ def test_div_scalar__int(self): self.cube_scalar_math_op(c1, scalar, op, commutative=False) -class Test_lazy_maths__scalar_cube(tests.IrisTest): +class Test_lazy_maths__scalar_cube: def build_lazy_cube(self, value, dtype=np.float64): data = as_lazy_data(np.array(value, dtype=dtype)) return iris.cube.Cube(data, standard_name="air_temperature", units="K") - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.c1 = self.build_lazy_cube(3) self.c2 = self.build_lazy_cube(4) self.c3 = self.build_lazy_cube(3, dtype=np.int64) @@ -153,8 +152,8 @@ def setUp(self): def check_common(self, c1, c2, math_op): cube = math_op(c1, c2) data = cube.data - self.assertTrue(isinstance(data, np.ndarray)) - self.assertEqual(data.shape, ()) + assert isinstance(data, np.ndarray) + assert data.shape == () def test_add_scalar__int(self): c3, c4, op = self.c3, 5, operator.add @@ -221,7 +220,7 @@ def test_div_cubes__float(self): self.check_common(c1, c2, op) -class Test_lazy_maths__masked_data(tests.IrisTest): +class Test_lazy_maths__masked_data: def build_lazy_cube(self, dtype=np.float64): data = ma.array( [[1.0, 1.0], [1.0, 100000.0]], mask=[[0, 0], [0, 1]], dtype=dtype @@ -238,14 +237,10 @@ def test_subtract__float(self): cube_a = self.build_lazy_cube() cube_b = self.build_lazy_cube() cube_c = cube_a - cube_b - self.assertTrue(ma.isMaskedArray(cube_c.data)) + assert ma.isMaskedArray(cube_c.data) def test_subtract__int(self): cube_a = self.build_lazy_cube(dtype=np.int64) cube_b = self.build_lazy_cube(dtype=np.int64) cube_c = cube_a - cube_b - self.assertTrue(ma.isMaskedArray(cube_c.data)) - - -if __name__ == "__main__": - tests.main() + assert ma.isMaskedArray(cube_c.data) diff --git a/lib/iris/tests/unit/fileformats/__init__.py b/lib/iris/tests/unit/fileformats/__init__.py index 81e6c8cedf..c5982fc475 100644 --- a/lib/iris/tests/unit/fileformats/__init__.py +++ b/lib/iris/tests/unit/fileformats/__init__.py @@ -3,63 +3,3 @@ # 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.fileformats` package.""" - -import iris.tests as tests # isort:skip - - -class TestField(tests.IrisTest): - def _test_for_coord( - self, field, convert, coord_predicate, expected_points, expected_bounds - ): - ( - factories, - references, - standard_name, - long_name, - units, - attributes, - cell_methods, - dim_coords_and_dims, - aux_coords_and_dims, - ) = convert(field) - - # Check for one and only one matching coordinate. - coords_and_dims = dim_coords_and_dims + aux_coords_and_dims - matching_coords = [ - coord for coord, _ in coords_and_dims if coord_predicate(coord) - ] - self.assertEqual(len(matching_coords), 1, str(matching_coords)) - coord = matching_coords[0] - - # Check points and bounds. - if expected_points is not None: - self.assertArrayEqual(coord.points, expected_points) - - if expected_bounds is None: - self.assertIsNone(coord.bounds) - else: - self.assertArrayEqual(coord.bounds, expected_bounds) - - def assertCoordsAndDimsListsMatch( - self, coords_and_dims_got, coords_and_dims_expected - ): - """Check that coords_and_dims lists are equivalent. - - The arguments are lists of pairs of (coordinate, dimensions). - The elements are compared one-to-one, by coordinate name (so the order - of the lists is _not_ significant). - It also checks that the coordinate types (DimCoord/AuxCoord) match. - - """ - - def sorted_by_coordname(list): - return sorted(list, key=lambda item: item[0].name()) - - coords_and_dims_got = sorted_by_coordname(coords_and_dims_got) - coords_and_dims_expected = sorted_by_coordname(coords_and_dims_expected) - self.assertEqual(coords_and_dims_got, coords_and_dims_expected) - # Also check coordinate type equivalences (as Coord.__eq__ does not). - self.assertEqual( - [type(coord) for coord, dims in coords_and_dims_got], - [type(coord) for coord, dims in coords_and_dims_expected], - ) diff --git a/lib/iris/tests/unit/fileformats/abf/test_ABFField.py b/lib/iris/tests/unit/fileformats/abf/test_ABFField.py index b67e02ec06..2f06c914ca 100644 --- a/lib/iris/tests/unit/fileformats/abf/test_ABFField.py +++ b/lib/iris/tests/unit/fileformats/abf/test_ABFField.py @@ -4,49 +4,22 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.fileformats.abf.ABFField` class.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - from iris.fileformats.abf import ABFField -class MethodCounter: - def __init__(self, method_name): - self.method_name = method_name - self.count = 0 - - def __enter__(self): - self.orig_method = getattr(ABFField, self.method_name) - - def new_method(*args, **kwargs): - self.count += 1 - self.orig_method(*args, **kwargs) - - setattr(ABFField, self.method_name, new_method) - return self - - def __exit__(self, exc_type, exc_value, traceback): - setattr(ABFField, self.method_name, self.orig_method) - return False - - -class Test_data(tests.IrisTest): - def test_single_read(self): +class Test_data: + def test_single_read(self, mocker): path = "0000000000000000jan00000" field = ABFField(path) - with mock.patch("iris.fileformats.abf.np.fromfile") as fromfile: - with MethodCounter("__getattr__") as getattr: - with MethodCounter("_read") as read: - field.data + # Fake the file fetch operation + fromfile = mocker.patch("iris.fileformats.abf.np.fromfile") + # Spy on the '_read' operation + read = mocker.spy(field, "_read") - fromfile.assert_called_once_with(path, dtype=">u1") - self.assertEqual(getattr.count, 1) - self.assertEqual(read.count, 1) + # do the access + field.data - -if __name__ == "__main__": - tests.main() + # Check that _read was called, and np.fromfile. + fromfile.assert_called_once_with(path, dtype=">u1") + assert read.call_count == 1 diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py b/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py index 25f64319af..3724a2f628 100644 --- a/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py @@ -6,6 +6,8 @@ from unittest.mock import MagicMock +import pytest + from iris.fileformats.cf import ( CFAuxiliaryCoordinateVariable, CFCoordinateVariable, @@ -16,14 +18,11 @@ CFUGridMeshVariable, ) -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -class Tests(tests.IrisTest): +class Tests: # TODO: unit tests for existing functionality pre 2021-03-11. - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.cf_group = CFGroup() def test_non_data_names(self): @@ -44,36 +43,37 @@ def test_non_data_names(self): expected_names = [var.cf_name for var in (aux_var, coord_var, coord_var2)] expected = set(expected_names) - self.assertEqual(expected, self.cf_group.non_data_variable_names) + assert self.cf_group.non_data_variable_names == expected -class Ugrid(tests.IrisTest): +class TestUgrid: """Separate class to test UGRID functionality.""" - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.cf_group = CFGroup() def test_inherited(self): coord_var = MagicMock(spec=CFCoordinateVariable, cf_name="coord_var") self.cf_group[coord_var.cf_name] = coord_var - self.assertEqual(coord_var, self.cf_group.coordinates[coord_var.cf_name]) + assert self.cf_group.coordinates[coord_var.cf_name] == coord_var def test_connectivities(self): conn_var = MagicMock(spec=CFUGridConnectivityVariable, cf_name="conn_var") self.cf_group[conn_var.cf_name] = conn_var - self.assertEqual(conn_var, self.cf_group.connectivities[conn_var.cf_name]) + assert self.cf_group.connectivities[conn_var.cf_name] == conn_var def test_ugrid_coords(self): coord_var = MagicMock( spec=CFUGridAuxiliaryCoordinateVariable, cf_name="coord_var" ) self.cf_group[coord_var.cf_name] = coord_var - self.assertEqual(coord_var, self.cf_group.ugrid_coords[coord_var.cf_name]) + assert self.cf_group.ugrid_coords[coord_var.cf_name] == coord_var def test_meshes(self): mesh_var = MagicMock(spec=CFUGridMeshVariable, cf_name="mesh_var") self.cf_group[mesh_var.cf_name] = mesh_var - self.assertEqual(mesh_var, self.cf_group.meshes[mesh_var.cf_name]) + assert self.cf_group.meshes[mesh_var.cf_name] == mesh_var def test_non_data_names(self): data_var = MagicMock(spec=CFDataVariable, cf_name="data_var") @@ -108,4 +108,4 @@ def test_non_data_names(self): ) ] expected = set(expected_names) - self.assertEqual(expected, self.cf_group.non_data_variable_names) + assert self.cf_group.non_data_variable_names == expected diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py index 12c1510413..7f37eb9f24 100644 --- a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py @@ -4,13 +4,10 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.fileformats.cf.CFReader` class.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from unittest import mock import numpy as np +import pytest from iris.fileformats.cf import ( CFCoordinateVariable, @@ -68,29 +65,31 @@ def netcdf_variable( return ncvar -class Test_translate__global_attributes(tests.IrisTest): - def setUp(self): +class Test_translate__global_attributes: + @pytest.fixture(autouse=True) + def _setup(self, mocker): ncvar = netcdf_variable("ncvar", "height", np.float64) ncattrs = mock.Mock(return_value=["dimensions"]) getncattr = mock.Mock(return_value="something something_else") - self.dataset = mock.Mock( + dataset = mock.Mock( file_format="NetCDF4", variables={"ncvar": ncvar}, ncattrs=ncattrs, getncattr=getncattr, ) - - def test_create_global_attributes(self): - with mock.patch( + mocker.patch( "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ): - global_attrs = CFReader("dummy").cf_group.global_attributes - self.assertEqual(global_attrs["dimensions"], "something something_else") + return_value=dataset, + ) + def test_create_global_attributes(self, mocker): + global_attrs = CFReader("dummy").cf_group.global_attributes + assert global_attrs["dimensions"] == "something something_else" -class Test_translate__formula_terms(tests.IrisTest): - def setUp(self): + +class Test_translate__formula_terms: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.delta = netcdf_variable("delta", "height", np.float64, bounds="delta_bnds") self.delta_bnds = netcdf_variable("delta_bnds", "height bnds", np.float64) self.sigma = netcdf_variable("sigma", "height", np.float64, bounds="sigma_bnds") @@ -139,53 +138,50 @@ def setUp(self): file_format="NetCDF4", variables=self.variables, ncattrs=ncattrs ) # Restrict the CFReader functionality to only performing translations. - build_patch = mock.patch("iris.fileformats.cf.CFReader._build_cf_groups") - reset_patch = mock.patch("iris.fileformats.cf.CFReader._reset") - build_patch.start() - reset_patch.start() - self.addCleanup(build_patch.stop) - self.addCleanup(reset_patch.stop) - - def test_create_formula_terms(self): - with mock.patch( + mocker.patch("iris.fileformats.cf.CFReader._build_cf_groups") + mocker.patch("iris.fileformats.cf.CFReader._reset") + mocker.patch( "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", return_value=self.dataset, - ): - cf_group = CFReader("dummy").cf_group - self.assertEqual(len(cf_group), len(self.variables)) - # Check there is a singular data variable. - group = cf_group.data_variables - self.assertEqual(len(group), 1) - self.assertEqual(list(group.keys()), ["temp"]) - self.assertIs(group["temp"].cf_data, self.temp) - # Check there are three coordinates. - group = cf_group.coordinates - self.assertEqual(len(group), 3) - coordinates = ["height", "lat", "lon"] - self.assertEqual(set(group.keys()), set(coordinates)) - for name in coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check there are three auxiliary coordinates. - group = cf_group.auxiliary_coordinates - self.assertEqual(len(group), 3) - aux_coordinates = ["delta", "sigma", "orography"] - self.assertEqual(set(group.keys()), set(aux_coordinates)) - for name in aux_coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check all the auxiliary coordinates are formula terms. - formula_terms = cf_group.formula_terms - self.assertEqual(set(group.items()), set(formula_terms.items())) - # Check there are three bounds. - group = cf_group.bounds - self.assertEqual(len(group), 3) - bounds = ["height_bnds", "delta_bnds", "sigma_bnds"] - self.assertEqual(set(group.keys()), set(bounds)) - for name in bounds: - self.assertEqual(group[name].cf_data, getattr(self, name)) - + ) -class Test_build_cf_groups__formula_terms(tests.IrisTest): - def setUp(self): + def test_create_formula_terms(self, mocker): + cf_group = CFReader("dummy").cf_group + assert len(cf_group) == len(self.variables) + # Check there is a singular data variable. + group = cf_group.data_variables + assert len(group) == 1 + assert list(group.keys()) == ["temp"] + assert group["temp"].cf_data is self.temp + # Check there are three coordinates. + group = cf_group.coordinates + assert len(group) == 3 + coordinates = ["height", "lat", "lon"] + assert set(group.keys()) == set(coordinates) + for name in coordinates: + assert group[name].cf_data is getattr(self, name) + # Check there are three auxiliary coordinates. + group = cf_group.auxiliary_coordinates + assert len(group) == 3 + aux_coordinates = ["delta", "sigma", "orography"] + assert set(group.keys()) == set(aux_coordinates) + for name in aux_coordinates: + assert group[name].cf_data is getattr(self, name) + # Check all the auxiliary coordinates are formula terms. + formula_terms = cf_group.formula_terms + assert set(group.items()) == set(formula_terms.items()) + # Check there are three bounds. + group = cf_group.bounds + assert len(group) == 3 + bounds = ["height_bnds", "delta_bnds", "sigma_bnds"] + assert set(group.keys()) == set(bounds) + for name in bounds: + assert group[name].cf_data == getattr(self, name) + + +class Test_build_cf_groups__formula_terms: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.delta = netcdf_variable("delta", "height", np.float64, bounds="delta_bnds") self.delta_bnds = netcdf_variable("delta_bnds", "height bnds", np.float64) self.sigma = netcdf_variable("sigma", "height", np.float64, bounds="sigma_bnds") @@ -239,175 +235,144 @@ def setUp(self): ) # Restrict the CFReader functionality to only performing translations # and building first level cf-groups for variables. - patcher = mock.patch("iris.fileformats.cf.CFReader._reset") - patcher.start() - self.addCleanup(patcher.stop) - - def test_associate_formula_terms_with_data_variable(self): - with mock.patch( + mocker.patch("iris.fileformats.cf.CFReader._reset") + mocker.patch( "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", return_value=self.dataset, - ): - cf_group = CFReader("dummy").cf_group - self.assertEqual(len(cf_group), len(self.variables)) - # Check the cf-group associated with the data variable. - temp_cf_group = cf_group["temp"].cf_group - # Check the data variable is associated with eight variables. - self.assertEqual(len(temp_cf_group), 8) - # Check there are three coordinates. - group = temp_cf_group.coordinates - self.assertEqual(len(group), 3) - coordinates = ["height", "lat", "lon"] - self.assertEqual(set(group.keys()), set(coordinates)) - for name in coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check the height coordinate is bounded. - group = group["height"].cf_group - self.assertEqual(len(group.bounds), 1) - self.assertIn("height_bnds", group.bounds) - self.assertIs(group["height_bnds"].cf_data, self.height_bnds) - # Check there are five auxiliary coordinates. - group = temp_cf_group.auxiliary_coordinates - self.assertEqual(len(group), 5) - aux_coordinates = ["delta", "sigma", "orography", "x", "y"] - self.assertEqual(set(group.keys()), set(aux_coordinates)) - for name in aux_coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check all the auxiliary coordinates are formula terms. - formula_terms = cf_group.formula_terms - self.assertTrue(set(formula_terms.items()).issubset(list(group.items()))) - # Check the terms by root. - for name, term in zip(aux_coordinates, ["a", "b", "orog"]): - self.assertEqual( - formula_terms[name].cf_terms_by_root, dict(height=term) - ) - # Check the bounded auxiliary coordinates. - for name, name_bnds in zip( - ["delta", "sigma"], ["delta_bnds", "sigma_bnds"] - ): - aux_coord_group = group[name].cf_group - self.assertEqual(len(aux_coord_group.bounds), 1) - self.assertIn(name_bnds, aux_coord_group.bounds) - self.assertIs( - aux_coord_group[name_bnds].cf_data, - getattr(self, name_bnds), - ) + ) + + def test_associate_formula_terms_with_data_variable(self, mocker): + cf_group = CFReader("dummy").cf_group + assert len(cf_group) == len(self.variables) + # Check the cf-group associated with the data variable. + temp_cf_group = cf_group["temp"].cf_group + # Check the data variable is associated with eight variables. + assert len(temp_cf_group) == 8 + # Check there are three coordinates. + group = temp_cf_group.coordinates + assert len(group) == 3 + coordinates = ["height", "lat", "lon"] + assert set(group.keys()) == set(coordinates) + for name in coordinates: + assert group[name].cf_data is getattr(self, name) + # Check the height coordinate is bounded. + group = group["height"].cf_group + assert len(group.bounds) == 1 + assert "height_bnds" in group.bounds + assert group["height_bnds"].cf_data is self.height_bnds + # Check there are five auxiliary coordinates. + group = temp_cf_group.auxiliary_coordinates + assert len(group) == 5 + aux_coordinates = ["delta", "sigma", "orography", "x", "y"] + assert set(group.keys()) == set(aux_coordinates) + for name in aux_coordinates: + assert group[name].cf_data is getattr(self, name) + # Check all the auxiliary coordinates are formula terms. + formula_terms = cf_group.formula_terms + assert set(formula_terms.items()).issubset(list(group.items())) + # Check the terms by root. + for name, term in zip(aux_coordinates, ["a", "b", "orog"]): + assert formula_terms[name].cf_terms_by_root == dict(height=term) + # Check the bounded auxiliary coordinates. + for name, name_bnds in zip(["delta", "sigma"], ["delta_bnds", "sigma_bnds"]): + aux_coord_group = group[name].cf_group + assert len(aux_coord_group.bounds) == 1 + assert name_bnds in aux_coord_group.bounds + assert aux_coord_group[name_bnds].cf_data is getattr(self, name_bnds) def test_promote_reference(self): - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ): - cf_group = CFReader("dummy").cf_group - self.assertEqual(len(cf_group), len(self.variables)) - # Check the number of data variables. - self.assertEqual(len(cf_group.data_variables), 1) - self.assertEqual(list(cf_group.data_variables.keys()), ["temp"]) - # Check the number of promoted variables. - self.assertEqual(len(cf_group.promoted), 1) - self.assertEqual(list(cf_group.promoted.keys()), ["orography"]) - # Check the promoted variable dependencies. - group = cf_group.promoted["orography"].cf_group.coordinates - self.assertEqual(len(group), 2) - coordinates = ("lat", "lon") - self.assertEqual(set(group.keys()), set(coordinates)) - for name in coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) + cf_group = CFReader("dummy").cf_group + assert len(cf_group) == len(self.variables) + # Check the number of data variables. + assert len(cf_group.data_variables) == 1 + assert list(cf_group.data_variables.keys()) == ["temp"] + # Check the number of promoted variables. + assert len(cf_group.promoted) == 1 + assert list(cf_group.promoted.keys()) == ["orography"] + # Check the promoted variable dependencies. + group = cf_group.promoted["orography"].cf_group.coordinates + assert len(group) == 2 + coordinates = ("lat", "lon") + assert set(group.keys()) == set(coordinates) + for name in coordinates: + assert group[name].cf_data == getattr(self, name) def test_formula_terms_ignore(self): self.orography.dimensions = ["lat", "wibble"] - with ( - mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ), - mock.patch("warnings.warn") as warn, - ): + with pytest.warns(match="Ignoring formula terms variable"): cf_group = CFReader("dummy").cf_group group = cf_group.promoted - self.assertEqual(list(group.keys()), ["orography"]) - self.assertIs(group["orography"].cf_data, self.orography) - self.assertEqual(warn.call_count, 1) + assert list(group.keys()) == ["orography"] + assert group["orography"].cf_data == self.orography def test_auxiliary_ignore(self): self.x.dimensions = ["lat", "wibble"] - with ( - mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ), - mock.patch("warnings.warn") as warn, - ): + with pytest.warns(match=r"Ignoring variable x"): cf_group = CFReader("dummy").cf_group promoted = ["x", "orography"] group = cf_group.promoted - self.assertEqual(set(group.keys()), set(promoted)) + assert set(group.keys()) == set(promoted) for name in promoted: - self.assertIs(group[name].cf_data, getattr(self, name)) - self.assertEqual(warn.call_count, 1) + assert group[name].cf_data == getattr(self, name) def test_promoted_auxiliary_ignore(self): self.wibble = netcdf_variable("wibble", "lat wibble", np.float64) self.variables["wibble"] = self.wibble self.orography.coordinates = "wibble" - with ( - mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ), - mock.patch("warnings.warn") as warn, - ): + with pytest.warns(match="Ignoring variable wibble") as warns: cf_group = CFReader("dummy").cf_group.promoted promoted = ["wibble", "orography"] - self.assertEqual(set(cf_group.keys()), set(promoted)) + assert set(cf_group.keys()) == set(promoted) for name in promoted: - self.assertIs(cf_group[name].cf_data, getattr(self, name)) - self.assertEqual(warn.call_count, 2) + assert cf_group[name].cf_data == getattr(self, name) + # we should have got 2 warnings + assert len(warns.list) == 2 -class Test_build_cf_groups__ugrid(tests.IrisTest): - @classmethod - def setUpClass(cls): +class Test_build_cf_groups__ugrid: + @pytest.fixture(autouse=True) + def _setup_class(self, mocker): # Replicating syntax from test_CFReader.Test_build_cf_groups__formula_terms. - cls.mesh = netcdf_variable("mesh", "", int) - cls.node_x = netcdf_variable("node_x", "node", float) - cls.node_y = netcdf_variable("node_y", "node", float) - cls.face_x = netcdf_variable("face_x", "face", float) - cls.face_y = netcdf_variable("face_y", "face", float) - cls.face_nodes = netcdf_variable("face_nodes", "face vertex", int) - cls.levels = netcdf_variable("levels", "levels", int) - cls.data = netcdf_variable( + self.mesh = netcdf_variable("mesh", "", int) + self.node_x = netcdf_variable("node_x", "node", float) + self.node_y = netcdf_variable("node_y", "node", float) + self.face_x = netcdf_variable("face_x", "face", float) + self.face_y = netcdf_variable("face_y", "face", float) + self.face_nodes = netcdf_variable("face_nodes", "face vertex", int) + self.levels = netcdf_variable("levels", "levels", int) + self.data = netcdf_variable( "data", "levels face", float, coordinates="face_x face_y" ) # Add necessary attributes for mesh recognition. - cls.mesh.cf_role = "mesh_topology" - cls.mesh.node_coordinates = "node_x node_y" - cls.mesh.face_coordinates = "face_x face_y" - cls.mesh.face_node_connectivity = "face_nodes" - cls.face_nodes.cf_role = "face_node_connectivity" - cls.data.mesh = "mesh" + self.mesh.cf_role = "mesh_topology" + self.mesh.node_coordinates = "node_x node_y" + self.mesh.face_coordinates = "face_x face_y" + self.mesh.face_node_connectivity = "face_nodes" + self.face_nodes.cf_role = "face_node_connectivity" + self.data.mesh = "mesh" - cls.variables = dict( - mesh=cls.mesh, - node_x=cls.node_x, - node_y=cls.node_y, - face_x=cls.face_x, - face_y=cls.face_y, - face_nodes=cls.face_nodes, - levels=cls.levels, - data=cls.data, + self.variables = dict( + mesh=self.mesh, + node_x=self.node_x, + node_y=self.node_y, + face_x=self.face_x, + face_y=self.face_y, + face_nodes=self.face_nodes, + levels=self.levels, + data=self.data, ) ncattrs = mock.Mock(return_value=[]) - cls.dataset = mock.Mock( - file_format="NetCDF4", variables=cls.variables, ncattrs=ncattrs + self.dataset = mock.Mock( + file_format="NetCDF4", variables=self.variables, ncattrs=ncattrs ) - def setUp(self): + # @pytest.fixture(autouse=True) + # def _setup(self, mocker): # Restrict the CFReader functionality to only performing # translations and building first level cf-groups for variables. - self.patch("iris.fileformats.cf.CFReader._reset") - self.patch( + mocker.patch("iris.fileformats.cf.CFReader._reset") + mocker.patch( "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", return_value=self.dataset, ) @@ -420,17 +385,17 @@ def test_inherited(self): [CFDataVariable("data", self.data), "data_variables"], ): expected = {expected_var.cf_name: expected_var} - self.assertDictEqual(expected, getattr(self.cf_group, collection)) + assert getattr(self.cf_group, collection) == expected def test_connectivities(self): expected_var = CFUGridConnectivityVariable("face_nodes", self.face_nodes) expected = {expected_var.cf_name: expected_var} - self.assertDictEqual(expected, self.cf_group.connectivities) + assert self.cf_group.connectivities == expected def test_mesh(self): expected_var = CFUGridMeshVariable("mesh", self.mesh) expected = {expected_var.cf_name: expected_var} - self.assertDictEqual(expected, self.cf_group.meshes) + assert self.cf_group.meshes == expected def test_ugrid_coords(self): names = [f"{loc}_{ax}" for loc in ("node", "face") for ax in ("x", "y")] @@ -438,11 +403,7 @@ def test_ugrid_coords(self): name: CFUGridAuxiliaryCoordinateVariable(name, getattr(self, name)) for name in names } - self.assertDictEqual(expected, self.cf_group.ugrid_coords) + assert self.cf_group.ugrid_coords == expected def test_is_cf_ugrid_group(self): - self.assertIsInstance(self.cf_group, CFGroup) - - -if __name__ == "__main__": - tests.main() + assert isinstance(self.cf_group, CFGroup) diff --git a/lib/iris/tests/unit/fileformats/dot/test__dot_path.py b/lib/iris/tests/unit/fileformats/dot/test__dot_path.py index ce4d6d6217..679f74c51d 100644 --- a/lib/iris/tests/unit/fileformats/dot/test__dot_path.py +++ b/lib/iris/tests/unit/fileformats/dot/test__dot_path.py @@ -4,65 +4,59 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for :func:`iris.fileformats.dot._dot_path`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import os.path import subprocess -from unittest import mock + +import pytest from iris.fileformats.dot import _DOT_EXECUTABLE_PATH, _dot_path -class Test(tests.IrisTest): - def setUp(self): +class Test: + @pytest.fixture(autouse=True) + def _setup(self, mocker): # Because _dot_path is triggered by the initial import we # reset the caching status to allow us to see what happens # under different circumstances. - self.patch("iris.fileformats.dot._DOT_CHECKED", new=False) + mocker.patch("iris.fileformats.dot._DOT_CHECKED", False) # Also patch the private path variable to the existing value (i.e. no # change), and restore it after each test: As these tests modify it, # that can potentially break subsequent 'normal' behaviour. - self.patch("iris.fileformats.dot._DOT_EXECUTABLE_PATH", _DOT_EXECUTABLE_PATH) + mocker.patch("iris.fileformats.dot._DOT_EXECUTABLE_PATH", _DOT_EXECUTABLE_PATH) - def test_valid_absolute_path(self): + def test_valid_absolute_path(self, mocker): # Override the configuration value for System.dot_path real_path = os.path.abspath(__file__) assert os.path.exists(real_path) and os.path.isabs(real_path) - with mock.patch("iris.config.get_option", return_value=real_path): - result = _dot_path() - self.assertEqual(result, real_path) + mocker.patch("iris.config.get_option", return_value=real_path) + result = _dot_path() + assert result == real_path - def test_invalid_absolute_path(self): + def test_invalid_absolute_path(self, mocker): # Override the configuration value for System.dot_path dummy_path = "/not_a_real_path" * 10 assert not os.path.exists(dummy_path) - with mock.patch("iris.config.get_option", return_value=dummy_path): - result = _dot_path() - self.assertIsNone(result) + mocker.patch("iris.config.get_option", return_value=dummy_path) + result = _dot_path() + assert result is None - def test_valid_relative_path(self): + def test_valid_relative_path(self, mocker): # Override the configuration value for System.dot_path dummy_path = "not_a_real_path" * 10 assert not os.path.exists(dummy_path) - with mock.patch("iris.config.get_option", return_value=dummy_path): - # Pretend we have a valid installation of dot - with mock.patch("subprocess.check_output"): - result = _dot_path() - self.assertEqual(result, dummy_path) + mocker.patch("iris.config.get_option", return_value=dummy_path) + # Pretend we have a valid installation of dot + mocker.patch("subprocess.check_output") + result = _dot_path() + assert result == dummy_path - def test_valid_relative_path_broken_install(self): + def test_valid_relative_path_broken_install(self, mocker): # Override the configuration value for System.dot_path dummy_path = "not_a_real_path" * 10 assert not os.path.exists(dummy_path) - with mock.patch("iris.config.get_option", return_value=dummy_path): - # Pretend we have a broken installation of dot - error = subprocess.CalledProcessError(-5, "foo", "bar") - with mock.patch("subprocess.check_output", side_effect=error): - result = _dot_path() - self.assertIsNone(result) - - -if __name__ == "__main__": - tests.main() + mocker.patch("iris.config.get_option", return_value=dummy_path) + # Pretend we have a broken installation of dot + error = subprocess.CalledProcessError(-5, "foo", "bar") + mocker.patch("subprocess.check_output", side_effect=error) + result = _dot_path() + assert result is None diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py b/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py index ff80acf95b..b6cf87f5a6 100644 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py +++ b/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py @@ -4,41 +4,37 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for :func:`iris.fileformats.name_loaders._build_cell_methods`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock +import pytest import iris.coords from iris.fileformats.name_loaders import _build_cell_methods from iris.warnings import IrisLoadWarning -class Tests(tests.IrisTest): - def test_nameII_average(self): +class Tests: + def test_name_ii_average(self): av_or_int = ["something average ob bla"] coord_name = "foo" res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual(res, [iris.coords.CellMethod("mean", "foo")]) + assert res == [iris.coords.CellMethod("mean", "foo")] - def test_nameIII_averaged(self): + def test_name_iii_averaged(self): av_or_int = ["something averaged ob bla"] coord_name = "bar" res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual(res, [iris.coords.CellMethod("mean", "bar")]) + assert res == [iris.coords.CellMethod("mean", "bar")] - def test_nameII_integral(self): + def test_name_ii_integral(self): av_or_int = ["something integral ob bla"] coord_name = "ensemble" res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual(res, [iris.coords.CellMethod("sum", "ensemble")]) + assert res == [iris.coords.CellMethod("sum", "ensemble")] - def test_nameIII_integrated(self): + def test_name_iii_integrated(self): av_or_int = ["something integrated ob bla"] coord_name = "time" res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual(res, [iris.coords.CellMethod("sum", "time")]) + assert res == [iris.coords.CellMethod("sum", "time")] def test_no_averaging(self): av_or_int = [ @@ -51,9 +47,9 @@ def test_no_averaging(self): ] coord_name = "time" res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual(res, [None] * len(av_or_int)) + assert res == [None] * len(av_or_int) - def test_nameII_mixed(self): + def test_name_ii_mixed(self): av_or_int = [ "something integral ob bla", "no averaging", @@ -61,16 +57,13 @@ def test_nameII_mixed(self): ] coord_name = "ensemble" res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual( - res, - [ - iris.coords.CellMethod("sum", "ensemble"), - None, - iris.coords.CellMethod("mean", "ensemble"), - ], - ) + assert res == [ + iris.coords.CellMethod("sum", "ensemble"), + None, + iris.coords.CellMethod("mean", "ensemble"), + ] - def test_nameIII_mixed(self): + def test_name_iii_mixed(self): av_or_int = [ "something integrated ob bla", "no averaging", @@ -78,14 +71,11 @@ def test_nameIII_mixed(self): ] coord_name = "ensemble" res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual( - res, - [ - iris.coords.CellMethod("sum", "ensemble"), - None, - iris.coords.CellMethod("mean", "ensemble"), - ], - ) + assert res == [ + iris.coords.CellMethod("sum", "ensemble"), + None, + iris.coords.CellMethod("mean", "ensemble"), + ] def test_unrecognised(self): unrecognised_heading = "bla else" @@ -95,14 +85,13 @@ def test_unrecognised(self): "something integral", ] coord_name = "foo" - with mock.patch("warnings.warn") as warn: - _ = _build_cell_methods(av_or_int, coord_name) expected_msg = ( "Unknown {} statistic: {!r}. Unable to create cell method.".format( coord_name, unrecognised_heading ) ) - warn.assert_called_with(expected_msg, category=IrisLoadWarning) + with pytest.warns(IrisLoadWarning, match=expected_msg): + _ = _build_cell_methods(av_or_int, coord_name) def test_unrecognised_similar_to_no_averaging(self): unrecognised_headings = [ @@ -121,15 +110,10 @@ def test_unrecognised_similar_to_no_averaging(self): "something integral", ] coord_name = "foo" - with mock.patch("warnings.warn") as warn: - _ = _build_cell_methods(av_or_int, coord_name) expected_msg = ( "Unknown {} statistic: {!r}. Unable to create cell method.".format( coord_name, unrecognised_heading ) ) - warn.assert_called_with(expected_msg, category=IrisLoadWarning) - - -if __name__ == "__main__": - tests.main() + with pytest.warns(IrisLoadWarning, match=expected_msg): + _ = _build_cell_methods(av_or_int, coord_name) diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__build_lat_lon_for_NAME_timeseries.py b/lib/iris/tests/unit/fileformats/name_loaders/test__build_lat_lon_for_NAME_timeseries.py index 9cc7ec356a..39067fdf2f 100644 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__build_lat_lon_for_NAME_timeseries.py +++ b/lib/iris/tests/unit/fileformats/name_loaders/test__build_lat_lon_for_NAME_timeseries.py @@ -4,28 +4,25 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for :func:`iris.analysis.name_loaders._build_lat_lon_for_NAME_timeseries`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from iris.fileformats.name_loaders import NAMECoord, _build_lat_lon_for_NAME_timeseries +from iris.tests._shared_utils import assert_array_equal -class TestCellMethods(tests.IrisTest): +class TestCellMethods: def test_float(self): column_headings = { "X": ["X = -.100 Lat-Long", "X = -1.600 Lat-Long"], "Y": ["Y = 52.450 Lat-Long", "Y = 51. Lat-Long"], } lat, lon = _build_lat_lon_for_NAME_timeseries(column_headings) - self.assertIsInstance(lat, NAMECoord) - self.assertIsInstance(lon, NAMECoord) - self.assertEqual(lat.name, "latitude") - self.assertEqual(lon.name, "longitude") - self.assertIsNone(lat.dimension) - self.assertIsNone(lon.dimension) - self.assertArrayEqual(lat.values, [52.45, 51.0]) - self.assertArrayEqual(lon.values, [-0.1, -1.6]) + assert isinstance(lat, NAMECoord) + assert isinstance(lon, NAMECoord) + assert lat.name == "latitude" + assert lon.name == "longitude" + assert lat.dimension is None + assert lon.dimension is None + assert_array_equal(lat.values, [52.45, 51.0]) + assert_array_equal(lon.values, [-0.1, -1.6]) def test_int(self): column_headings = { @@ -33,13 +30,13 @@ def test_int(self): "Y": ["Y = 52 Lat-Long", "Y = 51 Lat-Long"], } lat, lon = _build_lat_lon_for_NAME_timeseries(column_headings) - self.assertIsInstance(lat, NAMECoord) - self.assertIsInstance(lon, NAMECoord) - self.assertEqual(lat.name, "latitude") - self.assertEqual(lon.name, "longitude") - self.assertIsNone(lat.dimension) - self.assertIsNone(lon.dimension) - self.assertArrayEqual(lat.values, [52.0, 51.0]) - self.assertArrayEqual(lon.values, [-1.0, -2.0]) - self.assertIsInstance(lat.values[0], float) - self.assertIsInstance(lon.values[0], float) + assert isinstance(lat, NAMECoord) + assert isinstance(lon, NAMECoord) + assert lat.name == "latitude" + assert lon.name == "longitude" + assert lat.dimension is None + assert lon.dimension is None + assert_array_equal(lat.values, [52.0, 51.0]) + assert_array_equal(lon.values, [-1.0, -2.0]) + assert isinstance(lat.values[0], float) + assert isinstance(lon.values[0], float) diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__calc_integration_period.py b/lib/iris/tests/unit/fileformats/name_loaders/test__calc_integration_period.py index 35ca2760b8..9b40aa47ff 100644 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__calc_integration_period.py +++ b/lib/iris/tests/unit/fileformats/name_loaders/test__calc_integration_period.py @@ -4,58 +4,50 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for :func:`iris.fileformats.name_loaders.__calc_integration_period`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import datetime from iris.fileformats.name_loaders import _calc_integration_period -class Test(tests.IrisTest): +class Test: def test_30_min_av(self): time_avgs = [" 30min average"] result = _calc_integration_period(time_avgs) expected = [datetime.timedelta(0, (30 * 60))] - self.assertEqual(result, expected) + assert result == expected def test_30_min_av_rspace(self): time_avgs = [" 30min average "] result = _calc_integration_period(time_avgs) expected = [datetime.timedelta(0, (30 * 60))] - self.assertEqual(result, expected) + assert result == expected def test_30_min_av_lstrip(self): time_avgs = [" 30min average".lstrip()] result = _calc_integration_period(time_avgs) expected = [datetime.timedelta(0, (30 * 60))] - self.assertEqual(result, expected) + assert result == expected def test_3_hour_av(self): time_avgs = [" 3hr 0min average"] result = _calc_integration_period(time_avgs) expected = [datetime.timedelta(0, (3 * 60 * 60))] - self.assertEqual(result, expected) + assert result == expected def test_3_hour_int(self): time_avgs = [" 3hr 0min integral"] result = _calc_integration_period(time_avgs) expected = [datetime.timedelta(0, (3 * 60 * 60))] - self.assertEqual(result, expected) + assert result == expected def test_12_hour_av(self): time_avgs = [" 12hr 0min average"] result = _calc_integration_period(time_avgs) expected = [datetime.timedelta(0, (12 * 60 * 60))] - self.assertEqual(result, expected) + assert result == expected def test_5_day_av(self): time_avgs = [" 5day 0hr 0min integral"] result = _calc_integration_period(time_avgs) expected = [datetime.timedelta(0, (5 * 24 * 60 * 60))] - self.assertEqual(result, expected) - - -if __name__ == "__main__": - tests.main() + assert result == expected diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__cf_height_from_name.py b/lib/iris/tests/unit/fileformats/name_loaders/test__cf_height_from_name.py index 86729ef024..cd32f53843 100644 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__cf_height_from_name.py +++ b/lib/iris/tests/unit/fileformats/name_loaders/test__cf_height_from_name.py @@ -7,31 +7,26 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np from iris.coords import AuxCoord from iris.fileformats.name_loaders import _cf_height_from_name -class TestAll(tests.IrisTest): - def _default_coord(self, data): - # This private method returns a coordinate with values expected - # when no interpretation is made of the field header string. - return AuxCoord( - units="no-unit", - points=data, - bounds=None, - standard_name=None, - long_name="z", - attributes={"positive": "up"}, - ) +def _default_coord(data): + # This private method returns a coordinate with values expected + # when no interpretation is made of the field header string. + return AuxCoord( + units="no-unit", + points=data, + bounds=None, + standard_name=None, + long_name="z", + attributes={"positive": "up"}, + ) -class TestAll_NAMEII(TestAll): +class TestAll_NAMEII: # NAMEII formats are defined by bounds, not points def test_bounded_height_above_ground(self): data = "From 0 - 100m agl" @@ -44,7 +39,7 @@ def test_bounded_height_above_ground(self): long_name="height above ground level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_bounded_flight_level(self): data = "From FL0 - FL100" @@ -57,7 +52,7 @@ def test_bounded_flight_level(self): long_name="flight_level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_bounded_height_above_sea_level(self): data = "From 0 - 100m asl" @@ -70,31 +65,31 @@ def test_bounded_height_above_sea_level(self): long_name="altitude above sea level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_malformed_height_above_ground(self): # Parse height above ground level with additional stuff on the end of # the string (agl). data = "From 0 - 100m agl and stuff" res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) + com = _default_coord(data) + assert com == res def test_malformed_height_above_sea_level(self): # Parse height above ground level with additional stuff on the end of # the string (agl). data = "From 0 - 100m asl and stuff" res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) + com = _default_coord(data) + assert com == res def test_malformed_flight_level(self): # Parse height above ground level with additional stuff on the end of # the string (agl). data = "From FL0 - FL100 and stuff" res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) + com = _default_coord(data) + assert com == res def test_float_bounded_height_above_ground(self): # Parse height above ground level when its a float. @@ -108,7 +103,7 @@ def test_float_bounded_height_above_ground(self): long_name="height above ground level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_float_bounded_height_flight_level(self): # Parse height above ground level, as a float (agl). @@ -122,7 +117,7 @@ def test_float_bounded_height_flight_level(self): long_name="flight_level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_float_bounded_height_above_sea_level(self): # Parse height above ground level as a float (agl). @@ -136,15 +131,15 @@ def test_float_bounded_height_above_sea_level(self): long_name="altitude above sea level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_no_match(self): # Parse height information when there is no match. # No interpretation, just returns default values. data = "Vertical integral" res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) + com = _default_coord(data) + assert com == res def test_pressure(self): # Parse air_pressure string. @@ -158,10 +153,10 @@ def test_pressure(self): long_name=None, attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res -class TestAll_NAMEIII(TestAll): +class TestAll_NAMEIII: # NAMEIII formats are defined by points, not bounds. def test_height_above_ground(self): data = "Z = 50.00000 m agl" @@ -174,7 +169,7 @@ def test_height_above_ground(self): long_name="height above ground level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_height_flight_level(self): data = "Z = 50.00000 FL" @@ -187,7 +182,7 @@ def test_height_flight_level(self): long_name="flight_level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_height_above_sea_level(self): data = "Z = 50.00000 m asl" @@ -200,31 +195,31 @@ def test_height_above_sea_level(self): long_name="altitude above sea level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_malformed_height_above_ground(self): # Parse height above ground level, with additional stuff at the string # end (agl). data = "Z = 50.00000 m agl and stuff" res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) + com = _default_coord(data) + assert com == res def test_malformed_height_above_sea_level(self): # Parse height above ground level, with additional stuff at string # end (agl). data = "Z = 50.00000 m asl and stuff" res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) + com = _default_coord(data) + assert com == res def test_malformed_flight_level(self): # Parse height above ground level (agl), with additional stuff at # string end. data = "Z = 50.00000 FL and stuff" res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) + com = _default_coord(data) + assert com == res def test_integer_height_above_ground(self): # Parse height above ground level when its an integer. @@ -238,7 +233,7 @@ def test_integer_height_above_ground(self): long_name="height above ground level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_integer_height_flight_level(self): # Parse flight level when its an integer. @@ -252,7 +247,7 @@ def test_integer_height_flight_level(self): long_name="flight_level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_integer_height_above_sea_level(self): # Parse height above sea level (asl) when its an integer. @@ -266,7 +261,7 @@ def test_integer_height_above_sea_level(self): long_name="altitude above sea level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_enotation_height_above_ground(self): # Parse height above ground expressed in scientific notation @@ -280,7 +275,7 @@ def test_enotation_height_above_ground(self): long_name="height above ground level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_enotation_height_above_sea_level(self): # Parse height above sea level expressed in scientific notation @@ -294,7 +289,7 @@ def test_enotation_height_above_sea_level(self): long_name="altitude above sea level", attributes={"positive": "up"}, ) - self.assertEqual(com, res) + assert com == res def test_pressure(self): # Parse pressure. @@ -308,8 +303,4 @@ def test_pressure(self): long_name=None, attributes={"positive": "up"}, ) - self.assertEqual(com, res) - - -if __name__ == "__main__": - tests.main() + assert com == res diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__generate_cubes.py b/lib/iris/tests/unit/fileformats/name_loaders/test__generate_cubes.py index fc00db9663..7238b68d31 100644 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__generate_cubes.py +++ b/lib/iris/tests/unit/fileformats/name_loaders/test__generate_cubes.py @@ -4,20 +4,17 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for :func:`iris.analysis.name_loaders._generate_cubes`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from datetime import datetime, timedelta from unittest import mock import numpy as np from iris.fileformats.name_loaders import NAMECoord, _generate_cubes +from iris.tests._shared_utils import assert_array_equal -class TestCellMethods(tests.IrisTest): - def test_cell_methods(self): +class TestCellMethods: + def test_cell_methods(self, mocker): header = mock.MagicMock() column_headings = { "Species": [1, 2, 3], @@ -29,8 +26,8 @@ def test_cell_methods(self): data_arrays = [mock.Mock(), mock.Mock()] cell_methods = ["cell_method_1", "cell_method_2"] - self.patch("iris.fileformats.name_loaders._cf_height_from_name") - self.patch("iris.cube.Cube") + mocker.patch("iris.fileformats.name_loaders._cf_height_from_name") + mocker.patch("iris.cube.Cube") cubes = list( _generate_cubes(header, column_headings, coords, data_arrays, cell_methods) ) @@ -39,8 +36,9 @@ def test_cell_methods(self): cubes[1].assert_has_calls([mock.call.add_cell_method("cell_method_2")]) -class TestCircularLongitudes(tests.IrisTest): - def _simulate_with_coords(self, names, values, dimensions): +class TestCircularLongitudes: + @staticmethod + def _simulate_with_coords(mocker, names, values, dimensions): header = mock.MagicMock() column_headings = { "Species": [1, 2, 3], @@ -54,50 +52,53 @@ def _simulate_with_coords(self, names, values, dimensions): ] data_arrays = [mock.Mock()] - self.patch("iris.fileformats.name_loaders._cf_height_from_name") - self.patch("iris.cube.Cube") + mocker.patch("iris.fileformats.name_loaders._cf_height_from_name") + mocker.patch("iris.cube.Cube") cubes = list(_generate_cubes(header, column_headings, coords, data_arrays)) return cubes - def test_non_circular(self): + def test_non_circular(self, mocker): results = self._simulate_with_coords( - names=["longitude"], values=[[1, 7, 23]], dimensions=[0] + mocker, names=["longitude"], values=[[1, 7, 23]], dimensions=[0] ) - self.assertEqual(len(results), 1) + assert len(results) == 1 add_coord_calls = results[0].add_dim_coord.call_args_list - self.assertEqual(len(add_coord_calls), 1) + assert len(add_coord_calls) == 1 coord = add_coord_calls[0][0][0] - self.assertEqual(coord.circular, False) + assert coord.circular is False - def test_circular(self): + def test_circular(self, mocker): results = self._simulate_with_coords( + mocker, names=["longitude"], values=[[5.0, 95.0, 185.0, 275.0]], dimensions=[0], ) - self.assertEqual(len(results), 1) + assert len(results) == 1 add_coord_calls = results[0].add_dim_coord.call_args_list - self.assertEqual(len(add_coord_calls), 1) + assert len(add_coord_calls) == 1 coord = add_coord_calls[0][0][0] - self.assertEqual(coord.circular, True) + assert coord.circular is True - def test_lat_lon_byname(self): + def test_lat_lon_byname(self, mocker): results = self._simulate_with_coords( + mocker, names=["longitude", "latitude"], values=[[5.0, 95.0, 185.0, 275.0], [5.0, 95.0, 185.0, 275.0]], dimensions=[0, 1], ) - self.assertEqual(len(results), 1) + assert len(results) == 1 add_coord_calls = results[0].add_dim_coord.call_args_list - self.assertEqual(len(add_coord_calls), 2) + assert len(add_coord_calls) == 2 lon_coord = add_coord_calls[0][0][0] lat_coord = add_coord_calls[1][0][0] - self.assertEqual(lon_coord.circular, True) - self.assertEqual(lat_coord.circular, False) + assert lon_coord.circular is True + assert lat_coord.circular is False -class TestTimeCoord(tests.IrisTest): - def _simulate_with_coords(self, names, values, dimensions): +class TestTimeCoord: + @staticmethod + def _simulate_with_coords(mocker, names, values, dimensions): header = mock.MagicMock() column_headings = { "Species": [1, 2, 3], @@ -111,13 +112,14 @@ def _simulate_with_coords(self, names, values, dimensions): ] data_arrays = [mock.Mock()] - self.patch("iris.fileformats.name_loaders._cf_height_from_name") - self.patch("iris.cube.Cube") + mocker.patch("iris.fileformats.name_loaders._cf_height_from_name") + mocker.patch("iris.cube.Cube") cubes = list(_generate_cubes(header, column_headings, coords, data_arrays)) return cubes - def test_time_dim(self): + def test_time_dim(self, mocker): results = self._simulate_with_coords( + mocker, names=["longitude", "latitude", "time"], values=[ [10, 20], @@ -126,33 +128,30 @@ def test_time_dim(self): ], dimensions=[0, 1, 2], ) - self.assertEqual(len(results), 1) + assert len(results) == 1 result = results[0] dim_coord_calls = result.add_dim_coord.call_args_list - self.assertEqual(len(dim_coord_calls), 3) # lon, lat, time + assert len(dim_coord_calls) == 3 # lon, lat, time t_coord = dim_coord_calls[2][0][0] - self.assertEqual(t_coord.standard_name, "time") - self.assertArrayEqual(t_coord.points, [398232, 398256]) - self.assertArrayEqual(t_coord.bounds[0], [398208, 398232]) - self.assertArrayEqual(t_coord.bounds[-1], [398232, 398256]) + assert t_coord.standard_name == "time" + assert_array_equal(t_coord.points, [398232, 398256]) + assert_array_equal(t_coord.bounds[0], [398208, 398232]) + assert_array_equal(t_coord.bounds[-1], [398232, 398256]) - def test_time_scalar(self): + def test_time_scalar(self, mocker): results = self._simulate_with_coords( + mocker, names=["longitude", "latitude", "time"], values=[[10, 20], [30, 40], [datetime(2015, 6, 7)]], dimensions=[0, 1, None], ) - self.assertEqual(len(results), 1) + assert len(results) == 1 result = results[0] dim_coord_calls = result.add_dim_coord.call_args_list - self.assertEqual(len(dim_coord_calls), 2) + assert len(dim_coord_calls) == 2 aux_coord_calls = result.add_aux_coord.call_args_list - self.assertEqual(len(aux_coord_calls), 1) + assert len(aux_coord_calls) == 1 t_coord = aux_coord_calls[0][0][0] - self.assertEqual(t_coord.standard_name, "time") - self.assertArrayEqual(t_coord.points, [398232]) - self.assertArrayEqual(t_coord.bounds, [[398208, 398232]]) - - -if __name__ == "__main__": - tests.main() + assert t_coord.standard_name == "time" + assert_array_equal(t_coord.points, [398232]) + assert_array_equal(t_coord.bounds, [[398208, 398232]]) diff --git a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_units.py b/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_units.py index 2767807377..5c6c59c3a1 100644 --- a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_units.py +++ b/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_units.py @@ -4,24 +4,22 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.fileformats.nimrod_load_rules.units` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - import numpy as np +import pytest from iris.cube import Cube from iris.fileformats.nimrod import NimrodField from iris.fileformats.nimrod_load_rules import NIMROD_DEFAULT, units +from iris.tests._shared_utils import ( + assert_array_almost_equal, + assert_no_warnings_regexp, +) -class Test(tests.IrisTest): - NIMROD_LOCATION = "iris.fileformats.nimrod_load_rules" - - def setUp(self): - self.field = mock.Mock( +class Test: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.field = mocker.Mock( units="", int_mdi=-32767, float32_mdi=NIMROD_DEFAULT, @@ -36,90 +34,78 @@ def _call_units(self, data=None, units_str=None): self.field.units = units_str units(self.cube, self.field) - def test_null(self): - with mock.patch("warnings.warn") as warn: + def test_null(self, mocker): + with assert_no_warnings_regexp(): self._call_units(units_str="m") - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "m") - self.assertArrayAlmostEqual(self.cube.data, np.ones_like(self.cube.data)) + assert self.cube.units == "m" + assert_array_almost_equal(self.cube.data, np.ones_like(self.cube.data)) - def test_times32(self): - with mock.patch("warnings.warn") as warn: + def test_times32(self, mocker): + with assert_no_warnings_regexp(): self._call_units( data=np.ones_like(self.cube.data) * 32, units_str="mm/hr*32" ) - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "mm/hr") - self.assertArrayAlmostEqual(self.cube.data, np.ones_like(self.cube.data)) - self.assertEqual(self.cube.data.dtype, np.float32) + assert self.cube.units == "mm/hr" + assert_array_almost_equal(self.cube.data, np.ones_like(self.cube.data)) + assert self.cube.data.dtype == np.float32 - def test_visibility_units(self): - with mock.patch("warnings.warn") as warn: + def test_visibility_units(self, mocker): + with assert_no_warnings_regexp(): self._call_units( data=((np.ones_like(self.cube.data) / 2) - 25000), units_str="m/2-25k", ) - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "m") - self.assertArrayAlmostEqual(self.cube.data, np.ones_like(self.cube.data)) - self.assertEqual(self.cube.data.dtype, np.float32) + assert self.cube.units == "m" + assert_array_almost_equal(self.cube.data, np.ones_like(self.cube.data)) + assert self.cube.data.dtype == np.float32 - def test_power_in_units(self): - with mock.patch("warnings.warn") as warn: + def test_power_in_units(self, mocker): + with assert_no_warnings_regexp(): self._call_units( data=np.ones_like(self.cube.data) * 1000, units_str="mm*10^3" ) - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "mm") - self.assertArrayAlmostEqual(self.cube.data, np.ones_like(self.cube.data)) - self.assertEqual(self.cube.data.dtype, np.float32) + assert self.cube.units == "mm" + assert_array_almost_equal(self.cube.data, np.ones_like(self.cube.data)) + assert self.cube.data.dtype == np.float32 - def test_ug_per_m3_units(self): - with mock.patch("warnings.warn") as warn: + def test_ug_per_m3_units(self, mocker): + with assert_no_warnings_regexp(): self._call_units( data=(np.ones_like(self.cube.data) * 10), units_str="ug/m3E1", ) - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "ug/m3") - self.assertArrayAlmostEqual(self.cube.data, np.ones_like(self.cube.data)) - self.assertEqual(self.cube.data.dtype, np.float32) + assert self.cube.units == "ug/m3" + assert_array_almost_equal(self.cube.data, np.ones_like(self.cube.data)) + assert self.cube.data.dtype == np.float32 - def test_g_per_kg(self): - with mock.patch("warnings.warn") as warn: + def test_g_per_kg(self, mocker): + with assert_no_warnings_regexp(): self._call_units( data=(np.ones_like(self.cube.data) * 1000), units_str="g/Kg" ) - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "kg/kg") - self.assertArrayAlmostEqual(self.cube.data, np.ones_like(self.cube.data)) - self.assertEqual(self.cube.data.dtype, np.float32) + assert self.cube.units == "kg/kg" + assert_array_almost_equal(self.cube.data, np.ones_like(self.cube.data)) + assert self.cube.data.dtype == np.float32 - def test_unit_expection_dictionary(self): - with mock.patch("warnings.warn") as warn: + def test_unit_expection_dictionary(self, mocker): + with assert_no_warnings_regexp(): self._call_units(units_str="mb") - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "hPa") - self.assertArrayAlmostEqual(self.cube.data, np.ones_like(self.cube.data)) - self.assertEqual(self.cube.data.dtype, np.float32) + assert self.cube.units == "hPa" + assert_array_almost_equal(self.cube.data, np.ones_like(self.cube.data)) + assert self.cube.data.dtype == np.float32 - def test_per_second(self): - with mock.patch("warnings.warn") as warn: + def test_per_second(self, mocker): + with assert_no_warnings_regexp(): self._call_units(units_str="/s") - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "s^-1") - self.assertArrayAlmostEqual(self.cube.data, np.ones_like(self.cube.data)) - self.assertEqual(self.cube.data.dtype, np.float32) + assert self.cube.units == "s^-1" + assert_array_almost_equal(self.cube.data, np.ones_like(self.cube.data)) + assert self.cube.data.dtype == np.float32 - def test_unhandled_unit(self): - with mock.patch("warnings.warn") as warn: + def test_unhandled_unit(self, mocker): + warning_message = "Unhandled units 'kittens' recorded in cube attributes" + with pytest.warns(match=warning_message): self._call_units(units_str="kittens") - self.assertEqual(warn.call_count, 1) - self.assertEqual(self.cube.units, "") - self.assertArrayAlmostEqual(self.cube.data, np.ones_like(self.cube.data)) - self.assertEqual(self.cube.data.dtype, np.float32) - self.assertEqual(self.cube.attributes["invalid_units"], "kittens") - - -if __name__ == "__main__": - tests.main() + assert self.cube.units == "" + assert_array_almost_equal(self.cube.data, np.ones_like(self.cube.data)) + assert self.cube.data.dtype == np.float32 + assert self.cube.attributes["invalid_units"] == "kittens" diff --git a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py b/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py index 809c54726c..2a3d06e56a 100644 --- a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py +++ b/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py @@ -7,11 +7,7 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock +import pytest from iris.fileformats.nimrod import NimrodField from iris.fileformats.nimrod_load_rules import ( @@ -19,13 +15,13 @@ TranslationWarning, vertical_coord, ) +from iris.tests._shared_utils import assert_no_warnings_regexp -class Test(tests.IrisTest): - NIMROD_LOCATION = "iris.fileformats.nimrod_load_rules" - - def setUp(self): - self.field = mock.Mock( +class Test: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.field = mocker.Mock( vertical_coord=NIMROD_DEFAULT, vertical_coord_type=NIMROD_DEFAULT, reference_vertical_coord=NIMROD_DEFAULT, @@ -34,7 +30,7 @@ def setUp(self): float32_mdi=NIMROD_DEFAULT, spec=NimrodField, ) - self.cube = mock.Mock() + self.cube = mocker.Mock() def _call_vertical_coord( self, @@ -54,23 +50,15 @@ def _call_vertical_coord( vertical_coord(self.cube, self.field) def test_unhandled(self): - with mock.patch("warnings.warn") as warn: + message_regexp = "Vertical coord -1 not yet handled" + with pytest.warns(TranslationWarning, match=message_regexp): self._call_vertical_coord(vertical_coord_val=1.0, vertical_coord_type=-1) - warn.assert_called_once_with( - "Vertical coord -1 not yet handled", category=TranslationWarning - ) def test_null(self): - with mock.patch("warnings.warn") as warn: + with assert_no_warnings_regexp(): self._call_vertical_coord(vertical_coord_type=NIMROD_DEFAULT) self._call_vertical_coord(vertical_coord_type=self.field.int_mdi) - self.assertEqual(warn.call_count, 0) def test_ground_level(self): - with mock.patch("warnings.warn") as warn: + with assert_no_warnings_regexp(): self._call_vertical_coord(vertical_coord_val=9999.0, vertical_coord_type=0) - self.assertEqual(warn.call_count, 0) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/__init__.py b/lib/iris/tests/unit/fileformats/pp_load_rules/__init__.py index c8361feae4..dd6485b015 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/__init__.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/__init__.py @@ -3,3 +3,26 @@ # 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.fileformats.pp_load_rules` module.""" + + +# a general utility function for PP field tests +def assert_coords_and_dims_lists_match(coords_and_dims_got, coords_and_dims_expected): + """Check that coords_and_dims lists are equivalent. + + The arguments are lists of pairs of (coordinate, dimensions). + The elements are compared one-to-one, by coordinate name (so the order + of the lists is _not_ significant). + It also checks that the coordinate types (DimCoord/AuxCoord) match. + + """ + + def sorted_by_coordname(list): + return sorted(list, key=lambda item: item[0].name()) + + coords_and_dims_got = sorted_by_coordname(coords_and_dims_got) + coords_and_dims_expected = sorted_by_coordname(coords_and_dims_expected) + assert coords_and_dims_got == coords_and_dims_expected + # Also check coordinate type equivalences (as Coord.__eq__ does not). + assert [type(coord) for coord, dims in coords_and_dims_got] == [ + type(coord) for coord, dims in coords_and_dims_expected + ] diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py index e2c71790b4..05f9c6f82b 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py @@ -4,10 +4,6 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.fileformats.pp_load_rules._all_other_rules` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from unittest import mock from cf_units import CALENDAR_360_DAY, Unit @@ -17,7 +13,7 @@ from iris.coords import AuxCoord, CellMethod, DimCoord from iris.fileformats.pp import SplittableInt from iris.fileformats.pp_load_rules import _all_other_rules -from iris.tests.unit.fileformats import TestField +from iris.tests.unit.fileformats.pp_load_rules import assert_coords_and_dims_lists_match # iris.fileformats.pp_load_rules._all_other_rules() returns a tuple of # of various metadata. This constant is the index into this @@ -27,88 +23,88 @@ AUX_COORDS_INDEX = 7 -class TestCellMethods(tests.IrisTest): - def test_time_mean(self): +class TestCellMethods: + def test_time_mean(self, mocker): # lbproc = 128 -> mean # lbtim.ib = 2 -> simple t1 to t2 interval. - field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=0, ib=2, ic=3)) + field = mocker.MagicMock(lbproc=128, lbtim=mocker.Mock(ia=0, ib=2, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod("mean", "time")] - self.assertEqual(res, expected) + assert res == expected - def test_hourly_mean(self): + def test_hourly_mean(self, mocker): # lbtim.ia = 1 -> hourly - field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=1, ib=2, ic=3)) + field = mocker.MagicMock(lbproc=128, lbtim=mocker.Mock(ia=1, ib=2, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod("mean", "time", "1 hour")] - self.assertEqual(res, expected) + assert res == expected - def test_daily_mean(self): + def test_daily_mean(self, mocker): # lbtim.ia = 24 -> daily - field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=24, ib=2, ic=3)) + field = mocker.MagicMock(lbproc=128, lbtim=mocker.Mock(ia=24, ib=2, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod("mean", "time", "24 hour")] - self.assertEqual(res, expected) + assert res == expected - def test_custom_max(self): - field = mock.MagicMock(lbproc=8192, lbtim=mock.Mock(ia=47, ib=2, ic=3)) + def test_custom_max(self, mocker): + field = mocker.MagicMock(lbproc=8192, lbtim=mocker.Mock(ia=47, ib=2, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod("maximum", "time", "47 hour")] - self.assertEqual(res, expected) + assert res == expected - def test_daily_min(self): + def test_daily_min(self, mocker): # lbproc = 4096 -> min - field = mock.MagicMock(lbproc=4096, lbtim=mock.Mock(ia=24, ib=2, ic=3)) + field = mocker.MagicMock(lbproc=4096, lbtim=mocker.Mock(ia=24, ib=2, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod("minimum", "time", "24 hour")] - self.assertEqual(res, expected) + assert res == expected - def test_time_mean_over_multiple_years(self): + def test_time_mean_over_multiple_years(self, mocker): # lbtim.ib = 3 -> interval within a year, over multiple years. - field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=0, ib=3, ic=3)) + field = mocker.MagicMock(lbproc=128, lbtim=mocker.Mock(ia=0, ib=3, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [ CellMethod("mean within years", "time"), CellMethod("mean over years", "time"), ] - self.assertEqual(res, expected) + assert res == expected - def test_hourly_mean_over_multiple_years(self): - field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=1, ib=3, ic=3)) + def test_hourly_mean_over_multiple_years(self, mocker): + field = mocker.MagicMock(lbproc=128, lbtim=mocker.Mock(ia=1, ib=3, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [ CellMethod("mean within years", "time", "1 hour"), CellMethod("mean over years", "time"), ] - self.assertEqual(res, expected) + assert res == expected - def test_climatology_max(self): - field = mock.MagicMock(lbproc=8192, lbtim=mock.Mock(ia=24, ib=3, ic=3)) + def test_climatology_max(self, mocker): + field = mocker.MagicMock(lbproc=8192, lbtim=mocker.Mock(ia=24, ib=3, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod("maximum", "time")] - self.assertEqual(res, expected) + assert res == expected - def test_climatology_min(self): - field = mock.MagicMock(lbproc=4096, lbtim=mock.Mock(ia=24, ib=3, ic=3)) + def test_climatology_min(self, mocker): + field = mocker.MagicMock(lbproc=4096, lbtim=mocker.Mock(ia=24, ib=3, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod("minimum", "time")] - self.assertEqual(res, expected) + assert res == expected - def test_other_lbtim_ib(self): + def test_other_lbtim_ib(self, mocker): # lbtim.ib = 5 -> non-specific aggregation - field = mock.MagicMock(lbproc=4096, lbtim=mock.Mock(ia=24, ib=5, ic=3)) + field = mocker.MagicMock(lbproc=4096, lbtim=mocker.Mock(ia=24, ib=5, ic=3)) res = _all_other_rules(field)[CELL_METHODS_INDEX] expected = [CellMethod("minimum", "time")] - self.assertEqual(res, expected) + assert res == expected - def test_multiple_unordered_lbprocs(self): - field = mock.MagicMock( + def test_multiple_unordered_lbprocs(self, mocker): + field = mocker.MagicMock( lbproc=192, bzx=0, bdx=1, lbnpt=3, lbrow=3, - lbtim=mock.Mock(ia=24, ib=5, ic=3), + lbtim=mocker.Mock(ia=24, ib=5, ic=3), lbcode=SplittableInt(1), x_bounds=None, _x_coord_name=lambda: "longitude", @@ -122,16 +118,16 @@ def test_multiple_unordered_lbprocs(self): CellMethod("mean", "time"), CellMethod("mean", "longitude"), ] - self.assertEqual(res, expected) + assert res == expected - def test_multiple_unordered_rotated_lbprocs(self): - field = mock.MagicMock( + def test_multiple_unordered_rotated_lbprocs(self, mocker): + field = mocker.MagicMock( lbproc=192, bzx=0, bdx=1, lbnpt=3, lbrow=3, - lbtim=mock.Mock(ia=24, ib=5, ic=3), + lbtim=mocker.Mock(ia=24, ib=5, ic=3), lbcode=SplittableInt(101), x_bounds=None, _x_coord_name=lambda: "grid_longitude", @@ -145,15 +141,15 @@ def test_multiple_unordered_rotated_lbprocs(self): CellMethod("mean", "time"), CellMethod("mean", "grid_longitude"), ] - self.assertEqual(res, expected) + assert res == expected -class TestCrossSectionalTime(TestField): - def test_lbcode3x23(self): +class TestCrossSectionalTime: + def test_lbcode3x23(self, mocker): time_bounds = np.array( [[0.875, 1.125], [1.125, 1.375], [1.375, 1.625], [1.625, 1.875]] ) - field = mock.MagicMock( + field = mocker.MagicMock( lbproc=0, bzx=0, bdx=0, @@ -161,7 +157,7 @@ def test_lbcode3x23(self): lbrow=4, t1=nc_datetime(2000, 1, 2, hour=0, minute=0, second=0), t2=nc_datetime(2000, 1, 3, hour=0, minute=0, second=0), - lbtim=mock.Mock(ia=1, ib=2, ic=2), + lbtim=mocker.Mock(ia=1, ib=2, ic=2), lbcode=SplittableInt(31323, {"iy": slice(0, 2), "ix": slice(2, 4)}), x_bounds=None, y_bounds=time_bounds, @@ -207,10 +203,10 @@ def test_lbcode3x23(self): 0, ) ] - self.assertCoordsAndDimsListsMatch(res, expected) + assert_coords_and_dims_lists_match(res, expected) -class TestLBTIMx2x_ZeroYears(TestField): +class TestLBTIMx2x_ZeroYears: _spec = [ "lbtim", "lbcode", @@ -278,29 +274,25 @@ def test_month_coord(self): None, ), ] - self.assertCoordsAndDimsListsMatch(res, expected) + assert_coords_and_dims_lists_match(res, expected) def test_diff_month(self): field = self._make_field(lbmon=3, lbmond=4) field.mock_add_spec(self._spec) res = _all_other_rules(field)[AUX_COORDS_INDEX] - self.assertCoordsAndDimsListsMatch(res, []) + assert_coords_and_dims_lists_match(res, []) def test_nonzero_year(self): field = self._make_field(lbyr=1) field.mock_add_spec(self._spec) res = _all_other_rules(field)[AUX_COORDS_INDEX] - self.assertCoordsAndDimsListsMatch(res, []) + assert_coords_and_dims_lists_match(res, []) def test_nonzero_yeard(self): field = self._make_field(lbyrd=1) field.mock_add_spec(self._spec) res = _all_other_rules(field)[AUX_COORDS_INDEX] - self.assertCoordsAndDimsListsMatch(res, []) - - -if __name__ == "__main__": - tests.main() + assert_coords_and_dims_lists_match(res, []) diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py index 7d502bc2d6..523aef0e64 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py @@ -7,56 +7,53 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np from iris.fileformats.pp_load_rules import _collapse_degenerate_points_and_bounds +from iris.tests._shared_utils import assert_array_equal -class Test(tests.IrisTest): +class Test: def test_scalar(self): array = np.array(1) points, bounds = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(points, array) - self.assertIsNone(bounds) + assert_array_equal(points, array) + assert bounds is None def test_1d_nochange(self): array = np.array([1, 1, 3]) result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, array) + assert_array_equal(result, array) def test_1d_collapse(self): array = np.array([1, 1, 1]) result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, np.array([1])) + assert_array_equal(result, np.array([1])) def test_2d_nochange(self): array = np.array([[1, 2, 3], [4, 5, 6]]) result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, array) + assert_array_equal(result, array) def test_2d_collapse_dim0(self): array = np.array([[1, 2, 3], [1, 2, 3]]) result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, np.array([[1, 2, 3]])) + assert_array_equal(result, np.array([[1, 2, 3]])) def test_2d_collapse_dim1(self): array = np.array([[1, 1, 1], [2, 2, 2]]) result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, np.array([[1], [2]])) + assert_array_equal(result, np.array([[1], [2]])) def test_2d_collapse_both(self): array = np.array([[3, 3, 3], [3, 3, 3]]) result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, np.array([[3]])) + assert_array_equal(result, np.array([[3]])) def test_3d(self): array = np.array([[[3, 3, 3], [4, 4, 4]], [[3, 3, 3], [4, 4, 4]]]) result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, np.array([[[3], [4]]])) + assert_array_equal(result, np.array([[[3], [4]]])) def test_multiple_odd_dims(self): # Test to ensure multiple collapsed dimensions don't interfere. @@ -66,24 +63,20 @@ def test_multiple_odd_dims(self): array[:, :, 1:] = array[:, :, 0:1] array[:, :, :, 1:] = array[:, :, :, 0:1] result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertEqual(array.shape, (3, 3, 3, 3, 3)) - self.assertEqual(result.shape, (1, 3, 1, 1, 3)) - self.assertTrue(np.all(result == array[0:1, :, 0:1, 0:1, :])) + assert array.shape == (3, 3, 3, 3, 3) + assert result.shape == (1, 3, 1, 1, 3) + assert np.all(result == array[0:1, :, 0:1, 0:1, :]) def test_bounds_collapse(self): points = np.array([1, 1, 1]) bounds = np.array([[0, 1], [0, 1], [0, 1]]) result_pts, result_bds = _collapse_degenerate_points_and_bounds(points, bounds) - self.assertArrayEqual(result_pts, np.array([1])) - self.assertArrayEqual(result_bds, np.array([[0, 1]])) + assert_array_equal(result_pts, np.array([1])) + assert_array_equal(result_bds, np.array([[0, 1]])) def test_bounds_no_collapse(self): points = np.array([1, 1, 1]) bounds = np.array([[0, 1], [0, 1], [0, 2]]) result_pts, result_bds = _collapse_degenerate_points_and_bounds(points, bounds) - self.assertArrayEqual(result_pts, points) - self.assertArrayEqual(result_bds, bounds) - - -if __name__ == "__main__": - tests.main() + assert_array_equal(result_pts, points) + assert_array_equal(result_bds, bounds) diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_pseudo_level_coords.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_pseudo_level_coords.py index bc3cf8ed86..ad15d7a395 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_pseudo_level_coords.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_pseudo_level_coords.py @@ -7,27 +7,17 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from iris.coords import DimCoord from iris.fileformats.pp_load_rules import _convert_scalar_pseudo_level_coords -from iris.tests.unit.fileformats import TestField -class Test(TestField): +class Test: def test_valid(self): coords_and_dims = _convert_scalar_pseudo_level_coords(lbuser5=21) - self.assertEqual( - coords_and_dims, - [(DimCoord([21], long_name="pseudo_level", units="1"), None)], - ) + assert coords_and_dims == [ + (DimCoord([21], long_name="pseudo_level", units="1"), None) + ] def test_missing_indicator(self): coords_and_dims = _convert_scalar_pseudo_level_coords(lbuser5=0) - self.assertEqual(coords_and_dims, []) - - -if __name__ == "__main__": - tests.main() + assert coords_and_dims == [] diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_realization_coords.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_realization_coords.py index ac28fe0a1c..5d01eb5976 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_realization_coords.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_realization_coords.py @@ -7,27 +7,17 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from iris.coords import DimCoord from iris.fileformats.pp_load_rules import _convert_scalar_realization_coords -from iris.tests.unit.fileformats import TestField -class Test(TestField): +class Test: def test_valid(self): coords_and_dims = _convert_scalar_realization_coords(lbrsvd4=21) - self.assertEqual( - coords_and_dims, - [(DimCoord([21], standard_name="realization", units="1"), None)], - ) + assert coords_and_dims == [ + (DimCoord([21], standard_name="realization", units="1"), None) + ] def test_missing_indicator(self): coords_and_dims = _convert_scalar_realization_coords(lbrsvd4=0) - self.assertEqual(coords_and_dims, []) - - -if __name__ == "__main__": - tests.main() + assert coords_and_dims == [] diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py index 5cebc009b9..690f869a34 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py @@ -7,10 +7,6 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from cf_units import CALENDAR_360_DAY, CALENDAR_STANDARD, Unit from cftime import datetime as nc_datetime import numpy as np @@ -18,7 +14,8 @@ from iris.coords import AuxCoord, DimCoord from iris.fileformats.pp import SplittableInt from iris.fileformats.pp_load_rules import _convert_time_coords -from iris.tests.unit.fileformats import TestField +from iris.tests._shared_utils import assert_array_all_close +from iris.tests.unit.fileformats.pp_load_rules import assert_coords_and_dims_lists_match def _lbtim(ia=0, ib=0, ic=0): @@ -40,7 +37,7 @@ def _lbcode(value=None, ix=None, iy=None): _HOURS_UNIT = Unit("hours") -class TestLBTIMx0x_SingleTimepoint(TestField): +class TestLBTIMx0x_SingleTimepoint: def _check_timepoint(self, lbcode, expect_match=True): lbtim = _lbtim(ib=0, ic=1) t1 = nc_datetime(1970, 1, 1, hour=6, minute=0, second=0) @@ -69,7 +66,7 @@ def _check_timepoint(self, lbcode, expect_match=True): ] else: expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) + assert_coords_and_dims_lists_match(coords_and_dims, expect_result) def test_normal_xy_dims(self): self._check_timepoint(_lbcode(1)) @@ -81,7 +78,7 @@ def test_time_cross_section(self): self._check_timepoint(_lbcode(ix=1, iy=20), expect_match=False) -class TestLBTIMx1x_Forecast(TestField): +class TestLBTIMx1x_Forecast: def _check_forecast(self, lbcode, expect_match=True): lbtim = _lbtim(ib=1, ic=1) # Validity time @@ -126,7 +123,7 @@ def _check_forecast(self, lbcode, expect_match=True): ] else: expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) + assert_coords_and_dims_lists_match(coords_and_dims, expect_result) def test_normal_xy(self): self._check_forecast(_lbcode(1)) @@ -151,8 +148,8 @@ def test_exact_hours(self): ) (fp, _), (t, _), (frt, _) = coords_and_dims # These should both be exact whole numbers. - self.assertEqual(fp.points[0], 7) - self.assertEqual(t.points[0], 394927) + assert fp.points[0] == 7 + assert t.points[0] == 394927 def test_not_exact_hours(self): lbtim = _lbtim(ib=1, ic=1) @@ -167,11 +164,11 @@ def test_not_exact_hours(self): lbft=None, ) (fp, _), (t, _), (frt, _) = coords_and_dims - self.assertArrayAllClose(fp.points[0], 7.1666666, atol=0.0001, rtol=0) - self.assertArrayAllClose(t.points[0], 394927.166666, atol=0.01, rtol=0) + assert_array_all_close(fp.points[0], 7.1666666, atol=0.0001, rtol=0) + assert_array_all_close(t.points[0], 394927.166666, atol=0.01, rtol=0) -class TestLBTIMx2x_TimePeriod(TestField): +class TestLBTIMx2x_TimePeriod: def _check_period(self, lbcode, expect_match=True): lbtim = _lbtim(ib=2, ic=1) # Start time @@ -218,7 +215,7 @@ def _check_period(self, lbcode, expect_match=True): ] else: expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) + assert_coords_and_dims_lists_match(coords_and_dims, expect_result) def test_normal_xy(self): self._check_period(_lbcode(1)) @@ -230,7 +227,7 @@ def test_time_cross_section(self): self._check_period(_lbcode(ix=1, iy=20), expect_match=False) -class TestLBTIMx3x_YearlyAggregation(TestField): +class TestLBTIMx3x_YearlyAggregation: def _check_yearly(self, lbcode, expect_match=True): lbtim = _lbtim(ib=3, ic=1) # Start time @@ -280,7 +277,7 @@ def _check_yearly(self, lbcode, expect_match=True): ] else: expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) + assert_coords_and_dims_lists_match(coords_and_dims, expect_result) def test_normal_xy(self): self._check_yearly(_lbcode(1)) @@ -292,7 +289,7 @@ def test_time_cross_section(self): self._check_yearly(_lbcode(ix=1, iy=20), expect_match=False) -class TestLBTIMx2x_ZeroYear(TestField): +class TestLBTIMx2x_ZeroYear: def test_(self): lbtim = _lbtim(ib=2, ic=1) t1 = nc_datetime(0, 1, 1, has_year_zero=True) @@ -307,10 +304,10 @@ def test_(self): t2=t2, lbft=lbft, ) - self.assertEqual(coords_and_dims, []) + assert coords_and_dims == [] -class TestLBTIMxxx_Unhandled(TestField): +class TestLBTIMxxx_Unhandled: def test_unrecognised(self): lbtim = _lbtim(ib=4, ic=1) t1 = nc_datetime(0, 0, 0, calendar=None, has_year_zero=True) @@ -325,10 +322,10 @@ def test_unrecognised(self): t2=t2, lbft=lbft, ) - self.assertEqual(coords_and_dims, []) + assert coords_and_dims == [] -class TestLBCODE3xx(TestField): +class TestLBCODE3xx: def test(self): lbcode = _lbcode(value=31323) lbtim = _lbtim(ib=2, ic=2) @@ -355,10 +352,10 @@ def test(self): None, ) ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected_result) + assert_coords_and_dims_lists_match(coords_and_dims, expected_result) -class TestArrayInputWithLBTIM_0_0_1(TestField): +class TestArrayInputWithLBTIM_0_0_1: def test_t1_list(self): # lbtim ia = 0, ib = 0, ic = 1 # with a series of times (t1). @@ -387,10 +384,10 @@ def test_t1_list(self): (24 * 8) + 3 + hours, standard_name="time", units=_EPOCH_HOURS_UNIT ) expected = [(time_coord, (0,))] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) + assert_coords_and_dims_lists_match(coords_and_dims, expected) -class TestArrayInputWithLBTIM_0_1_1(TestField): +class TestArrayInputWithLBTIM_0_1_1: def test_t1_list_t2_scalar(self): # lbtim ia = 0, ib = 1, ic = 1 # with a single forecast reference time (t2) and a series @@ -436,7 +433,7 @@ def test_t1_list_t2_scalar(self): (time_coord, (0,)), (fref_time_coord, None), ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) + assert_coords_and_dims_lists_match(coords_and_dims, expected) def test_t1_and_t2_list(self): # lbtim ia = 0, ib = 1, ic = 1 @@ -485,7 +482,7 @@ def test_t1_and_t2_list(self): (time_coord, (0,)), (fref_time_coord, None), ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) + assert_coords_and_dims_lists_match(coords_and_dims, expected) def test_t1_and_t2_orthogonal_lists(self): # lbtim ia = 0, ib = 1, ic = 1 @@ -532,7 +529,7 @@ def test_t1_and_t2_orthogonal_lists(self): (time_coord, (0,)), (fref_time_coord, (1,)), ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) + assert_coords_and_dims_lists_match(coords_and_dims, expected) def test_t1_multi_dim_list_t2_scalar(self): # Another case of lbtim ia = 0, ib = 1, ic = 1 but @@ -586,7 +583,7 @@ def test_t1_multi_dim_list_t2_scalar(self): (time_coord, (0, 1)), (fref_time_coord, None), ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) + assert_coords_and_dims_lists_match(coords_and_dims, expected) def test_t1_and_t2_nparrays(self): # lbtim ia = 0, ib = 1, ic = 1 @@ -639,10 +636,10 @@ def test_t1_and_t2_nparrays(self): (time_coord, (0,)), (fref_time_coord, None), ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) + assert_coords_and_dims_lists_match(coords_and_dims, expected) -class TestArrayInputWithLBTIM_0_2_1(TestField): +class TestArrayInputWithLBTIM_0_2_1: def test_t1_list_t2_scalar(self): lbtim = _lbtim(ib=2, ic=1) lbcode = _lbcode(1) @@ -694,10 +691,10 @@ def test_t1_list_t2_scalar(self): (time_coord, (0,)), (fref_time_coord, None), ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) + assert_coords_and_dims_lists_match(coords_and_dims, expected) -class TestArrayInputWithLBTIM_0_3_1(TestField): +class TestArrayInputWithLBTIM_0_3_1: def test_t1_scalar_t2_list(self): lbtim = _lbtim(ib=3, ic=1) lbcode = _lbcode(1) @@ -754,8 +751,4 @@ def test_t1_scalar_t2_list(self): (time_coord, (0,)), (fref_time_coord, (0,)), ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) - - -if __name__ == "__main__": - tests.main() + assert_coords_and_dims_lists_match(coords_and_dims, expected) diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_vertical_coords.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_vertical_coords.py index 0e159b254e..7f6270e9f3 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_vertical_coords.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_vertical_coords.py @@ -7,17 +7,13 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np from iris.aux_factory import HybridHeightFactory, HybridPressureFactory from iris.coords import AuxCoord, DimCoord from iris.fileformats.pp import STASH, SplittableInt from iris.fileformats.pp_load_rules import Reference, _convert_vertical_coords -from iris.tests.unit.fileformats import TestField +from iris.tests.unit.fileformats.pp_load_rules import assert_coords_and_dims_lists_match def _lbcode(value=None, ix=None, iy=None): @@ -31,7 +27,7 @@ def _lbcode(value=None, ix=None, iy=None): return result -class TestLBVC001_Height(TestField): +class TestLBVC001_Height: def _check_height( self, blev, @@ -89,8 +85,8 @@ def _check_height( ] else: expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) + assert_coords_and_dims_lists_match(coords_and_dims, expect_result) + assert factories == [] def test_normal_height__present(self): self._check_height(blev=12.3, stash=STASH(1, 1, 1)) @@ -172,7 +168,7 @@ def test_implied_height_10m__vector(self): ) -class TestLBVC002_Depth(TestField): +class TestLBVC002_Depth: def _check_depth( self, lbcode, @@ -248,8 +244,8 @@ def _check_depth( ) else: expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) + assert_coords_and_dims_lists_match(coords_and_dims, expect_result) + assert factories == [] def test_unbounded(self): self._check_depth(_lbcode(1), lblev=23.0, expect_bounds=False) @@ -323,7 +319,7 @@ def test_cross_section__vector(self): ) -class TestLBVC006_SoilLevel(TestField): +class TestLBVC006_SoilLevel: def _check_soil_level(self, lbcode, lblev=12.3, expect_match=True, dim=None): lbvc = 6 stash = STASH(1, 1, 1) @@ -354,8 +350,8 @@ def _check_soil_level(self, lbcode, lblev=12.3, expect_match=True, dim=None): units="1", ) expect_result = [(coord, dim)] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) + assert_coords_and_dims_lists_match(coords_and_dims, expect_result) + assert factories == [] def test_normal(self): self._check_soil_level(_lbcode(0)) @@ -374,7 +370,7 @@ def test_cross_section__vector(self): ) -class TestLBVC006_SoilDepth(TestField): +class TestLBVC006_SoilDepth: def _check_soil_depth( self, lbcode, @@ -410,8 +406,8 @@ def _check_soil_depth( attributes={"positive": "down"}, ) expect_result = [(coord, dim)] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) + assert_coords_and_dims_lists_match(coords_and_dims, expect_result) + assert factories == [] def test_normal(self): self._check_soil_depth(_lbcode(0)) @@ -450,7 +446,7 @@ def test_cross_section__vector(self): ) -class TestLBVC008_Pressure(TestField): +class TestLBVC008_Pressure: def _check_pressure(self, lbcode, blev=250.3, expect_match=True, dim=None): lbvc = 8 stash = STASH(1, 1, 1) @@ -479,8 +475,8 @@ def _check_pressure(self, lbcode, blev=250.3, expect_match=True, dim=None): expect_result = [(DimCoord(blev, long_name="pressure", units="hPa"), dim)] else: expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) + assert_coords_and_dims_lists_match(coords_and_dims, expect_result) + assert factories == [] def test_normal(self): self._check_pressure(_lbcode(0)) @@ -504,7 +500,7 @@ def test_pressure_cross_section__vector(self): self._check_pressure(_lbcode(ix=10, iy=1), blev=blev, dim=1, expect_match=False) -class TestLBVC019_PotentialTemperature(TestField): +class TestLBVC019_PotentialTemperature: def _check_potm(self, lbcode, blev=130.6, expect_match=True, dim=None): lbvc = 19 stash = STASH(1, 1, 1) @@ -543,8 +539,8 @@ def _check_potm(self, lbcode, blev=130.6, expect_match=True, dim=None): ] else: expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) + assert_coords_and_dims_lists_match(coords_and_dims, expect_result) + assert factories == [] def test_normal(self): self._check_potm(_lbcode(0)) @@ -561,7 +557,7 @@ def test_cross_section__vector(self): self._check_potm(_lbcode(ix=10, iy=11), blev=blev, dim=1, expect_match=False) -class TestLBVC009_HybridPressure(TestField): +class TestLBVC009_HybridPressure: def _check( self, lblev=37.0, @@ -638,8 +634,8 @@ def _check( ], ) ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_coords_and_dims) - self.assertEqual(factories, expect_factories) + assert_coords_and_dims_lists_match(coords_and_dims, expect_coords_and_dims) + assert factories == expect_factories def test_normal(self): self._check() @@ -664,7 +660,7 @@ def test_normal__vector(self): ) -class TestLBVC065_HybridHeight(TestField): +class TestLBVC065_HybridHeight: def _check( self, lblev=37.0, @@ -740,8 +736,8 @@ def _check( ], ) ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_coords_and_dims) - self.assertEqual(factories, expect_factories) + assert_coords_and_dims_lists_match(coords_and_dims, expect_coords_and_dims) + assert factories == expect_factories def test_normal(self): self._check() @@ -767,7 +763,7 @@ def test_normal__vector(self): ) -class TestLBVCxxx_Unhandled(TestField): +class TestLBVCxxx_Unhandled: def test_unknown_lbvc(self): lbvc = 999 blev, lblev, bhlev, bhrlev, brsvd1, brsvd2, brlev = ( @@ -793,9 +789,5 @@ def test_unknown_lbvc(self): brsvd2=brsvd2, brlev=brlev, ) - self.assertEqual(coords_and_dims, []) - self.assertEqual(factories, []) - - -if __name__ == "__main__": - tests.main() + assert coords_and_dims == [] + assert factories == [] diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__dim_or_aux.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__dim_or_aux.py index 176d0a38a1..496b82c6c8 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__dim_or_aux.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__dim_or_aux.py @@ -4,16 +4,15 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for :func:`iris.fileformats.pp_load_rules._dim_or_aux`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip +import pytest from iris.coords import AuxCoord, DimCoord from iris.fileformats.pp_load_rules import _dim_or_aux -class Test(tests.IrisTest): - def setUp(self): +class Test: + @pytest.fixture(autouse=True) + def _setup(self): self.mono = list(range(5)) self.non_mono = [0, 1, 3, 2, 4] self.std_name = "depth" @@ -33,7 +32,7 @@ def test_dim_monotonic(self): units=self.units, attributes=self.attr, ) - self.assertEqual(result, expected) + assert result == expected def test_dim_non_monotonic(self): result = _dim_or_aux( @@ -50,8 +49,4 @@ def test_dim_non_monotonic(self): units=self.units, attributes=attr, ) - self.assertEqual(result, expected) - - -if __name__ == "__main__": - tests.main() + assert result == expected diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py index f2f19d9bb1..826156dade 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py @@ -7,15 +7,13 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import cf_units from cf_units import Unit from cftime import datetime as nc_datetime +import pytest from iris.fileformats.pp_load_rules import _epoch_date_hours as epoch_hours_call +from iris.tests._shared_utils import assert_array_all_close # # Run tests for each of the possible calendars from PPfield.calendar(). @@ -24,30 +22,31 @@ # -class TestEpochHours__standard(tests.IrisTest): - def setUp(self): +class TestEpochHours__standard: + @pytest.fixture(autouse=True) + def _setup(self): self.calendar = cf_units.CALENDAR_STANDARD self.hrs_unit = Unit("hours since epoch", calendar=self.calendar) def test_1970_1_1(self): test_date = nc_datetime(1970, 1, 1, calendar=self.calendar) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, 0.0) + assert result == 0.0 def test_ymd_1_1_1(self): test_date = nc_datetime(1, 1, 1, calendar=self.calendar) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17259936.0) + assert result == -17259936.0 def test_year_0(self): test_date = nc_datetime(0, 1, 1, calendar=self.calendar, has_year_zero=True) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17268720.0) + assert result == -17268720.0 def test_ymd_0_0_0(self): test_date = nc_datetime(0, 0, 0, calendar=None, has_year_zero=True) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17269488.0) + assert result == -17269488.0 def test_ymd_0_preserves_timeofday(self): hrs, mins, secs, usecs = (7, 13, 24, 335772) @@ -69,64 +68,66 @@ def test_ymd_0_preserves_timeofday(self): # NOTE: the calculation is only accurate to approx +/- 0.5 seconds # in such a large number of hours -- even 0.1 seconds is too fine. absolute_tolerance = 0.5 / 3600 - self.assertArrayAllClose( + assert_array_all_close( result, -17269488.0 + hours_in_day, rtol=0, atol=absolute_tolerance ) -class TestEpochHours__360day(tests.IrisTest): - def setUp(self): +class TestEpochHours__360day: + @pytest.fixture(autouse=True) + def _setup(self): self.calendar = cf_units.CALENDAR_360_DAY self.hrs_unit = Unit("hours since epoch", calendar=self.calendar) def test_1970_1_1(self): test_date = nc_datetime(1970, 1, 1, calendar=self.calendar) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, 0.0) + assert result == 0.0 def test_ymd_1_1_1(self): test_date = nc_datetime(1, 1, 1, calendar=self.calendar) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17012160.0) + assert result == -17012160.0 def test_year_0(self): test_date = nc_datetime(0, 1, 1, calendar=self.calendar, has_year_zero=True) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17020800.0) + assert result == -17020800.0 def test_ymd_0_0_0(self): test_date = nc_datetime(0, 0, 0, calendar=None, has_year_zero=True) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17021544.0) + assert result == -17021544.0 -class TestEpochHours__365day(tests.IrisTest): - def setUp(self): +class TestEpochHours__365day: + @pytest.fixture(autouse=True) + def _setup(self): self.calendar = cf_units.CALENDAR_365_DAY self.hrs_unit = Unit("hours since epoch", calendar=self.calendar) def test_1970_1_1(self): test_date = nc_datetime(1970, 1, 1, calendar=self.calendar) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, 0.0) + assert result == 0.0 def test_ymd_1_1_1(self): test_date = nc_datetime(1, 1, 1, calendar=self.calendar) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17248440.0) + assert result == -17248440.0 def test_year_0(self): test_date = nc_datetime(0, 1, 1, calendar=self.calendar, has_year_zero=True) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17257200.0) + assert result == -17257200.0 def test_ymd_0_0_0(self): test_date = nc_datetime(0, 0, 0, calendar=None, has_year_zero=True) result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17257968.0) + assert result == -17257968.0 -class TestEpochHours__invalid_calendar(tests.IrisTest): +class TestEpochHours__invalid_calendar: def test_bad_calendar(self): self.calendar = cf_units.CALENDAR_ALL_LEAP # Setup a unit with an unrecognised calendar @@ -134,9 +135,5 @@ def test_bad_calendar(self): # Test against a date with year=0, which requires calendar correction. test_date = nc_datetime(0, 1, 1, calendar=self.calendar, has_year_zero=True) # Check that this causes an error. - with self.assertRaisesRegex(ValueError, "unrecognised calendar"): + with pytest.raises(ValueError, match="unrecognised calendar"): epoch_hours_call(hrs_unit, test_date) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__model_level_number.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__model_level_number.py index 65c6bc8442..c721e82754 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__model_level_number.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__model_level_number.py @@ -4,23 +4,15 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for :func:`iris.fileformats.pp_load_rules._model_level_number`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from iris.fileformats.pp_load_rules import _model_level_number -class Test_9999(tests.IrisTest): +class Test_9999: def test(self): - self.assertEqual(_model_level_number(9999), 0) + assert _model_level_number(9999) == 0 -class Test_lblev(tests.IrisTest): +class Test_lblev: def test(self): for val in range(9999): - self.assertEqual(_model_level_number(val), val) - - -if __name__ == "__main__": - tests.main() + assert _model_level_number(val) == val diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__reduced_points_and_bounds.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__reduced_points_and_bounds.py index 6dfc6189bb..72e59963e7 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__reduced_points_and_bounds.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__reduced_points_and_bounds.py @@ -7,64 +7,61 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np from iris.fileformats.pp_load_rules import _reduce_points_and_bounds +from iris.tests._shared_utils import assert_array_equal -class Test(tests.IrisTest): +class Test: def test_scalar(self): array = np.array(1) dims, result, bounds = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, array) - self.assertEqual(dims, None) - self.assertIsNone(bounds) + assert_array_equal(result, array) + assert dims is None + assert bounds is None def test_1d_nochange(self): array = np.array([1, 2, 3]) dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, array) - self.assertEqual(dims, (0,)) + assert_array_equal(result, array) + assert dims == (0,) def test_1d_collapse(self): array = np.array([1, 1, 1]) dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, np.array(1)) - self.assertEqual(dims, None) + assert_array_equal(result, np.array(1)) + assert dims is None def test_2d_nochange(self): array = np.array([[1, 2, 3], [4, 5, 6]]) dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, array) - self.assertEqual(dims, (0, 1)) + assert_array_equal(result, array) + assert dims == (0, 1) def test_2d_collapse_dim0(self): array = np.array([[1, 2, 3], [1, 2, 3]]) dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, np.array([1, 2, 3])) - self.assertEqual(dims, (1,)) + assert_array_equal(result, np.array([1, 2, 3])) + assert dims == (1,) def test_2d_collapse_dim1(self): array = np.array([[1, 1, 1], [2, 2, 2]]) dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, np.array([1, 2])) - self.assertEqual(dims, (0,)) + assert_array_equal(result, np.array([1, 2])) + assert dims == (0,) def test_2d_collapse_both(self): array = np.array([[3, 3, 3], [3, 3, 3]]) dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, np.array(3)) - self.assertEqual(dims, None) + assert_array_equal(result, np.array(3)) + assert dims is None def test_3d(self): array = np.array([[[3, 3, 3], [4, 4, 4]], [[3, 3, 3], [4, 4, 4]]]) dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, np.array([3, 4])) - self.assertEqual(dims, (1,)) + assert_array_equal(result, np.array([3, 4])) + assert dims == (1,) def test_bounds_collapse(self): points = np.array([1, 1, 1]) @@ -72,9 +69,9 @@ def test_bounds_collapse(self): result_dims, result_pts, result_bds = _reduce_points_and_bounds( points, (bounds[..., 0], bounds[..., 1]) ) - self.assertArrayEqual(result_pts, np.array(1)) - self.assertArrayEqual(result_bds, np.array([0, 2])) - self.assertEqual(result_dims, None) + assert_array_equal(result_pts, np.array(1)) + assert_array_equal(result_bds, np.array([0, 2])) + assert result_dims is None def test_bounds_no_collapse(self): points = np.array([1, 2, 3]) @@ -82,10 +79,6 @@ def test_bounds_no_collapse(self): result_dims, result_pts, result_bds = _reduce_points_and_bounds( points, (bounds[..., 0], bounds[..., 1]) ) - self.assertArrayEqual(result_pts, points) - self.assertArrayEqual(result_bds, bounds) - self.assertEqual(result_dims, (0,)) - - -if __name__ == "__main__": - tests.main() + assert_array_equal(result_pts, points) + assert_array_equal(result_bds, bounds) + assert result_dims == (0,) diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__reshape_vector_args.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__reshape_vector_args.py index 69ff56391e..0bb8d55aae 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__reshape_vector_args.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__reshape_vector_args.py @@ -7,26 +7,24 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest from iris.fileformats.pp_load_rules import _reshape_vector_args +from iris.tests._shared_utils import assert_array_equal -class TestEmpty(tests.IrisTest): +class TestEmpty: def test(self): result = _reshape_vector_args([]) - self.assertEqual(result, []) + assert result == [] -class TestSingleArg(tests.IrisTest): +class TestSingleArg: def _check(self, result, expected): - self.assertEqual(len(result), len(expected)) + assert len(result) == len(expected) for result_arr, expected_arr in zip(result, expected): - self.assertArrayEqual(result_arr, expected_arr) + assert_array_equal(result_arr, expected_arr) def test_nochange(self): points = np.array([[1, 2, 3], [4, 5, 6]]) @@ -36,7 +34,7 @@ def test_nochange(self): def test_bad_dimensions(self): points = np.array([[1, 2, 3], [4, 5, 6]]) - with self.assertRaisesRegex(ValueError, "Length"): + with pytest.raises(ValueError, match="Length"): _reshape_vector_args([(points, (0, 1, 2))]) def test_scalar(self): @@ -64,11 +62,11 @@ def test_extend(self): self._check(result, expected) -class TestMultipleArgs(tests.IrisTest): +class TestMultipleArgs: def _check(self, result, expected): - self.assertEqual(len(result), len(expected)) + assert len(result) == len(expected) for result_arr, expected_arr in zip(result, expected): - self.assertArrayEqual(result_arr, expected_arr) + assert_array_equal(result_arr, expected_arr) def test_nochange(self): a1 = np.array([[1, 2, 3], [4, 5, 6]]) @@ -131,7 +129,3 @@ def test_triple(self): a3.reshape(1, 1, 1), ] self._check(result, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test_convert.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test_convert.py index 476ecbc8ae..d9e1e74e00 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test_convert.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test_convert.py @@ -4,10 +4,6 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for :func:`iris.fileformats.pp_load_rules.convert`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from types import MethodType from unittest import mock @@ -17,10 +13,41 @@ from iris.fileformats.pp import STASH, PPField3, SplittableInt from iris.fileformats.pp_load_rules import convert -import iris.tests.unit.fileformats +from iris.tests._shared_utils import assert_array_equal from iris.util import guess_coord_axis +def assert_test_for_coord( + field, convert, coord_predicate, expected_points, expected_bounds +): + ( + factories, + references, + standard_name, + long_name, + units, + attributes, + cell_methods, + dim_coords_and_dims, + aux_coords_and_dims, + ) = convert(field) + + # Check for one and only one matching coordinate. + coords_and_dims = dim_coords_and_dims + aux_coords_and_dims + matching_coords = [coord for coord, _ in coords_and_dims if coord_predicate(coord)] + assert len(matching_coords) == 1, str(matching_coords) + coord = matching_coords[0] + + # Check points and bounds. + if expected_points is not None: + assert_array_equal(coord.points, expected_points) + + if expected_bounds is None: + assert coord.bounds is None + else: + assert_array_equal(coord.bounds, expected_bounds) + + def _mock_field(**kwargs): # Generate a mock field, but ensure T1 and T2 viable for rules. field = mock.MagicMock( @@ -31,7 +58,7 @@ def _mock_field(**kwargs): return field -class TestLBCODE(iris.tests.unit.fileformats.TestField): +class TestLBCODE: @staticmethod def _is_cross_section_height_coord(coord): return ( @@ -45,7 +72,7 @@ def test_cross_section_height_bdy_zero(self): points = np.array([10, 20, 30, 40]) bounds = np.array([[0, 15], [15, 25], [25, 35], [35, 45]]) field = _mock_field(lbcode=lbcode, bdy=0, y=points, y_bounds=bounds) - self._test_for_coord( + assert_test_for_coord( field, convert, TestLBCODE._is_cross_section_height_coord, @@ -61,7 +88,7 @@ def test_cross_section_height_bdy_bmdi(self): field = _mock_field( lbcode=lbcode, bdy=bmdi, bmdi=bmdi, y=points, y_bounds=bounds ) - self._test_for_coord( + assert_test_for_coord( field, convert, TestLBCODE._is_cross_section_height_coord, @@ -70,7 +97,7 @@ def test_cross_section_height_bdy_bmdi(self): ) -class TestLBVC(iris.tests.unit.fileformats.TestField): +class TestLBVC: @staticmethod def _is_potm_level_coord(coord): return ( @@ -113,7 +140,7 @@ def _is_soil_depth_coord(coord): def test_soil_levels(self): level = 1234 field = _mock_field(lbvc=6, lblev=level, brsvd=[0, 0], brlev=0) - self._test_for_coord( + assert_test_for_coord( field, convert, self._is_soil_model_level_number_coord, @@ -124,7 +151,7 @@ def test_soil_levels(self): def test_soil_depth(self): lower, point, upper = 1.2, 3.4, 5.6 field = _mock_field(lbvc=6, blev=point, brsvd=[lower, 0], brlev=upper) - self._test_for_coord( + assert_test_for_coord( field, convert, self._is_soil_depth_coord, @@ -143,7 +170,7 @@ def test_hybrid_pressure_model_level_number(self): bhrlev=45, brsvd=[17, 40], ) - self._test_for_coord( + assert_test_for_coord( field, convert, TestLBVC._is_model_level_number_coord, @@ -164,7 +191,7 @@ def test_hybrid_pressure_delta(self): bhrlev=delta_lower_bound, brsvd=[17, delta_upper_bound], ) - self._test_for_coord( + assert_test_for_coord( field, convert, TestLBVC._is_level_pressure_coord, @@ -185,7 +212,7 @@ def test_hybrid_pressure_sigma(self): bhrlev=11, brsvd=[sigma_upper_bound, 13], ) - self._test_for_coord( + assert_test_for_coord( field, convert, TestLBVC._is_sigma_coord, @@ -196,7 +223,7 @@ def test_hybrid_pressure_sigma(self): def test_potential_temperature_levels(self): potm_value = 27.32 field = _mock_field(lbvc=19, blev=potm_value) - self._test_for_coord( + assert_test_for_coord( field, convert, TestLBVC._is_potm_level_coord, @@ -205,7 +232,7 @@ def test_potential_temperature_levels(self): ) -class TestLBTIM(iris.tests.unit.fileformats.TestField): +class TestLBTIM: def test_365_calendar(self): f = mock.MagicMock( lbtim=SplittableInt(4, {"ia": 2, "ib": 1, "ic": 0}), @@ -238,10 +265,10 @@ def is_t_coord(coord_and_dims): return coord.standard_name == "time" coords_and_dims = list(filter(is_t_coord, aux_coords_and_dims)) - self.assertEqual(len(coords_and_dims), 1) + assert len(coords_and_dims) == 1 coord, dims = coords_and_dims[0] - self.assertEqual(guess_coord_axis(coord), "T") - self.assertEqual(coord.units.calendar, "365_day") + assert guess_coord_axis(coord) == "T" + assert coord.units.calendar == "365_day" def base_field(self): field = PPField3(header=mock.MagicMock()) @@ -278,7 +305,7 @@ def test_time_mean_ib2(self): field.lbyrd, field.lbmond, field.lbdatd = 1970, 1, 2 field.lbhrd, field.lbmind, field.lbsecd = 15, 0, 0 - self._test_for_coord( + assert_test_for_coord( field, convert, self.is_forecast_period, @@ -286,7 +313,7 @@ def test_time_mean_ib2(self): expected_bounds=[[6, 9]], ) - self._test_for_coord( + assert_test_for_coord( field, convert, self.is_time, @@ -306,7 +333,7 @@ def test_time_mean_ib3(self): field.lbyrd, field.lbmond, field.lbdatd = 1971, 1, 2 field.lbhrd, field.lbmind, field.lbsecd = 15, 0, 0 - self._test_for_coord( + assert_test_for_coord( field, convert, self.is_forecast_period, @@ -314,7 +341,7 @@ def test_time_mean_ib3(self): expected_bounds=[[36 - 30, lbft]], ) - self._test_for_coord( + assert_test_for_coord( field, convert, self.is_time, @@ -323,7 +350,7 @@ def test_time_mean_ib3(self): ) -class TestLBRSVD(iris.tests.unit.fileformats.TestField): +class TestLBRSVD: @staticmethod def _is_realization(coord): return coord.standard_name == "realization" and coord.units == "1" @@ -334,7 +361,7 @@ def test_realization(self): points = np.array([71]) bounds = None field = _mock_field(lbrsvd=lbrsvd) - self._test_for_coord( + assert_test_for_coord( field, convert, TestLBRSVD._is_realization, @@ -343,7 +370,7 @@ def test_realization(self): ) -class TestLBSRCE(iris.tests.IrisTest): +class TestLBSRCE: def check_um_source_attrs(self, lbsrce, source_str=None, um_version_str=None): field = _mock_field(lbsrce=lbsrce) ( @@ -358,13 +385,13 @@ def check_um_source_attrs(self, lbsrce, source_str=None, um_version_str=None): aux_coords_and_dims, ) = convert(field) if source_str is not None: - self.assertEqual(attributes["source"], source_str) + assert attributes["source"] == source_str else: - self.assertNotIn("source", attributes) + assert "source" not in attributes if um_version_str is not None: - self.assertEqual(attributes["um_version"], um_version_str) + assert attributes["um_version"] == um_version_str else: - self.assertNotIn("um_version", attributes) + assert "um_version" not in attributes def test_none(self): self.check_um_source_attrs(lbsrce=8123, source_str=None, um_version_str=None) @@ -384,7 +411,7 @@ def test_um_version(self): ) -class Test_STASH_CF(iris.tests.unit.fileformats.TestField): +class Test_STASH_CF: def test_stash_cf_air_temp(self): lbuser = [1, 0, 0, 16203, 0, 0, 1] lbfc = 16 @@ -401,8 +428,8 @@ def test_stash_cf_air_temp(self): dim_coords_and_dims, aux_coords_and_dims, ) = convert(field) - self.assertEqual(standard_name, "air_temperature") - self.assertEqual(units, "K") + assert standard_name == "air_temperature" + assert units == "K" def test_no_std_name(self): lbuser = [1, 0, 0, 0, 0, 0, 0] @@ -420,11 +447,11 @@ def test_no_std_name(self): dim_coords_and_dims, aux_coords_and_dims, ) = convert(field) - self.assertIsNone(standard_name) - self.assertIsNone(units) + assert standard_name is None + assert units is None -class Test_LBFC_CF(iris.tests.unit.fileformats.TestField): +class Test_LBFC_CF: def test_fc_cf_air_temp(self): lbuser = [1, 0, 0, 0, 0, 0, 0] lbfc = 16 @@ -441,9 +468,5 @@ def test_fc_cf_air_temp(self): dim_coords_and_dims, aux_coords_and_dims, ) = convert(field) - self.assertEqual(standard_name, "air_temperature") - self.assertEqual(units, "K") - - -if __name__ == "__main__": - tests.main() + assert standard_name == "air_temperature" + assert units == "K" diff --git a/lib/iris/tests/unit/fileformats/rules/test_Loader.py b/lib/iris/tests/unit/fileformats/rules/test_Loader.py index fafa018d3a..08e0764a45 100644 --- a/lib/iris/tests/unit/fileformats/rules/test_Loader.py +++ b/lib/iris/tests/unit/fileformats/rules/test_Loader.py @@ -4,40 +4,29 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for :class:`iris.fileformats.rules.Loader`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - from iris.fileformats.rules import Loader +from iris.tests._shared_utils import assert_no_warnings_regexp -class Test___init__(tests.IrisTest): - def test_normal(self): - with mock.patch("warnings.warn") as warn: +class Test___init__: + def test_normal(self, mocker): + with assert_no_warnings_regexp(): loader = Loader( - mock.sentinel.GEN_FUNC, - mock.sentinel.GEN_FUNC_KWARGS, - mock.sentinel.CONVERTER, + mocker.sentinel.GEN_FUNC, + mocker.sentinel.GEN_FUNC_KWARGS, + mocker.sentinel.CONVERTER, ) - self.assertEqual(warn.call_count, 0) - self.assertIs(loader.field_generator, mock.sentinel.GEN_FUNC) - self.assertIs(loader.field_generator_kwargs, mock.sentinel.GEN_FUNC_KWARGS) - self.assertIs(loader.converter, mock.sentinel.CONVERTER) + assert loader.field_generator is mocker.sentinel.GEN_FUNC + assert loader.field_generator_kwargs is mocker.sentinel.GEN_FUNC_KWARGS + assert loader.converter is mocker.sentinel.CONVERTER - def test_normal_with_explicit_none(self): - with mock.patch("warnings.warn") as warn: + def test_normal_with_explicit_none(self, mocker): + with assert_no_warnings_regexp(): loader = Loader( - mock.sentinel.GEN_FUNC, - mock.sentinel.GEN_FUNC_KWARGS, - mock.sentinel.CONVERTER, + mocker.sentinel.GEN_FUNC, + mocker.sentinel.GEN_FUNC_KWARGS, + mocker.sentinel.CONVERTER, ) - self.assertEqual(warn.call_count, 0) - self.assertIs(loader.field_generator, mock.sentinel.GEN_FUNC) - self.assertIs(loader.field_generator_kwargs, mock.sentinel.GEN_FUNC_KWARGS) - self.assertIs(loader.converter, mock.sentinel.CONVERTER) - - -if __name__ == "__main__": - tests.main() + assert loader.field_generator is mocker.sentinel.GEN_FUNC + assert loader.field_generator_kwargs is mocker.sentinel.GEN_FUNC_KWARGS + assert loader.converter is mocker.sentinel.CONVERTER diff --git a/lib/iris/tests/unit/fileformats/rules/test__make_cube.py b/lib/iris/tests/unit/fileformats/rules/test__make_cube.py index 91862658e5..c14a5df705 100644 --- a/lib/iris/tests/unit/fileformats/rules/test__make_cube.py +++ b/lib/iris/tests/unit/fileformats/rules/test__make_cube.py @@ -4,19 +4,15 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for :func:`iris.fileformats.rules._make_cube`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from unittest import mock -import warnings import numpy as np +import pytest from iris.fileformats.rules import ConversionMetadata, _make_cube -class Test(tests.IrisTest): +class Test: def test_invalid_units(self): # Mock converter() function that returns an invalid # units string amongst the collection of other elements. @@ -46,20 +42,12 @@ def test_invalid_units(self): field = mock.Mock( core_data=lambda: data, bmdi=9999.0, realised_dtype=data.dtype ) - with warnings.catch_warnings(record=True) as warn: - warnings.simplefilter("always") + + exp_emsg = "invalid units {!r}".format(units) + with pytest.warns(match=exp_emsg): cube, factories, references = _make_cube(field, converter) # Check attributes dictionary is correctly populated. expected_attributes = attributes.copy() expected_attributes["invalid_units"] = units - self.assertEqual(cube.attributes, expected_attributes) - - # Check warning was raised. - self.assertEqual(len(warn), 1) - exp_emsg = "invalid units {!r}".format(units) - self.assertRegex(str(warn[0]), exp_emsg) - - -if __name__ == "__main__": - tests.main() + assert cube.attributes == expected_attributes diff --git a/lib/iris/tests/unit/fileformats/test_rules.py b/lib/iris/tests/unit/fileformats/rules/test_rules.py similarity index 81% rename from lib/iris/tests/unit/fileformats/test_rules.py rename to lib/iris/tests/unit/fileformats/rules/test_rules.py index d39b6a997d..df3c769a70 100644 --- a/lib/iris/tests/unit/fileformats/test_rules.py +++ b/lib/iris/tests/unit/fileformats/rules/test_rules.py @@ -4,14 +4,11 @@ # See LICENSE in the root of the repository for full licensing details. """Test iris.fileformats.rules.py - metadata translation rules.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import types from unittest import mock import numpy as np +import pytest from iris.aux_factory import HybridHeightFactory from iris.coords import CellMethod @@ -26,30 +23,31 @@ load_cubes, scalar_cell_method, ) +from iris.tests._shared_utils import skip_data import iris.tests.stock as stock -class TestConcreteReferenceTarget(tests.IrisTest): +class TestConcreteReferenceTarget: def test_attributes(self): - with self.assertRaises(TypeError): + with pytest.raises(TypeError): target = ConcreteReferenceTarget() target = ConcreteReferenceTarget("foo") - self.assertEqual(target.name, "foo") - self.assertIsNone(target.transform) + assert target.name == "foo" + assert target.transform is None def transform(_): return _ target = ConcreteReferenceTarget("foo", transform) - self.assertEqual(target.name, "foo") - self.assertIs(target.transform, transform) + assert target.name == "foo" + assert target.transform is transform def test_single_cube_no_transform(self): target = ConcreteReferenceTarget("foo") src = stock.simple_2d() target.add_cube(src) - self.assertIs(target.as_cube(), src) + assert target.as_cube() is src def test_single_cube_with_transform(self): def transform(cube): @@ -59,22 +57,22 @@ def transform(cube): src = stock.simple_2d() target.add_cube(src) dest = target.as_cube() - self.assertEqual(dest.long_name, "wibble") - self.assertNotEqual(dest, src) + assert dest.long_name == "wibble" + assert dest != src dest.long_name = src.long_name - self.assertEqual(dest, src) + assert dest == src - @tests.skip_data + @skip_data def test_multiple_cubes_no_transform(self): target = ConcreteReferenceTarget("foo") src = stock.realistic_4d() for i in range(src.shape[0]): target.add_cube(src[i]) dest = target.as_cube() - self.assertIsNot(dest, src) - self.assertEqual(dest, src) + assert dest is not src + assert dest == src - @tests.skip_data + @skip_data def test_multiple_cubes_with_transform(self): def transform(cube): return {"long_name": "wibble"} @@ -84,13 +82,13 @@ def transform(cube): for i in range(src.shape[0]): target.add_cube(src[i]) dest = target.as_cube() - self.assertEqual(dest.long_name, "wibble") - self.assertNotEqual(dest, src) + assert dest.long_name == "wibble" + assert dest != src dest.long_name = src.long_name - self.assertEqual(dest, src) + assert dest == src -class TestLoadCubes(tests.IrisTest): +class TestLoadCubes: def test_simple_factory(self): # Test the creation process for a factory definition which only # uses simple dict arguments. @@ -124,7 +122,7 @@ def converter(field): cubes = load_cubes(["fake_filename"], None, fake_loader) # Check the result is a generator with a single entry. - self.assertIsInstance(cubes, types.GeneratorType) + assert isinstance(cubes, types.GeneratorType) try: # Suppress the normal Cube.coord() and Cube.add_aux_factory() # methods. @@ -139,15 +137,15 @@ def converter(field): finally: Cube.coord = coord_method Cube.add_aux_factory = add_aux_factory_method - self.assertEqual(len(cubes), 1) + assert len(cubes) == 1 # Check the "cube" has an "aux_factory" added, which itself # must have been created with the correct arguments. - self.assertTrue(hasattr(cubes[0], "fake_aux_factory")) - self.assertIs(cubes[0].fake_aux_factory, aux_factory) - self.assertTrue(hasattr(aux_factory, "fake_args")) - self.assertEqual(aux_factory.fake_args, ({"name": "foo"},)) + assert hasattr(cubes[0], "fake_aux_factory") + assert cubes[0].fake_aux_factory is aux_factory + assert hasattr(aux_factory, "fake_args") + assert aux_factory.fake_args == ({"name": "foo"},) - @tests.skip_data + @skip_data def test_cross_reference(self): # Test the creation process for a factory definition which uses # a cross-reference. @@ -219,42 +217,38 @@ def converter(field): cubes = load_cubes(["fake_filename"], None, fake_loader) # Check the result is a generator containing two Cubes. - self.assertIsInstance(cubes, types.GeneratorType) + assert isinstance(cubes, types.GeneratorType) cubes = list(cubes) - self.assertEqual(len(cubes), 2) + assert len(cubes) == 2 # Check the "cube" has an "aux_factory" added, which itself # must have been created with the correct arguments. - self.assertEqual(len(cubes[1].aux_factories), 1) - self.assertEqual(len(cubes[1].coords("surface_altitude")), 1) + assert len(cubes[1].aux_factories) == 1 + assert len(cubes[1].coords("surface_altitude")) == 1 -class Test_scalar_cell_method(tests.IrisTest): +class Test_scalar_cell_method: """Tests for iris.fileformats.rules.scalar_cell_method() function.""" - def setUp(self): + def setup_method(self): self.cube = stock.simple_2d() self.cm = CellMethod("mean", "foo", "1 hour") self.cube.cell_methods = (self.cm,) def test_cell_method_found(self): actual = scalar_cell_method(self.cube, "mean", "foo") - self.assertEqual(actual, self.cm) + assert actual == self.cm def test_method_different(self): actual = scalar_cell_method(self.cube, "average", "foo") - self.assertIsNone(actual) + assert actual is None def test_coord_name_different(self): actual = scalar_cell_method(self.cube, "average", "bar") - self.assertIsNone(actual) + assert actual is None def test_double_coord_fails(self): self.cube.cell_methods = ( CellMethod("mean", ("foo", "bar"), ("1 hour", "1 hour")), ) actual = scalar_cell_method(self.cube, "mean", "foo") - self.assertIsNone(actual) - - -if __name__ == "__main__": - tests.main() + assert actual is None diff --git a/lib/iris/tests/unit/fileformats/structured_array_identification/test_ArrayStructure.py b/lib/iris/tests/unit/fileformats/structured_array_identification/test_ArrayStructure.py index 6012f1fce8..bf23b39a5c 100644 --- a/lib/iris/tests/unit/fileformats/structured_array_identification/test_ArrayStructure.py +++ b/lib/iris/tests/unit/fileformats/structured_array_identification/test_ArrayStructure.py @@ -7,16 +7,14 @@ """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest from iris.fileformats._structured_array_identification import ( ArrayStructure, _UnstructuredArrayException, ) +from iris.tests._shared_utils import assert_array_equal def construct_nd(sub_array, sub_dim, shape): @@ -28,136 +26,136 @@ def construct_nd(sub_array, sub_dim, shape): return sub_array.reshape(sub_shape) * np.ones(shape) -class TestArrayStructure_from_array(tests.IrisTest): +class TestArrayStructure_from_array: def struct_from_arr(self, nd_array): return ArrayStructure.from_array(nd_array.flatten()) def test_1d_len_0(self): a = np.arange(0) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, a)) + assert self.struct_from_arr(a) == ArrayStructure(1, a) def test_1d_len_1(self): a = np.arange(1) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, a)) + assert self.struct_from_arr(a) == ArrayStructure(1, a) def test_1d(self): a = np.array([-1, 3, 1, 2]) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, a)) + assert self.struct_from_arr(a) == ArrayStructure(1, a) def test_1d_ones(self): a = np.ones(10) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, [1])) + assert self.struct_from_arr(a) == ArrayStructure(1, [1]) def test_1d_range(self): a = np.arange(6) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, list(range(6)))) + assert self.struct_from_arr(a) == ArrayStructure(1, list(range(6))) def test_3d_ones(self): a = np.ones([10, 2, 1]) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, [1])) + assert self.struct_from_arr(a) == ArrayStructure(1, [1]) def test_1d_over_2d_first_dim_manual(self): sub = np.array([10, 10, 20, 20]) - self.assertEqual(self.struct_from_arr(sub), ArrayStructure(2, [10, 20])) + assert self.struct_from_arr(sub) == ArrayStructure(2, [10, 20]) def test_3d_first_dimension(self): flattened = np.array([1, 1, 1, 2, 2, 2]) - self.assertEqual( - ArrayStructure.from_array(flattened), ArrayStructure(3, [1, 2]) - ) + assert ArrayStructure.from_array(flattened) == ArrayStructure(3, [1, 2]) def test_1d_over_2d_first_dim(self): sub = np.array([-1, 3, 1, 2]) a = construct_nd(sub, 0, (4, 2)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(2, sub)) + assert self.struct_from_arr(a) == ArrayStructure(2, sub) def test_1d_over_2d_second_dim(self): sub = np.array([-1, 3, 1, 2]) a = construct_nd(sub, 1, (2, 4)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, sub)) + assert self.struct_from_arr(a) == ArrayStructure(1, sub) def test_1d_over_3d_first_dim(self): sub = np.array([-1, 3, 1, 2]) a = construct_nd(sub, 0, (4, 2, 3)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(6, sub)) + assert self.struct_from_arr(a) == ArrayStructure(6, sub) def test_1d_over_3d_second_dim(self): sub = np.array([-1, 3, 1, 2]) a = construct_nd(sub, 1, (2, 4, 3)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(3, sub)) + assert self.struct_from_arr(a) == ArrayStructure(3, sub) def test_1d_over_3d_third_dim(self): sub = np.array([-1, 3, 1, 2]) a = construct_nd(sub, 2, (3, 2, 4)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, sub)) + assert self.struct_from_arr(a) == ArrayStructure(1, sub) def test_irregular_3d(self): sub = np.array([-1, 3, 1, 2]) a = construct_nd(sub, 2, (3, 2, 4)) a[0, 0, 0] = 5 - self.assertEqual(self.struct_from_arr(a), None) + assert self.struct_from_arr(a) is None def test_repeated_3d(self): sub = np.array([-1, 3, 1, 2]) a = construct_nd(sub, 2, (3, 2, 4)) a[:, 0, 0] = 1 - self.assertEqual(self.struct_from_arr(a), None) + assert self.struct_from_arr(a) is None def test_rolled_3d(self): # Shift the 3D array on by one, making the array 1d. sub = np.arange(4) a = construct_nd(sub, 0, (4, 2, 3)) a = np.roll(a.flatten(), 1) - self.assertEqual(self.struct_from_arr(a), None) + assert self.struct_from_arr(a) is None def test_len_1_3d(self): # Setup a case which triggers an IndexError when identifying # the stride, but the result should still be correct. sub = np.arange(2) a = construct_nd(sub, 1, (1, 1, 1)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, sub)) + assert self.struct_from_arr(a) == ArrayStructure(1, sub) def test_not_an_array(self): # Support lists as an argument. - self.assertEqual( - ArrayStructure.from_array([1, 2, 3]), ArrayStructure(1, [1, 2, 3]) - ) + assert ArrayStructure.from_array([1, 2, 3]) == ArrayStructure(1, [1, 2, 3]) def test_multi_dim_array(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ArrayStructure.from_array(np.arange(12).reshape(3, 4)) -class nd_array_and_dims_cases: +class TestNdarrayAndDimsCases: """Defines the test functionality for nd_array_and_dims. This class isn't actually the test case - see the C order and F order subclasses for those. """ + @pytest.fixture(params=["c", "f"], ids=["c_order", "f_order"], autouse=True) + def _order(self, request): + self.order = request.param + def test_scalar_len1_first_dim(self): struct = ArrayStructure(1, [1]) orig = np.array([1, 1, 1]) array, dims = struct.nd_array_and_dims(orig, (1, 3), order=self.order) - self.assertArrayEqual(array, [1]) - self.assertEqual(dims, ()) + assert_array_equal(array, [1]) + assert dims == () def test_scalar_non_len1_first_dim(self): struct = ArrayStructure(1, [1]) orig = np.array([1, 1, 1]) array, dims = struct.nd_array_and_dims(orig, (3, 1), order=self.order) - self.assertArrayEqual(array, [1]) - self.assertEqual(dims, ()) + assert_array_equal(array, [1]) + assert dims == () def test_single_vector(self): orig = construct_nd(np.array([1, 2]), 0, (2, 1, 3)) flattened = orig.flatten(order=self.order) struct = ArrayStructure.from_array(flattened) array, dims = struct.nd_array_and_dims(flattened, (2, 1, 3), order=self.order) - self.assertArrayEqual(array, [1, 2]) - self.assertEqual(dims, (0,)) + assert_array_equal(array, [1, 2]) + assert dims == (0,) def test_single_vector_3rd_dim(self): orig = construct_nd(np.array([1, 2, 3]), 2, (4, 1, 3)) @@ -165,8 +163,8 @@ def test_single_vector_3rd_dim(self): struct = ArrayStructure.from_array(flattened) array, dims = struct.nd_array_and_dims(flattened, (4, 1, 3), order=self.order) - self.assertArrayEqual(array, [1, 2, 3]) - self.assertEqual(dims, (2,)) + assert_array_equal(array, [1, 2, 3]) + assert dims == (2,) def test_orig_array_and_target_shape_inconsistent(self): # An array structure which has a length which is a product @@ -175,7 +173,7 @@ def test_orig_array_and_target_shape_inconsistent(self): orig = np.array([1, 1, 2, 2, 3, 3]) msg = "Original array and target shape do not match up." - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): struct.nd_array_and_dims(orig, (2, 3, 2), order=self.order) def test_array_bigger_than_expected(self): @@ -184,7 +182,7 @@ def test_array_bigger_than_expected(self): struct = ArrayStructure(2, [1, 2, 3, 4, 5, 6]) orig = np.array([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6]) - with self.assertRaises(_UnstructuredArrayException): + with pytest.raises(_UnstructuredArrayException): struct.nd_array_and_dims(orig, (2, 3, 2), order=self.order) def test_single_vector_extra_dimension(self): @@ -199,21 +197,5 @@ def test_single_vector_extra_dimension(self): array, dims = struct.nd_array_and_dims( input_array, (3, 1, 2, 1), order=self.order ) - self.assertArrayEqual(array, [[1, 101], [2, 102]]) - self.assertEqual(dims, (2,)) - - -class TestArrayStructure_nd_array_and_dims_f_order( - tests.IrisTest, nd_array_and_dims_cases -): - order = "f" - - -class TestArrayStructure_nd_array_and_dims_c_order( - tests.IrisTest, nd_array_and_dims_cases -): - order = "c" - - -if __name__ == "__main__": - tests.main() + assert_array_equal(array, [[1, 101], [2, 102]]) + assert dims == (2,) diff --git a/lib/iris/tests/unit/fileformats/structured_array_identification/test_GroupStructure.py b/lib/iris/tests/unit/fileformats/structured_array_identification/test_GroupStructure.py index 868f37a1a8..ef2d1d2e75 100644 --- a/lib/iris/tests/unit/fileformats/structured_array_identification/test_GroupStructure.py +++ b/lib/iris/tests/unit/fileformats/structured_array_identification/test_GroupStructure.py @@ -12,11 +12,13 @@ import iris.tests as tests # isort:skip import numpy as np +import pytest from iris.fileformats._structured_array_identification import ( ArrayStructure, GroupStructure, ) +from iris.tests._shared_utils import assert_array_equal def regular_array_structures(shape, names="abcdefg"): @@ -34,7 +36,7 @@ class TestGroupStructure_from_component_arrays(tests.IrisTest): def test_different_sizes(self): arrays = {"a": np.arange(6), "b": np.arange(5)} msg = "All array elements must have the same size." - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): GroupStructure.from_component_arrays(arrays) def test_structure_creation(self): @@ -45,8 +47,8 @@ def test_structure_creation(self): grp = GroupStructure.from_component_arrays({"a": array}) - self.assertEqual(grp.length, 6) - self.assertEqual(grp._cmpt_structure, expected_structure) + assert grp.length == 6 + assert grp._cmpt_structure == expected_structure class TestGroupStructure_possible_structures(tests.IrisTest): @@ -66,7 +68,7 @@ def test_simple_3d_structure(self): ("c", array_structures["c"]), ], ) - self.assertEqual(structure.possible_structures(), expected) + assert structure.possible_structures() == expected def assert_potentials(self, length, array_structures, expected): structure = GroupStructure(length, array_structures, array_order="f") @@ -74,7 +76,7 @@ def assert_potentials(self, length, array_structures, expected): names = [ [name for (name, _) in allowed_structure] for allowed_structure in allowed ] - self.assertEqual(names, expected) + assert names == expected def test_multiple_potentials(self): # More than one potential dimension for dim 1. @@ -116,8 +118,8 @@ class TestGroupStructure_build_arrays(tests.IrisTest): def assert_built_array(self, name, result, expected): ex_arr, ex_dims = expected re_arr, re_dims = result[name] - self.assertEqual(ex_dims, re_dims) - self.assertArrayEqual(ex_arr, re_arr) + assert ex_dims == re_dims + assert_array_equal(ex_arr, re_arr) def test_build_arrays_regular_f_order(self): # Construct simple orthogonal 1d array structures, adding a trailing @@ -181,7 +183,3 @@ def test_structured_array_not_applicable(self): }, ) self.assert_built_array("d", r, (expected, (0, 1, 2))) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/lazy_data/test_as_concrete_data.py b/lib/iris/tests/unit/lazy_data/test_as_concrete_data.py index 9b9929a156..1562a28e82 100644 --- a/lib/iris/tests/unit/lazy_data/test_as_concrete_data.py +++ b/lib/iris/tests/unit/lazy_data/test_as_concrete_data.py @@ -4,14 +4,11 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris._lazy data.as_concrete_data`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np import numpy.ma as ma from iris._lazy_data import as_concrete_data, as_lazy_data, is_lazy_data +from iris.tests._shared_utils import assert_array_equal, assert_masked_array_equal class MyProxy: @@ -25,58 +22,54 @@ def __getitem__(self, keys): return self.a[keys] -class Test_as_concrete_data(tests.IrisTest): +class Test_as_concrete_data: def test_concrete_input_data(self): data = np.arange(24).reshape((4, 6)) result = as_concrete_data(data) - self.assertIs(data, result) - self.assertFalse(is_lazy_data(result)) + assert data is result + assert not is_lazy_data(result) def test_concrete_masked_input_data(self): data = ma.masked_array([10, 12, 8, 2], mask=[True, True, False, True]) result = as_concrete_data(data) - self.assertIs(data, result) - self.assertFalse(is_lazy_data(result)) + assert data is result + assert not is_lazy_data(result) def test_lazy_data(self): data = np.arange(24).reshape((2, 12)) lazy_array = as_lazy_data(data) - self.assertTrue(is_lazy_data(lazy_array)) + assert is_lazy_data(lazy_array) result = as_concrete_data(lazy_array) - self.assertFalse(is_lazy_data(result)) - self.assertArrayEqual(result, data) + assert not is_lazy_data(result) + assert_array_equal(result, data) def test_lazy_mask_data(self): data = np.arange(24).reshape((2, 12)) fill_value = 1234 mask_data = ma.masked_array(data, fill_value=fill_value) lazy_array = as_lazy_data(mask_data) - self.assertTrue(is_lazy_data(lazy_array)) + assert is_lazy_data(lazy_array) result = as_concrete_data(lazy_array) - self.assertFalse(is_lazy_data(result)) - self.assertMaskedArrayEqual(result, mask_data) - self.assertEqual(result.fill_value, fill_value) + assert not is_lazy_data(result) + assert_masked_array_equal(result, mask_data) + assert result.fill_value == fill_value def test_lazy_scalar_proxy(self): a = np.array(5) proxy = MyProxy(a) meta = np.empty((0,) * proxy.ndim, dtype=proxy.dtype) lazy_array = as_lazy_data(proxy, meta=meta) - self.assertTrue(is_lazy_data(lazy_array)) + assert is_lazy_data(lazy_array) result = as_concrete_data(lazy_array) - self.assertFalse(is_lazy_data(result)) - self.assertEqual(result, a) + assert not is_lazy_data(result) + assert result == a def test_lazy_scalar_proxy_masked(self): a = np.ma.masked_array(5, True) proxy = MyProxy(a) meta = np.ma.array(np.empty((0,) * proxy.ndim, dtype=proxy.dtype), mask=True) lazy_array = as_lazy_data(proxy, meta=meta) - self.assertTrue(is_lazy_data(lazy_array)) + assert is_lazy_data(lazy_array) result = as_concrete_data(lazy_array) - self.assertFalse(is_lazy_data(result)) - self.assertMaskedArrayEqual(result, a) - - -if __name__ == "__main__": - tests.main() + assert not is_lazy_data(result) + assert_masked_array_equal(result, a) diff --git a/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py b/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py index 90166b5e78..821370ce6c 100644 --- a/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py +++ b/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py @@ -4,56 +4,53 @@ # See LICENSE in the root of the repository for full licensing details. """Test the function :func:`iris._lazy data.as_lazy_data`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from unittest import mock import dask.array as da import dask.config import numpy as np import numpy.ma as ma +import pytest from iris._lazy_data import _optimum_chunksize, as_lazy_data -class Test_as_lazy_data(tests.IrisTest): +class Test_as_lazy_data: def test_lazy(self): data = da.from_array(np.arange(24).reshape((2, 3, 4)), chunks="auto") result = as_lazy_data(data) - self.assertIsInstance(result, da.core.Array) + assert isinstance(result, da.core.Array) def test_real(self): data = np.arange(24).reshape((2, 3, 4)) result = as_lazy_data(data) - self.assertIsInstance(result, da.core.Array) + assert isinstance(result, da.core.Array) def test_masked(self): data = np.ma.masked_greater(np.arange(24), 10) result = as_lazy_data(data) - self.assertIsInstance(result, da.core.Array) + assert isinstance(result, da.core.Array) def test_non_default_chunks(self): data = np.arange(24) chunks = (12,) lazy_data = as_lazy_data(data, chunks=chunks) (result,) = np.unique(lazy_data.chunks) - self.assertEqual(result, 24) + assert result == 24 - def test_dask_chunking(self): + def test_dask_chunking(self, mocker): data = np.arange(24) chunks = (12,) - optimum = self.patch("iris._lazy_data._optimum_chunksize") + optimum = mocker.patch("iris._lazy_data._optimum_chunksize") optimum.return_value = chunks _ = as_lazy_data(data, chunks="auto") - self.assertFalse(optimum.called) + assert not optimum.called def test_with_masked_constant(self): masked_data = ma.masked_array([8], mask=True) masked_constant = masked_data[0] result = as_lazy_data(masked_constant) - self.assertIsInstance(result, da.core.Array) + assert isinstance(result, da.core.Array) def test_missing_meta(self): class MyProxy: @@ -61,15 +58,15 @@ class MyProxy: data = MyProxy() - with self.assertRaisesRegex( + with pytest.raises( ValueError, - r"`meta` cannot be `None` if `data` is anything other than a Numpy " + match=r"For performance reasons, `meta` cannot be `None` if `data` is anything other than a Numpy " r"or Dask array.", ): as_lazy_data(data) -class Test__optimised_chunks(tests.IrisTest): +class Test__optimised_chunks: # Stable, known chunksize for testing. FIXED_CHUNKSIZE_LIMIT = 1024 * 1024 * 64 @@ -91,7 +88,7 @@ def test_chunk_size_limiting(self): for shape, expected in given_shapes_and_resulting_chunks: chunks = _optimum_chunksize(shape, shape, limit=self.FIXED_CHUNKSIZE_LIMIT) msg = err_fmt.format(shape, chunks, expected) - self.assertEqual(chunks, expected, msg) + assert chunks == expected, msg def test_chunk_size_expanding(self): # Check the expansion of small chunks, (with a known size limit). @@ -109,7 +106,7 @@ def test_chunk_size_expanding(self): chunks=shape, shape=fullshape, limit=self.FIXED_CHUNKSIZE_LIMIT ) msg = err_fmt.format(fullshape, shape, chunks, expected) - self.assertEqual(chunks, expected, msg) + assert chunks == expected, msg def test_chunk_expanding_equal_division(self): # Check that expansion chooses equal chunk sizes as far as possible. @@ -147,41 +144,34 @@ def test_chunk_expanding_equal_division(self): chunks=chunks, shape=shape, limit=limit, dtype=np.dtype("b1") ) msg = err_fmt_main.format(chunks, shape, limit, result, expected_result) - self.assertEqual(result, expected_result, msg) + assert result == expected_result, msg def test_default_chunksize(self): # Check that the "ideal" chunksize is taken from the dask config. with dask.config.set({"array.chunk-size": "20b"}): chunks = _optimum_chunksize((1, 8), shape=(400, 20), dtype=np.dtype("f4")) - self.assertEqual(chunks, (1, 4)) + assert chunks == (1, 4) - def test_default_chunks_limiting(self): + def test_default_chunks_limiting(self, mocker): # Check that chunking is still controlled when no specific 'chunks' # is passed. - limitcall_patch = self.patch("iris._lazy_data._optimum_chunksize") + limitcall_patch = mocker.patch("iris._lazy_data._optimum_chunksize") test_shape = (3, 2, 4) data = self._dummydata(test_shape) as_lazy_data(data) - self.assertEqual( - limitcall_patch.call_args_list, - [ - mock.call( - list(test_shape), - shape=test_shape, - dtype=np.dtype("f4"), - dims_fixed=None, - ) - ], - ) + assert limitcall_patch.call_args_list == [ + mock.call( + list(test_shape), + shape=test_shape, + dtype=np.dtype("f4"), + dims_fixed=None, + ) + ] - def test_shapeless_data(self): + def test_shapeless_data(self, mocker): # Check that chunk optimisation is skipped if shape contains a zero. - limitcall_patch = self.patch("iris._lazy_data._optimum_chunksize") + limitcall_patch = mocker.patch("iris._lazy_data._optimum_chunksize") test_shape = (2, 1, 0, 2) data = self._dummydata(test_shape) as_lazy_data(data, chunks=test_shape) - self.assertFalse(limitcall_patch.called) - - -if __name__ == "__main__": - tests.main() + assert not limitcall_patch.called diff --git a/lib/iris/tests/unit/lazy_data/test_co_realise_cubes.py b/lib/iris/tests/unit/lazy_data/test_co_realise_cubes.py index 4e304d4910..ecc969cc2c 100644 --- a/lib/iris/tests/unit/lazy_data/test_co_realise_cubes.py +++ b/lib/iris/tests/unit/lazy_data/test_co_realise_cubes.py @@ -4,14 +4,11 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris._lazy data.co_realise_cubes`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np from iris._lazy_data import as_lazy_data, co_realise_cubes from iris.cube import Cube +from iris.tests._shared_utils import assert_array_all_close class ArrayAccessCounter: @@ -28,7 +25,7 @@ def __getitem__(self, keys): return self._array[keys] -class Test_co_realise_cubes(tests.IrisTest): +class Test_co_realise_cubes: def test_empty(self): # Ensure that 'no args' case does not raise an error. co_realise_cubes() @@ -37,8 +34,8 @@ def test_basic(self): real_data = np.arange(3.0) cube = Cube(as_lazy_data(real_data)) co_realise_cubes(cube) - self.assertFalse(cube.has_lazy_data()) - self.assertArrayAllClose(cube.core_data(), real_data) + assert not cube.has_lazy_data() + assert_array_all_close(cube.core_data(), real_data) def test_multi(self): real_data = np.arange(3.0) @@ -48,11 +45,11 @@ def test_multi(self): result_b = cube_inner + 1 co_realise_cubes(result_a, result_b) # Check that target cubes were realised. - self.assertFalse(result_a.has_lazy_data()) - self.assertFalse(result_b.has_lazy_data()) + assert not result_a.has_lazy_data() + assert not result_b.has_lazy_data() # Check that other cubes referenced remain lazy. - self.assertTrue(cube_base.has_lazy_data()) - self.assertTrue(cube_inner.has_lazy_data()) + assert cube_base.has_lazy_data() + assert cube_inner.has_lazy_data() def test_combined_access(self): wrapped_array = ArrayAccessCounter(np.arange(3.0)) @@ -74,8 +71,4 @@ def test_combined_access(self): # access with no data payload to ascertain the metadata associated with # the dask.array (this access is specific to dask 2+, # see dask.array.utils.meta_from_array). - self.assertEqual(wrapped_array.access_count, 1) - - -if __name__ == "__main__": - tests.main() + assert wrapped_array.access_count == 1 diff --git a/lib/iris/tests/unit/lazy_data/test_is_lazy_data.py b/lib/iris/tests/unit/lazy_data/test_is_lazy_data.py index a8018c67b1..ca61460710 100644 --- a/lib/iris/tests/unit/lazy_data/test_is_lazy_data.py +++ b/lib/iris/tests/unit/lazy_data/test_is_lazy_data.py @@ -4,26 +4,18 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris._lazy data.is_lazy_data`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import dask.array as da import numpy as np from iris._lazy_data import is_lazy_data -class Test_is_lazy_data(tests.IrisTest): +class Test_is_lazy_data: def test_lazy(self): values = np.arange(30).reshape((2, 5, 3)) lazy_array = da.from_array(values, chunks="auto") - self.assertTrue(is_lazy_data(lazy_array)) + assert is_lazy_data(lazy_array) def test_real(self): real_array = np.arange(24).reshape((2, 3, 4)) - self.assertFalse(is_lazy_data(real_array)) - - -if __name__ == "__main__": - tests.main() + assert not is_lazy_data(real_array) diff --git a/lib/iris/tests/unit/lazy_data/test_lazy_elementwise.py b/lib/iris/tests/unit/lazy_data/test_lazy_elementwise.py index 651a774c4d..1600067d79 100644 --- a/lib/iris/tests/unit/lazy_data/test_lazy_elementwise.py +++ b/lib/iris/tests/unit/lazy_data/test_lazy_elementwise.py @@ -4,13 +4,10 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris._lazy data.lazy_elementwise`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np from iris._lazy_data import as_lazy_data, is_lazy_data, lazy_elementwise +from iris.tests._shared_utils import assert_array_all_close def _test_elementwise_op(array): @@ -18,32 +15,26 @@ def _test_elementwise_op(array): return array + 1 -class Test_lazy_elementwise(tests.IrisTest): +class Test_lazy_elementwise: def test_basic(self): concrete_array = np.arange(30).reshape((2, 5, 3)) lazy_array = as_lazy_data(concrete_array) wrapped = lazy_elementwise(lazy_array, _test_elementwise_op) - self.assertTrue(is_lazy_data(wrapped)) - self.assertArrayAllClose( - wrapped.compute(), _test_elementwise_op(concrete_array) - ) + assert is_lazy_data(wrapped) + assert_array_all_close(wrapped.compute(), _test_elementwise_op(concrete_array)) def test_dtype_same(self): concrete_array = np.array([3.0], dtype=np.float16) lazy_array = as_lazy_data(concrete_array) wrapped = lazy_elementwise(lazy_array, _test_elementwise_op) - self.assertTrue(is_lazy_data(wrapped)) - self.assertEqual(wrapped.dtype, np.float16) - self.assertEqual(wrapped.compute().dtype, np.float16) + assert is_lazy_data(wrapped) + assert wrapped.dtype == np.float16 + assert wrapped.compute().dtype == np.float16 def test_dtype_change(self): concrete_array = np.array([True, False]) lazy_array = as_lazy_data(concrete_array) wrapped = lazy_elementwise(lazy_array, _test_elementwise_op) - self.assertTrue(is_lazy_data(wrapped)) - self.assertEqual(wrapped.dtype, np.int_) - self.assertEqual(wrapped.compute().dtype, wrapped.dtype) - - -if __name__ == "__main__": - tests.main() + assert is_lazy_data(wrapped) + assert wrapped.dtype == np.int_ + assert wrapped.compute().dtype == wrapped.dtype diff --git a/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py b/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py index 7d619353ed..be25ab6c09 100644 --- a/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py +++ b/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py @@ -4,33 +4,30 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris._lazy data.map_complete_blocks`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import unittest +from unittest.mock import Mock, PropertyMock import dask.array as da import numpy as np from iris._lazy_data import is_lazy_data, map_complete_blocks +from iris.tests._shared_utils import assert_array_equal def create_mock_cube(array): - cube = unittest.mock.Mock() - cube_data = unittest.mock.PropertyMock(return_value=array) + cube = Mock() + cube_data = PropertyMock(return_value=array) type(cube).data = cube_data cube.dtype = array.dtype - cube.has_lazy_data = unittest.mock.Mock(return_value=is_lazy_data(array)) - cube.lazy_data = unittest.mock.Mock(return_value=array) + cube.has_lazy_data = Mock(return_value=is_lazy_data(array)) + cube.lazy_data = Mock(return_value=array) cube.shape = array.shape # Remove compute so cube is not interpreted as dask array. del cube.compute return cube, cube_data -class Test_map_complete_blocks(tests.IrisTest): - def setUp(self): +class Test_map_complete_blocks: + def setup_method(self): self.array = np.arange(8).reshape(2, 4) def func(chunk): @@ -53,8 +50,8 @@ def test_non_lazy_input(self): result = map_complete_blocks( cube, self.func, dims=(1,), out_sizes=(4,), dtype=self.array.dtype ) - self.assertFalse(is_lazy_data(result)) - self.assertArrayEqual(result, self.func_result) + assert not is_lazy_data(result) + assert_array_equal(result, self.func_result) # check correct data was accessed cube.lazy_data.assert_not_called() cube_data.assert_called_once() @@ -65,8 +62,8 @@ def test_lazy_input(self): result = map_complete_blocks( cube, self.func, dims=(1,), out_sizes=(4,), dtype=lazy_array.dtype ) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(result.compute(), self.func_result) + assert is_lazy_data(result) + assert_array_equal(result.compute(), self.func_result) # check correct data was accessed cube.lazy_data.assert_called_once() cube_data.assert_not_called() @@ -76,17 +73,17 @@ def test_dask_array_input(self): result = map_complete_blocks( lazy_array, self.func, dims=(1,), out_sizes=(4,), dtype=lazy_array.dtype ) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(result.compute(), self.func_result) + assert is_lazy_data(result) + assert_array_equal(result.compute(), self.func_result) def test_dask_masked_array_input(self): array = da.ma.masked_array(np.arange(2), mask=np.arange(2)) result = map_complete_blocks( array, self.func, dims=tuple(), out_sizes=tuple(), dtype=array.dtype ) - self.assertTrue(is_lazy_data(result)) - self.assertTrue(isinstance(da.utils.meta_from_array(result), np.ma.MaskedArray)) - self.assertArrayEqual(result.compute(), np.ma.masked_array([1, 2], mask=[0, 1])) + assert is_lazy_data(result) + assert isinstance(da.utils.meta_from_array(result), np.ma.MaskedArray) + assert_array_equal(result.compute(), np.ma.masked_array([1, 2], mask=[0, 1])) def test_dask_array_input_with_different_output_dtype(self): lazy_array = da.ma.masked_array(self.array, chunks=((1, 1), (4,))) @@ -100,10 +97,10 @@ def func(chunk): result = map_complete_blocks( lazy_array, func, dims=(1,), out_sizes=(4,), dtype=dtype ) - self.assertTrue(isinstance(da.utils.meta_from_array(result), np.ma.MaskedArray)) - self.assertTrue(result.dtype == dtype) - self.assertTrue(result.compute().dtype == dtype) - self.assertArrayEqual(result.compute(), self.func_result) + assert isinstance(da.utils.meta_from_array(result), np.ma.MaskedArray) + assert result.dtype == dtype + assert result.compute().dtype == dtype + assert_array_equal(result.compute(), self.func_result) def test_rechunk(self): lazy_array = da.asarray(self.array, chunks=((1, 1), (2, 2))) @@ -111,8 +108,8 @@ def test_rechunk(self): result = map_complete_blocks( cube, self.func, dims=(1,), out_sizes=(4,), dtype=lazy_array.dtype ) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(result.compute(), self.func_result) + assert is_lazy_data(result) + assert_array_equal(result.compute(), self.func_result) def test_different_out_shape(self): lazy_array = da.asarray(self.array, chunks=((1, 1), (4,))) @@ -125,8 +122,8 @@ def func(_): result = map_complete_blocks( cube, func, dims=(1,), out_sizes=(2,), dtype=lazy_array.dtype ) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(result.compute(), func_result) + assert is_lazy_data(result) + assert_array_equal(result.compute(), func_result) def test_multidimensional_input(self): array = np.arange(2 * 3 * 4).reshape(2, 3, 4) @@ -135,9 +132,5 @@ def test_multidimensional_input(self): result = map_complete_blocks( cube, self.func, dims=(1, 2), out_sizes=(3, 4), dtype=lazy_array.dtype ) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(result.compute(), array + 1) - - -if __name__ == "__main__": - tests.main() + assert is_lazy_data(result) + assert_array_equal(result.compute(), array + 1) diff --git a/lib/iris/tests/unit/lazy_data/test_multidim_lazy_stack.py b/lib/iris/tests/unit/lazy_data/test_multidim_lazy_stack.py index 993cb01178..93e04659fa 100644 --- a/lib/iris/tests/unit/lazy_data/test_multidim_lazy_stack.py +++ b/lib/iris/tests/unit/lazy_data/test_multidim_lazy_stack.py @@ -4,17 +4,14 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris._lazy data.multidim_lazy_stack`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import dask.array as da import numpy as np from iris._lazy_data import as_concrete_data, as_lazy_data, multidim_lazy_stack +from iris.tests._shared_utils import assert_array_all_close -class Test_multidim_lazy_stack(tests.IrisTest): +class Test_multidim_lazy_stack: def _check(self, stack_shape): vals = np.arange(np.prod(stack_shape)).reshape(stack_shape) stack = np.empty(stack_shape, "object") @@ -26,10 +23,10 @@ def _check(self, stack_shape): expected[index] = val result = multidim_lazy_stack(stack) - self.assertEqual(result.shape, stack_shape + stack_element_shape) - self.assertIsInstance(result, da.core.Array) + assert result.shape == stack_shape + stack_element_shape + assert isinstance(result, da.core.Array) result = as_concrete_data(result) - self.assertArrayAllClose(result, expected) + assert_array_all_close(result, expected) def test_0d_lazy_stack(self): shape = () @@ -42,7 +39,3 @@ def test_1d_lazy_stack(self): def test_2d_lazy_stack(self): shape = (3, 2) self._check(shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/lazy_data/test_non_lazy.py b/lib/iris/tests/unit/lazy_data/test_non_lazy.py index 3c6bb99e0a..9fa496d56f 100644 --- a/lib/iris/tests/unit/lazy_data/test_non_lazy.py +++ b/lib/iris/tests/unit/lazy_data/test_non_lazy.py @@ -4,17 +4,14 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris._lazy data.non_lazy`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np from iris._lazy_data import as_lazy_data, is_lazy_data, non_lazy +from iris.tests._shared_utils import assert_array_equal -class Test_non_lazy(tests.IrisTest): - def setUp(self): +class Test_non_lazy: + def setup_method(self): self.array = np.arange(8).reshape(2, 4) self.lazy_array = as_lazy_data(self.array) self.func = non_lazy(lambda array: array.sum(axis=0)) @@ -22,15 +19,11 @@ def setUp(self): def test_lazy_input(self): result = self.func(self.lazy_array) - self.assertFalse(is_lazy_data(result)) - self.assertArrayEqual(result, self.func_result) + assert not is_lazy_data(result) + assert_array_equal(result, self.func_result) def test_non_lazy_input(self): # Check that a non-lazy input doesn't trip up the functionality. result = self.func(self.array) - self.assertFalse(is_lazy_data(result)) - self.assertArrayEqual(result, self.func_result) - - -if __name__ == "__main__": - tests.main() + assert not is_lazy_data(result) + assert_array_equal(result, self.func_result) diff --git a/lib/iris/tests/unit/plot/__init__.py b/lib/iris/tests/unit/plot/__init__.py index c262d014f3..3438d6884a 100644 --- a/lib/iris/tests/unit/plot/__init__.py +++ b/lib/iris/tests/unit/plot/__init__.py @@ -4,19 +4,18 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :mod:`iris.plot` module.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip +import pytest from iris.coords import AuxCoord from iris.plot import _broadcast_2d as broadcast +from iris.tests import _shared_utils from iris.tests.stock import lat_lon_cube, simple_2d -@tests.skip_plot -class TestGraphicStringCoord(tests.GraphicsTest): - def setUp(self): - super().setUp() +@_shared_utils.skip_plot +class TestGraphicStringCoord(_shared_utils.GraphicsTest): + @pytest.fixture(autouse=True) + def _setup(self): self.cube = simple_2d(with_bounds=True) self.cube.add_aux_coord(AuxCoord(list("abcd"), long_name="str_coord"), 1) self.lat_lon_cube = lat_lon_cube() @@ -38,7 +37,7 @@ def tick_loc_and_label(self, axis_name, axes=None): labels = [tick.get_text() for tick in axis.get_ticklabels()] return list(zip(locations, labels)) - def assertBoundsTickLabels(self, axis, axes=None): + def assert_bounds_tick_labels(self, axis, axes=None): actual = self.tick_loc_and_label(axis, axes) expected = [ (-1.0, ""), @@ -48,15 +47,15 @@ def assertBoundsTickLabels(self, axis, axes=None): (3.0, "d"), (4.0, ""), ] - self.assertEqual(expected, actual) + assert expected == actual - def assertPointsTickLabels(self, axis, axes=None): + def assert_points_tick_labels(self, axis, axes=None): actual = self.tick_loc_and_label(axis, axes) expected = [(0.0, "a"), (1.0, "b"), (2.0, "c"), (3.0, "d")] - self.assertEqual(expected, actual) + assert expected == actual -@tests.skip_plot +@_shared_utils.skip_plot class MixinCoords: """Mixin class of common plotting tests providing 2-dimensional permutations of coordinates and anonymous dimensions. @@ -64,14 +63,14 @@ class MixinCoords: """ def _check(self, u, v, data=None): - self.assertEqual(self.mpl_patch.call_count, 1) + assert self.mpl_patch.call_count == 1 if data is not None: (actual_u, actual_v, actual_data), _ = self.mpl_patch.call_args - self.assertArrayEqual(actual_data, data) + _shared_utils.assert_array_equal(actual_data, data) else: (actual_u, actual_v), _ = self.mpl_patch.call_args - self.assertArrayEqual(actual_u, u) - self.assertArrayEqual(actual_v, v) + _shared_utils.assert_array_equal(actual_u, u) + _shared_utils.assert_array_equal(actual_v, v) def test_foo_bar(self): self.draw_func(self.cube, coords=("foo", "bar")) diff --git a/lib/iris/tests/unit/plot/_blockplot_common.py b/lib/iris/tests/unit/plot/_blockplot_common.py index 04a7d8866f..a7abd9dd23 100644 --- a/lib/iris/tests/unit/plot/_blockplot_common.py +++ b/lib/iris/tests/unit/plot/_blockplot_common.py @@ -4,13 +4,8 @@ # See LICENSE in the root of the repository for full licensing details. """Common test code for `iris.plot.pcolor` and `iris.plot.pcolormesh`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - import numpy as np +import pytest from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords @@ -23,11 +18,11 @@ class MixinStringCoordPlot: # and defines "self.blockplot_func()", to return the `iris.plot` function. def test_yaxis_labels(self): self.blockplot_func()(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") + self.assert_bounds_tick_labels("yaxis") def test_xaxis_labels(self): self.blockplot_func()(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") def test_xaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -37,7 +32,7 @@ def test_xaxis_labels_with_axes(self): ax.set_xlim(0, 3) self.blockplot_func()(self.cube, coords=("str_coord", "bar"), axes=ax) plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) + self.assert_points_tick_labels("xaxis", ax) def test_yaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -47,23 +42,23 @@ def test_yaxis_labels_with_axes(self): ax.set_ylim(0, 3) self.blockplot_func()(self.cube, axes=ax, coords=("bar", "str_coord")) plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) + self.assert_points_tick_labels("yaxis", ax) def test_geoaxes_exception(self): import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) - self.assertRaises(TypeError, self.blockplot_func(), self.lat_lon_cube, axes=ax) + pytest.raises(TypeError, self.blockplot_func(), self.lat_lon_cube, axes=ax) plt.close(fig) class Mixin2dCoordsPlot(MixinCoords): # Mixin for common coordinate tests on pcolor/pcolormesh. # To use, make a class that inherits from this *and* - # :class:`iris.tests.IrisTest`, - # and defines "self.blockplot_func()", to return the `iris.plot` function. - def blockplot_setup(self): + # defines "self.blockplot_func()", to return the `iris.plot` function. + @pytest.fixture(autouse=True) + def _blockplot_setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=True) coord = self.cube.coord("foo") @@ -76,22 +71,21 @@ def blockplot_setup(self): self.dataT = self.data.T self.draw_func = self.blockplot_func() patch_target_name = "matplotlib.pyplot." + self.draw_func.__name__ - self.mpl_patch = self.patch(patch_target_name) + self.mpl_patch = mocker.patch(patch_target_name) class Mixin2dCoordsContigTol: # Mixin for contiguity tolerance argument to pcolor/pcolormesh. # To use, make a class that inherits from this *and* - # :class:`iris.tests.IrisTest`, - # and defines "self.blockplot_func()", to return the `iris.plot` function, + # defines "self.blockplot_func()", to return the `iris.plot` function, # and defines "self.additional_kwargs" for expected extra call args. - def test_contig_tol(self): + def test_contig_tol(self, mocker): # Patch the inner call to ensure contiguity_tolerance is passed. - cube_argument = mock.sentinel.passed_arg - expected_result = mock.sentinel.returned_value - blockplot_patch = self.patch( + cube_argument = mocker.sentinel.passed_arg + expected_result = mocker.sentinel.returned_value + blockplot_patch = mocker.patch( "iris.plot._draw_2d_from_bounds", - mock.Mock(return_value=expected_result), + mocker.Mock(return_value=expected_result), ) # Make the call draw_func = self.blockplot_func() @@ -99,19 +93,12 @@ def test_contig_tol(self): result = draw_func(cube_argument, contiguity_tolerance=0.0123) drawfunc_name = draw_func.__name__ # Check details of the call that was made. - self.assertEqual( - blockplot_patch.call_args_list, - [ - mock.call( - drawfunc_name, - cube_argument, - contiguity_tolerance=0.0123, - **other_kwargs, - ) - ], - ) - self.assertEqual(result, expected_result) - - -if __name__ == "__main__": - tests.main() + assert blockplot_patch.call_args_list == [ + mocker.call( + drawfunc_name, + cube_argument, + contiguity_tolerance=0.0123, + **other_kwargs, + ) + ] + assert result == expected_result diff --git a/lib/iris/tests/unit/plot/test__check_bounds_contiguity_and_mask.py b/lib/iris/tests/unit/plot/test__check_bounds_contiguity_and_mask.py index 9ec80cbd50..3b23945118 100644 --- a/lib/iris/tests/unit/plot/test__check_bounds_contiguity_and_mask.py +++ b/lib/iris/tests/unit/plot/test__check_bounds_contiguity_and_mask.py @@ -6,22 +6,18 @@ function. """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - import numpy as np import numpy.ma as ma +import pytest from iris.coords import DimCoord from iris.plot import _check_bounds_contiguity_and_mask +from iris.tests import _shared_utils from iris.tests.stock import make_bounds_discontiguous_at_point, sample_2d_latlons -@tests.skip_plot -class Test_check_bounds_contiguity_and_mask(tests.IrisTest): +@_shared_utils.skip_plot +class Test_check_bounds_contiguity_and_mask: def test_1d_not_checked(self): # Test a 1D coordinate, which is not checked as atol is not set. coord = DimCoord([1, 3, 5], bounds=[[0, 2], [2, 4], [5, 6]]) @@ -51,7 +47,7 @@ def test_1d_discontigous_unmasked(self): "coordinate are not contiguous and data is not masked where " "the discontiguity occurs" ) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): _check_bounds_contiguity_and_mask(coord, data, atol=1e-3) def test_2d_contiguous(self): @@ -60,18 +56,14 @@ def test_2d_contiguous(self): cube = sample_2d_latlons() _check_bounds_contiguity_and_mask(cube.coord("longitude"), cube.data) - def test_2d_contiguous_atol(self): + def test_2d_contiguous_atol(self, mocker): # Check the atol is passed correctly. cube = sample_2d_latlons() - with mock.patch( - "iris.coords.Coord._discontiguity_in_bounds" - ) as discontiguity_check: - # Discontiguity returns two objects that are unpacked in - # `_check_bounds_contiguity_and_mask`. - discontiguity_check.return_value = [True, None] - _check_bounds_contiguity_and_mask( - cube.coord("longitude"), cube.data, atol=1e-3 - ) + discontiguity_check = mocker.patch("iris.coords.Coord._discontiguity_in_bounds") + # Discontiguity returns two objects that are unpacked in + # `_check_bounds_contiguity_and_mask`. + discontiguity_check.return_value = [True, None] + _check_bounds_contiguity_and_mask(cube.coord("longitude"), cube.data, atol=1e-3) discontiguity_check.assert_called_with(atol=1e-3) def test_2d_discontigous_masked(self): @@ -88,9 +80,5 @@ def test_2d_discontigous_unmasked(self): make_bounds_discontiguous_at_point(cube, 3, 4) msg = "coordinate are not contiguous" cube.data[3, 4] = ma.nomask - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): _check_bounds_contiguity_and_mask(cube.coord("longitude"), cube.data) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test__check_geostationary_coords_and_convert.py b/lib/iris/tests/unit/plot/test__check_geostationary_coords_and_convert.py index bf724c443c..c881d550df 100644 --- a/lib/iris/tests/unit/plot/test__check_geostationary_coords_and_convert.py +++ b/lib/iris/tests/unit/plot/test__check_geostationary_coords_and_convert.py @@ -6,20 +6,19 @@ function. """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest.mock import Mock - from cartopy.crs import Geostationary, NearsidePerspective import numpy as np +import pytest from iris.plot import _check_geostationary_coords_and_convert +from iris.tests import _shared_utils + +class Test__check_geostationary_coords_and_convert: + @pytest.fixture(autouse=True) + def _setup(self, mocker): + self.mocker = mocker -class Test__check_geostationary_coords_and_convert(tests.IrisTest): - def setUp(self): geostationary_altitude = 35785831.0 # proj4_params is the one attribute of the Geostationary class that # is needed for the function. @@ -46,7 +45,7 @@ def _test(self, geostationary=True): projection_spec = NearsidePerspective target_tuple = (self.x_original, self.y_original) - projection = Mock(spec=projection_spec) + projection = self.mocker.Mock(spec=projection_spec) projection.proj4_params = self.proj4_params # Projection is looked for within a dictionary called kwargs. kwargs = {"transform": projection} @@ -54,7 +53,7 @@ def _test(self, geostationary=True): x, y = _check_geostationary_coords_and_convert( self.x_original, self.y_original, kwargs ) - self.assertArrayEqual((x, y), target_tuple) + _shared_utils.assert_array_equal((x, y), target_tuple) def test_geostationary_present(self): self._test(geostationary=True) diff --git a/lib/iris/tests/unit/plot/test__fixup_dates.py b/lib/iris/tests/unit/plot/test__fixup_dates.py index d155f30969..4f5b88faab 100644 --- a/lib/iris/tests/unit/plot/test__fixup_dates.py +++ b/lib/iris/tests/unit/plot/test__fixup_dates.py @@ -4,10 +4,6 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot._fixup_dates` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import datetime from cf_units import Unit @@ -15,34 +11,35 @@ from iris.coords import AuxCoord from iris.plot import _fixup_dates +from iris.tests import _shared_utils -class Test(tests.IrisTest): +class Test: def test_standard_calendar(self): unit = Unit("hours since 2000-04-13 00:00:00", calendar="standard") coord = AuxCoord([1, 3, 6], "time", units=unit) result = _fixup_dates(coord, coord.points) - self.assertIsInstance(result[0], datetime.datetime) + assert isinstance(result[0], datetime.datetime) expected = [ datetime.datetime(2000, 4, 13, 1), datetime.datetime(2000, 4, 13, 3), datetime.datetime(2000, 4, 13, 6), ] - self.assertArrayEqual(result, expected) + _shared_utils.assert_array_equal(result, expected) def test_standard_calendar_sub_second(self): unit = Unit("seconds since 2000-04-13 00:00:00", calendar="standard") coord = AuxCoord([1, 1.25, 1.5], "time", units=unit) result = _fixup_dates(coord, coord.points) - self.assertIsInstance(result[0], datetime.datetime) + assert isinstance(result[0], datetime.datetime) expected = [ datetime.datetime(2000, 4, 13, 0, 0, 1), datetime.datetime(2000, 4, 13, 0, 0, 1), datetime.datetime(2000, 4, 13, 0, 0, 2), ] - self.assertArrayEqual(result, expected) + _shared_utils.assert_array_equal(result, expected) - @tests.skip_nc_time_axis + @_shared_utils.skip_nc_time_axis def test_360_day_calendar(self): calendar = "360_day" unit = Unit("days since 2000-02-25 00:00:00", calendar=calendar) @@ -53,9 +50,9 @@ def test_360_day_calendar(self): cftime.datetime(2000, 2, 29, calendar=calendar), cftime.datetime(2000, 2, 30, calendar=calendar), ] - self.assertArrayEqual(result, expected_datetimes) + _shared_utils.assert_array_equal(result, expected_datetimes) - @tests.skip_nc_time_axis + @_shared_utils.skip_nc_time_axis def test_365_day_calendar(self): calendar = "365_day" unit = Unit("minutes since 2000-02-25 00:00:00", calendar=calendar) @@ -66,16 +63,12 @@ def test_365_day_calendar(self): cftime.datetime(2000, 2, 25, 1, 0, calendar=calendar), cftime.datetime(2000, 2, 25, 2, 30, calendar=calendar), ] - self.assertArrayEqual(result, expected_datetimes) + _shared_utils.assert_array_equal(result, expected_datetimes) - @tests.skip_nc_time_axis + @_shared_utils.skip_nc_time_axis def test_360_day_calendar_attribute(self): calendar = "360_day" unit = Unit("days since 2000-02-01 00:00:00", calendar=calendar) coord = AuxCoord([0, 3, 6], "time", units=unit) result = _fixup_dates(coord, coord.points) - self.assertEqual(result[0].calendar, calendar) - - -if __name__ == "__main__": - tests.main() + assert result[0].calendar == calendar diff --git a/lib/iris/tests/unit/plot/test__get_plot_defn.py b/lib/iris/tests/unit/plot/test__get_plot_defn.py index 4032c8792d..81d54f9716 100644 --- a/lib/iris/tests/unit/plot/test__get_plot_defn.py +++ b/lib/iris/tests/unit/plot/test__get_plot_defn.py @@ -4,35 +4,28 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot._get_plot_defn` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import iris.coords +from iris.tests import _shared_utils from iris.tests.stock import simple_2d, simple_2d_w_multidim_coords -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt -@tests.skip_plot -class Test_get_plot_defn(tests.IrisTest): +@_shared_utils.skip_plot +class Test_get_plot_defn: def test_axis_order_xy(self): cube_xy = simple_2d() defn = iplt._get_plot_defn(cube_xy, iris.coords.POINT_MODE) - self.assertEqual([coord.name() for coord in defn.coords], ["bar", "foo"]) + assert [coord.name() for coord in defn.coords] == ["bar", "foo"] def test_axis_order_yx(self): cube_yx = simple_2d() cube_yx.transpose() defn = iplt._get_plot_defn(cube_yx, iris.coords.POINT_MODE) - self.assertEqual([coord.name() for coord in defn.coords], ["foo", "bar"]) + assert [coord.name() for coord in defn.coords] == ["foo", "bar"] def test_2d_coords(self): cube = simple_2d_w_multidim_coords() defn = iplt._get_plot_defn(cube, iris.coords.BOUND_MODE) - self.assertEqual([coord.name() for coord in defn.coords], ["bar", "foo"]) - - -if __name__ == "__main__": - tests.main() + assert [coord.name() for coord in defn.coords] == ["bar", "foo"] diff --git a/lib/iris/tests/unit/plot/test__get_plot_defn_custom_coords_picked.py b/lib/iris/tests/unit/plot/test__get_plot_defn_custom_coords_picked.py index 7b39043559..defae2ca86 100644 --- a/lib/iris/tests/unit/plot/test__get_plot_defn_custom_coords_picked.py +++ b/lib/iris/tests/unit/plot/test__get_plot_defn_custom_coords_picked.py @@ -6,69 +6,64 @@ function. """ -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip +import pytest from iris.coords import BOUND_MODE, POINT_MODE +from iris.tests import _shared_utils from iris.tests.stock import hybrid_height, simple_2d, simple_2d_w_multidim_coords -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt -@tests.skip_plot -class Test_get_plot_defn_custom_coords_picked(tests.IrisTest): +@_shared_utils.skip_plot +class Test_get_plot_defn_custom_coords_picked: def test_1d_coords(self): cube = simple_2d() defn = iplt._get_plot_defn_custom_coords_picked( cube, ("foo", "bar"), POINT_MODE ) - self.assertEqual([coord.name() for coord in defn.coords], ["bar", "foo"]) - self.assertFalse(defn.transpose) + assert [coord.name() for coord in defn.coords] == ["bar", "foo"] + assert not defn.transpose def test_1d_coords_swapped(self): cube = simple_2d() defn = iplt._get_plot_defn_custom_coords_picked( cube, ("bar", "foo"), POINT_MODE ) - self.assertEqual([coord.name() for coord in defn.coords], ["foo", "bar"]) - self.assertTrue(defn.transpose) + assert [coord.name() for coord in defn.coords] == ["foo", "bar"] + assert defn.transpose def test_1d_coords_as_integers(self): cube = simple_2d() defn = iplt._get_plot_defn_custom_coords_picked(cube, (1, 0), POINT_MODE) - self.assertEqual([coord for coord in defn.coords], [0, 1]) - self.assertFalse(defn.transpose) + assert [coord for coord in defn.coords] == [0, 1] + assert not defn.transpose def test_1d_coords_as_integers_swapped(self): cube = simple_2d() defn = iplt._get_plot_defn_custom_coords_picked(cube, (0, 1), POINT_MODE) - self.assertEqual([coord for coord in defn.coords], [1, 0]) - self.assertTrue(defn.transpose) + assert [coord for coord in defn.coords] == [1, 0] + assert defn.transpose def test_2d_coords(self): cube = simple_2d_w_multidim_coords() defn = iplt._get_plot_defn_custom_coords_picked( cube, ("foo", "bar"), BOUND_MODE ) - self.assertEqual([coord.name() for coord in defn.coords], ["bar", "foo"]) - self.assertFalse(defn.transpose) + assert [coord.name() for coord in defn.coords] == ["bar", "foo"] + assert not defn.transpose def test_2d_coords_as_integers(self): cube = simple_2d_w_multidim_coords() defn = iplt._get_plot_defn_custom_coords_picked(cube, (0, 1), BOUND_MODE) - self.assertEqual([coord for coord in defn.coords], [1, 0]) - self.assertTrue(defn.transpose) + assert [coord for coord in defn.coords] == [1, 0] + assert defn.transpose def test_span_check(self): cube = hybrid_height() emsg = "don't span the 2 data dimensions" - with self.assertRaisesRegex(ValueError, emsg): + with pytest.raises(ValueError, match=emsg): iplt._get_plot_defn_custom_coords_picked( cube, ("sigma", "level_height"), POINT_MODE ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test__get_plot_objects.py b/lib/iris/tests/unit/plot/test__get_plot_objects.py index fbccbe94fb..a0ae48141a 100644 --- a/lib/iris/tests/unit/plot/test__get_plot_objects.py +++ b/lib/iris/tests/unit/plot/test__get_plot_objects.py @@ -4,41 +4,32 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot._get_plot_objects` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip +import pytest import iris.cube +from iris.tests import _shared_utils -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: from iris.plot import _get_plot_objects -@tests.skip_plot -class Test__get_plot_objects(tests.IrisTest): +@_shared_utils.skip_plot +class Test__get_plot_objects: def test_scalar(self): cube1 = iris.cube.Cube(1) cube2 = iris.cube.Cube(1) expected = (cube1, cube2, 1, 1, ()) result = _get_plot_objects((cube1, cube2)) - self.assertTupleEqual(expected, result) + assert result == expected def test_mismatched_size_first_scalar(self): cube1 = iris.cube.Cube(1) cube2 = iris.cube.Cube([1, 42]) - with self.assertRaisesRegex( - ValueError, "x and y-axis objects are not compatible" - ): + with pytest.raises(ValueError, match="x and y-axis objects are not compatible"): _get_plot_objects((cube1, cube2)) def test_mismatched_size_second_scalar(self): cube1 = iris.cube.Cube(1) cube2 = iris.cube.Cube([1, 42]) - with self.assertRaisesRegex( - ValueError, "x and y-axis objects are not compatible" - ): + with pytest.raises(ValueError, match="x and y-axis objects are not compatible"): _get_plot_objects((cube2, cube1)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py b/lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py index cac42bb765..e8d4b6d1cd 100644 --- a/lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py +++ b/lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py @@ -4,20 +4,21 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot.__replace_axes_with_cartopy_axes` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import cartopy.crs as ccrs import matplotlib.pyplot as plt +import pytest from iris.plot import _replace_axes_with_cartopy_axes +from iris.tests import _shared_utils -@tests.skip_plot -class Test_replace_axes_with_cartopy_axes(tests.IrisTest): - def setUp(self): +@_shared_utils.skip_plot +class Test_replace_axes_with_cartopy_axes: + @pytest.fixture(autouse=True) + def _setup(self): self.fig = plt.figure() + yield + plt.close(self.fig) def test_preserve_position(self): position = [0.17, 0.65, 0.2, 0.2] @@ -32,18 +33,11 @@ def test_preserve_position(self): # get_position returns mpl.transforms.Bbox object, for which equality does # not appear to be implemented. Compare the bounds (tuple) instead. - self.assertEqual(expected.get_position().bounds, result.get_position().bounds) + assert expected.get_position().bounds == result.get_position().bounds def test_ax_on_subfigure(self): subfig, _ = self.fig.subfigures(nrows=2) subfig.subplots() _replace_axes_with_cartopy_axes(ccrs.PlateCarree()) result = plt.gca() - self.assertIs(result.get_figure(), subfig) - - def tearDown(self): - plt.close(self.fig) - - -if __name__ == "__main__": - tests.main() + assert result.get_figure() is subfig diff --git a/lib/iris/tests/unit/plot/test_contour.py b/lib/iris/tests/unit/plot/test_contour.py index 43c0564ff4..e874c756ce 100644 --- a/lib/iris/tests/unit/plot/test_contour.py +++ b/lib/iris/tests/unit/plot/test_contour.py @@ -4,28 +4,26 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot.contour` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): def test_yaxis_labels(self): iplt.contour(self.cube, coords=("bar", "str_coord")) - self.assertPointsTickLabels("yaxis") + self.assert_points_tick_labels("yaxis") def test_xaxis_labels(self): iplt.contour(self.cube, coords=("str_coord", "bar")) - self.assertPointsTickLabels("xaxis") + self.assert_points_tick_labels("xaxis") def test_yaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -34,7 +32,7 @@ def test_yaxis_labels_with_axes(self): ax = fig.add_subplot(111) iplt.contour(self.cube, axes=ax, coords=("bar", "str_coord")) plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) + self.assert_points_tick_labels("yaxis", ax) def test_xaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -43,20 +41,21 @@ def test_xaxis_labels_with_axes(self): ax = fig.add_subplot(111) iplt.contour(self.cube, axes=ax, coords=("str_coord", "bar")) plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) + self.assert_points_tick_labels("xaxis", ax) def test_geoaxes_exception(self): import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) - self.assertRaises(TypeError, iplt.contour, self.lat_lon_cube, axes=ax) + pytest.raises(TypeError, iplt.contour, self.lat_lon_cube, axes=ax) plt.close(fig) -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): +@_shared_utils.skip_plot +class TestCoords(MixinCoords): + @pytest.fixture(autouse=True) + def _setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=False) self.foo = self.cube.coord("foo").points @@ -65,9 +64,5 @@ def setUp(self): self.bar_index = np.arange(self.bar.size) self.data = self.cube.data self.dataT = self.data.T - self.mpl_patch = self.patch("matplotlib.pyplot.contour") + self.mpl_patch = mocker.patch("matplotlib.pyplot.contour") self.draw_func = iplt.contour - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_contourf.py b/lib/iris/tests/unit/plot/test_contourf.py index 59fe631b67..6ee170f4c1 100644 --- a/lib/iris/tests/unit/plot/test_contourf.py +++ b/lib/iris/tests/unit/plot/test_contourf.py @@ -4,31 +4,27 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot.contourf` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - import matplotlib.pyplot as plt import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): def test_yaxis_labels(self): iplt.contourf(self.cube, coords=("bar", "str_coord")) - self.assertPointsTickLabels("yaxis") + self.assert_points_tick_labels("yaxis") def test_xaxis_labels(self): iplt.contourf(self.cube, coords=("str_coord", "bar")) - self.assertPointsTickLabels("xaxis") + self.assert_points_tick_labels("xaxis") def test_yaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -37,7 +33,7 @@ def test_yaxis_labels_with_axes(self): ax = fig.add_subplot(111) iplt.contourf(self.cube, axes=ax, coords=("bar", "str_coord")) plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) + self.assert_points_tick_labels("yaxis", ax) def test_xaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -46,20 +42,21 @@ def test_xaxis_labels_with_axes(self): ax = fig.add_subplot(111) iplt.contourf(self.cube, axes=ax, coords=("str_coord", "bar")) plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) + self.assert_points_tick_labels("xaxis", ax) def test_geoaxes_exception(self): import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) - self.assertRaises(TypeError, iplt.contourf, self.lat_lon_cube, axes=ax) + pytest.raises(TypeError, iplt.contourf, self.lat_lon_cube, axes=ax) plt.close(fig) -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): +@_shared_utils.skip_plot +class TestCoords(MixinCoords): + @pytest.fixture(autouse=True) + def _setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=False) self.foo = self.cube.coord("foo").points @@ -68,29 +65,31 @@ def setUp(self): self.bar_index = np.arange(self.bar.size) self.data = self.cube.data self.dataT = self.data.T - mocker = mock.Mock(wraps=plt.contourf) - self.mpl_patch = self.patch("matplotlib.pyplot.contourf", mocker) + self.mpl_patch = mocker.patch("matplotlib.pyplot.contourf") self.draw_func = iplt.contourf -@tests.skip_plot -class TestAntialias(tests.IrisTest): - def setUp(self): +@_shared_utils.skip_plot +class TestAntialias: + @pytest.fixture(autouse=True) + def _setup(self): self.fig = plt.figure() + yield + plt.close(self.fig) - def test_skip_contour(self): + def test_skip_contour(self, mocker): # Contours should not be added if data is all below second level. See #4086. cube = simple_2d() levels = [5, 15, 20, 200] colors = ["b", "r", "y"] - with mock.patch("matplotlib.pyplot.contour") as mocked_contour: - iplt.contourf(cube, levels=levels, colors=colors, antialiased=True) + mocked_contour = mocker.patch("matplotlib.pyplot.contour") + iplt.contourf(cube, levels=levels, colors=colors, antialiased=True) mocked_contour.assert_not_called() - def test_apply_contour_nans(self): + def test_apply_contour_nans(self, mocker): # Presence of nans should not prevent contours being added. cube = simple_2d() cube.data = cube.data.astype(np.float64) @@ -99,14 +98,7 @@ def test_apply_contour_nans(self): levels = [2, 4, 6, 8] colors = ["b", "r", "y"] - with mock.patch("matplotlib.pyplot.contour") as mocked_contour: - iplt.contourf(cube, levels=levels, colors=colors, antialiased=True) + mocked_contour = mocker.patch("matplotlib.pyplot.contour") + iplt.contourf(cube, levels=levels, colors=colors, antialiased=True) mocked_contour.assert_called_once() - - def tearDown(self): - plt.close(self.fig) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_hist.py b/lib/iris/tests/unit/plot/test_hist.py index 9c1740587c..c4651e846e 100644 --- a/lib/iris/tests/unit/plot/test_hist.py +++ b/lib/iris/tests/unit/plot/test_hist.py @@ -4,47 +4,42 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot.hist` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - import numpy as np import pytest from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord from iris.cube import Cube +from iris.tests import _shared_utils -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt -@tests.skip_plot +@_shared_utils.skip_plot class Test: @pytest.fixture(autouse=True) - def create_data(self): + def _create_data(self): self.data = np.array([0, 100, 110, 120, 200, 320]) @pytest.mark.parametrize( "x", [AuxCoord, Cube, DimCoord, CellMeasure, AncillaryVariable] ) - def test_simple(self, x): - with mock.patch("matplotlib.pyplot.hist") as mocker: - iplt.hist(x(self.data)) + def test_simple(self, x, mocker): + mock_patch = mocker.patch("matplotlib.pyplot.hist") + iplt.hist(x(self.data)) # mocker.assert_called_once_with is not working as expected with - # _DimensionalMetadata objects so we use np.testing array equality + # _DimensionalMetadata objects so we use array equality # checks instead. - args, kwargs = mocker.call_args + args, kwargs = mock_patch.call_args assert len(args) == 1 - np.testing.assert_array_equal(args[0], self.data) + _shared_utils.assert_array_equal(args[0], self.data) - def test_kwargs(self): + def test_kwargs(self, mocker): cube = Cube(self.data) bins = [0, 150, 250, 350] - with mock.patch("matplotlib.pyplot.hist") as mocker: - iplt.hist(cube, bins=bins) - mocker.assert_called_once_with(self.data, bins=bins) + mock_patch = mocker.patch("matplotlib.pyplot.hist") + iplt.hist(cube, bins=bins) + mock_patch.assert_called_once_with(self.data, bins=bins) def test_unsupported_input(self): with pytest.raises(TypeError, match="x must be a"): diff --git a/lib/iris/tests/unit/plot/test_outline.py b/lib/iris/tests/unit/plot/test_outline.py index dc1b27487b..2c84a6c718 100644 --- a/lib/iris/tests/unit/plot/test_outline.py +++ b/lib/iris/tests/unit/plot/test_outline.py @@ -4,28 +4,26 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot.outline` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): def test_yaxis_labels(self): iplt.outline(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") + self.assert_bounds_tick_labels("yaxis") def test_xaxis_labels(self): iplt.outline(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") def test_xaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -35,7 +33,7 @@ def test_xaxis_labels_with_axes(self): ax.set_xlim(0, 3) iplt.outline(self.cube, coords=("str_coord", "bar"), axes=ax) plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) + self.assert_points_tick_labels("xaxis", ax) def test_yaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -45,20 +43,21 @@ def test_yaxis_labels_with_axes(self): ax.set_ylim(0, 3) iplt.outline(self.cube, axes=ax, coords=("bar", "str_coord")) plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) + self.assert_points_tick_labels("yaxis", ax) def test_geoaxes_exception(self): import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) - self.assertRaises(TypeError, iplt.outline, self.lat_lon_cube, axes=ax) + pytest.raises(TypeError, iplt.outline, self.lat_lon_cube, axes=ax) plt.close(fig) -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): +@_shared_utils.skip_plot +class TestCoords(MixinCoords): + @pytest.fixture(autouse=True) + def _setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=True) coord = self.cube.coord("foo") @@ -69,9 +68,5 @@ def setUp(self): self.bar_index = np.arange(coord.points.size + 1) self.data = self.cube.data self.dataT = self.data.T - self.mpl_patch = self.patch("matplotlib.pyplot.pcolormesh") + self.mpl_patch = mocker.patch("matplotlib.pyplot.pcolormesh") self.draw_func = iplt.outline - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_pcolor.py b/lib/iris/tests/unit/plot/test_pcolor.py index 219df4d446..27f6a7b6ed 100644 --- a/lib/iris/tests/unit/plot/test_pcolor.py +++ b/lib/iris/tests/unit/plot/test_pcolor.py @@ -4,10 +4,7 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot.pcolor` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - +from iris.tests import _shared_utils from iris.tests.unit.plot import TestGraphicStringCoord from iris.tests.unit.plot._blockplot_common import ( Mixin2dCoordsContigTol, @@ -15,35 +12,28 @@ MixinStringCoordPlot, ) -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt PLOT_FUNCTION_TO_TEST = iplt.pcolor -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(MixinStringCoordPlot, TestGraphicStringCoord): def blockplot_func(self): return PLOT_FUNCTION_TO_TEST -@tests.skip_plot -class Test2dCoords(tests.IrisTest, Mixin2dCoordsPlot): - def setUp(self): - self.blockplot_setup() - +@_shared_utils.skip_plot +class Test2dCoords(Mixin2dCoordsPlot): def blockplot_func(self): return PLOT_FUNCTION_TO_TEST -@tests.skip_plot -class Test2dContigTol(tests.IrisTest, Mixin2dCoordsContigTol): +@_shared_utils.skip_plot +class Test2dContigTol(Mixin2dCoordsContigTol): # Extra call kwargs expected. additional_kwargs = dict(antialiased=True, snap=False) def blockplot_func(self): return PLOT_FUNCTION_TO_TEST - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_pcolormesh.py b/lib/iris/tests/unit/plot/test_pcolormesh.py index dba3cce5c0..9a9fa19ae0 100644 --- a/lib/iris/tests/unit/plot/test_pcolormesh.py +++ b/lib/iris/tests/unit/plot/test_pcolormesh.py @@ -8,8 +8,7 @@ # importing anything else. from typing import Any -import iris.tests as tests # isort:skip - +from iris.tests import _shared_utils from iris.tests.unit.plot import TestGraphicStringCoord from iris.tests.unit.plot._blockplot_common import ( Mixin2dCoordsContigTol, @@ -17,35 +16,28 @@ MixinStringCoordPlot, ) -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt PLOT_FUNCTION_TO_TEST = iplt.pcolormesh -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(MixinStringCoordPlot, TestGraphicStringCoord): def blockplot_func(self): return PLOT_FUNCTION_TO_TEST -@tests.skip_plot -class Test2dCoords(tests.IrisTest, Mixin2dCoordsPlot): - def setUp(self): - self.blockplot_setup() - +@_shared_utils.skip_plot +class Test2dCoords(Mixin2dCoordsPlot): def blockplot_func(self): return PLOT_FUNCTION_TO_TEST -@tests.skip_plot -class Test2dContigTol(tests.IrisTest, Mixin2dCoordsContigTol): +@_shared_utils.skip_plot +class Test2dContigTol(Mixin2dCoordsContigTol): # Extra call kwargs expected -- unlike 'pcolor', there are none. additional_kwargs: dict[str, Any] = {} def blockplot_func(self): return PLOT_FUNCTION_TO_TEST - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_plot.py b/lib/iris/tests/unit/plot/test_plot.py index 6adf1c4cf5..76225557f0 100644 --- a/lib/iris/tests/unit/plot/test_plot.py +++ b/lib/iris/tests/unit/plot/test_plot.py @@ -4,17 +4,15 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot.plot` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest import iris.coord_systems as ics import iris.coords as coords +from iris.tests import _shared_utils from iris.tests.unit.plot import TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import cartopy.crs as ccrs import cartopy.mpl.geoaxes from matplotlib.path import Path @@ -23,20 +21,22 @@ import iris.plot as iplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): - def setUp(self): - super().setUp() + parent_setup = TestGraphicStringCoord._setup + + @pytest.fixture(autouse=True) + def _setup(self, parent_setup): self.cube = self.cube[0, :] self.lat_lon_cube = self.lat_lon_cube[0, :] def test_yaxis_labels(self): iplt.plot(self.cube, self.cube.coord("str_coord")) - self.assertBoundsTickLabels("yaxis") + self.assert_bounds_tick_labels("yaxis") def test_xaxis_labels(self): iplt.plot(self.cube.coord("str_coord"), self.cube) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") def test_yaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -45,7 +45,7 @@ def test_yaxis_labels_with_axes(self): ax = fig.add_subplot(111) iplt.plot(self.cube, self.cube.coord("str_coord"), axes=ax) plt.close(fig) - self.assertBoundsTickLabels("yaxis", ax) + self.assert_bounds_tick_labels("yaxis", ax) def test_xaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -54,7 +54,7 @@ def test_xaxis_labels_with_axes(self): ax = fig.add_subplot(111) iplt.plot(self.cube.coord("str_coord"), self.cube, axes=ax) plt.close(fig) - self.assertBoundsTickLabels("xaxis", ax) + self.assert_bounds_tick_labels("xaxis", ax) def test_plot_longitude(self): import matplotlib.pyplot as plt @@ -65,14 +65,15 @@ def test_plot_longitude(self): plt.close(fig) -@tests.skip_plot -class TestTrajectoryWrap(tests.IrisTest): +@_shared_utils.skip_plot +class TestTrajectoryWrap: """Test that a line plot of geographic coordinates wraps around the end of the coordinates rather than plotting across the map. """ - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): plt.figure() self.geog_cs = ics.GeogCS(6371229.0) self.plate_carree = self.geog_cs.as_cartopy_projection() @@ -85,7 +86,7 @@ def lon_lat_coords(self, lons, lats, cs=None): coords.AuxCoord(lats, "latitude", units="degrees", coord_system=cs), ) - def assertPathsEqual(self, expected, actual): + def assert_paths_equal(self, expected, actual): """Assert that the given paths are equal once STOP vertices have been removed. @@ -95,17 +96,15 @@ def assertPathsEqual(self, expected, actual): # Remove Path.STOP vertices everts = expected.vertices[np.where(expected.codes != Path.STOP)] averts = actual.vertices[np.where(actual.codes != Path.STOP)] - self.assertArrayAlmostEqual(everts, averts) - self.assertArrayEqual(expected.codes, actual.codes) + _shared_utils.assert_array_almost_equal(everts, averts) + _shared_utils.assert_array_equal(expected.codes, actual.codes) def check_paths(self, expected_path, expected_path_crs, lines, axes): """Check that the paths in `lines` match the given expected paths when plotted on the given geoaxes. """ - self.assertEqual( - 1, len(lines), "Expected a single line, got {}".format(len(lines)) - ) + assert 1 == len(lines), "Expected a single line, got {}".format(len(lines)) (line,) = lines inter_proj_transform = cartopy.mpl.geoaxes.InterProjectionTransform( expected_path_crs, axes.projection @@ -115,7 +114,7 @@ def check_paths(self, expected_path, expected_path_crs, lines, axes): expected = ax_transform.transform_path(expected_path) actual = line.get_transform().transform_path(line.get_path()) - self.assertPathsEqual(expected, actual) + self.assert_paths_equal(expected, actual) def test_simple(self): lon, lat = self.lon_lat_coords([359, 1], [0, 0]) @@ -255,7 +254,3 @@ def test_rotated(self): grid_north_pole_longitude=120, north_pole_grid_longitude=45, ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_points.py b/lib/iris/tests/unit/plot/test_points.py index 0d713e3d84..e8778ed30e 100644 --- a/lib/iris/tests/unit/plot/test_points.py +++ b/lib/iris/tests/unit/plot/test_points.py @@ -4,28 +4,26 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot.points` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): def test_yaxis_labels(self): iplt.points(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") + self.assert_bounds_tick_labels("yaxis") def test_xaxis_labels(self): iplt.points(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") def test_xaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -35,7 +33,7 @@ def test_xaxis_labels_with_axes(self): ax.set_xlim(0, 3) iplt.points(self.cube, coords=("str_coord", "bar"), axes=ax) plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) + self.assert_points_tick_labels("xaxis", ax) def test_yaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -45,20 +43,21 @@ def test_yaxis_labels_with_axes(self): ax.set_ylim(0, 3) iplt.points(self.cube, coords=("bar", "str_coord"), axes=ax) plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) + self.assert_points_tick_labels("yaxis", ax) def test_geoaxes_exception(self): import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) - self.assertRaises(TypeError, iplt.points, self.lat_lon_cube, axes=ax) + pytest.raises(TypeError, iplt.points, self.lat_lon_cube, axes=ax) plt.close(fig) -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): +@_shared_utils.skip_plot +class TestCoords(MixinCoords): + @pytest.fixture(autouse=True) + def _setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=False) self.foo = self.cube.coord("foo").points @@ -67,9 +66,5 @@ def setUp(self): self.bar_index = np.arange(self.bar.size) self.data = None self.dataT = None - self.mpl_patch = self.patch("matplotlib.pyplot.scatter") + self.mpl_patch = mocker.patch("matplotlib.pyplot.scatter") self.draw_func = iplt.points - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_scatter.py b/lib/iris/tests/unit/plot/test_scatter.py index 21412010ab..9a0a7db96c 100644 --- a/lib/iris/tests/unit/plot/test_scatter.py +++ b/lib/iris/tests/unit/plot/test_scatter.py @@ -4,29 +4,31 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.plot.scatter` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip +import pytest + +from iris.tests import _shared_utils from iris.tests.unit.plot import TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.plot as iplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): - def setUp(self): - super().setUp() + parent_setup = TestGraphicStringCoord._setup + + @pytest.fixture(autouse=True) + def _setup(self, parent_setup): self.cube = self.cube[0, :] self.lat_lon_cube = self.lat_lon_cube[0, :] def test_xaxis_labels(self): iplt.scatter(self.cube.coord("str_coord"), self.cube) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") def test_yaxis_labels(self): iplt.scatter(self.cube, self.cube.coord("str_coord")) - self.assertBoundsTickLabels("yaxis") + self.assert_bounds_tick_labels("yaxis") def test_xaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -36,7 +38,7 @@ def test_xaxis_labels_with_axes(self): ax.set_xlim(0, 3) iplt.scatter(self.cube.coord("str_coord"), self.cube, axes=ax) plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) + self.assert_points_tick_labels("xaxis", ax) def test_yaxis_labels_with_axes(self): import matplotlib.pyplot as plt @@ -46,7 +48,7 @@ def test_yaxis_labels_with_axes(self): ax.set_ylim(0, 3) iplt.scatter(self.cube, self.cube.coord("str_coord"), axes=ax) plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) + self.assert_points_tick_labels("yaxis", ax) def test_scatter_longitude(self): import matplotlib.pyplot as plt @@ -55,7 +57,3 @@ def test_scatter_longitude(self): ax = fig.add_subplot(111) iplt.scatter(self.lat_lon_cube, self.lat_lon_cube.coord("longitude"), axes=ax) plt.close(fig) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_contour.py b/lib/iris/tests/unit/quickplot/test_contour.py index 2f3bb1a45d..8d5fe7a01c 100644 --- a/lib/iris/tests/unit/quickplot/test_contour.py +++ b/lib/iris/tests/unit/quickplot/test_contour.py @@ -4,33 +4,32 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.quickplot.contour` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.quickplot as qplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): def test_yaxis_labels(self): qplt.contour(self.cube, coords=("bar", "str_coord")) - self.assertPointsTickLabels("yaxis") + self.assert_points_tick_labels("yaxis") def test_xaxis_labels(self): qplt.contour(self.cube, coords=("str_coord", "bar")) - self.assertPointsTickLabels("xaxis") + self.assert_points_tick_labels("xaxis") -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): +@_shared_utils.skip_plot +class TestCoords(MixinCoords): + @pytest.fixture(autouse=True) + def _setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=False) self.foo = self.cube.coord("foo").points @@ -39,9 +38,5 @@ def setUp(self): self.bar_index = np.arange(self.bar.size) self.data = self.cube.data self.dataT = self.data.T - self.mpl_patch = self.patch("matplotlib.pyplot.contour") + self.mpl_patch = mocker.patch("matplotlib.pyplot.contour") self.draw_func = qplt.contour - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_contourf.py b/lib/iris/tests/unit/quickplot/test_contourf.py index 55c9940821..91deb3b79e 100644 --- a/lib/iris/tests/unit/quickplot/test_contourf.py +++ b/lib/iris/tests/unit/quickplot/test_contourf.py @@ -4,36 +4,32 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.quickplot.contourf` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import matplotlib.pyplot as plt import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.quickplot as qplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): def test_yaxis_labels(self): qplt.contourf(self.cube, coords=("bar", "str_coord")) - self.assertPointsTickLabels("yaxis") + self.assert_points_tick_labels("yaxis") def test_xaxis_labels(self): qplt.contourf(self.cube, coords=("str_coord", "bar")) - self.assertPointsTickLabels("xaxis") + self.assert_points_tick_labels("xaxis") -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): +@_shared_utils.skip_plot +class TestCoords(MixinCoords): + @pytest.fixture(autouse=True) + def _setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=False) self.foo = self.cube.coord("foo").points @@ -42,12 +38,7 @@ def setUp(self): self.bar_index = np.arange(self.bar.size) self.data = self.cube.data self.dataT = self.data.T - mocker = mock.Mock(wraps=plt.contourf) - self.mpl_patch = self.patch("matplotlib.pyplot.contourf", mocker) + self.mpl_patch = mocker.patch("matplotlib.pyplot.contourf") # Also need to mock the colorbar. - self.patch("matplotlib.pyplot.colorbar") + mocker.patch("matplotlib.pyplot.colorbar") self.draw_func = qplt.contourf - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_outline.py b/lib/iris/tests/unit/quickplot/test_outline.py index 4dd924b749..1d83561f9d 100644 --- a/lib/iris/tests/unit/quickplot/test_outline.py +++ b/lib/iris/tests/unit/quickplot/test_outline.py @@ -4,33 +4,32 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.quickplot.outline` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.quickplot as qplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): def test_yaxis_labels(self): qplt.outline(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") + self.assert_bounds_tick_labels("yaxis") def test_xaxis_labels(self): qplt.outline(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): +@_shared_utils.skip_plot +class TestCoords(MixinCoords): + @pytest.fixture(autouse=True) + def _setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=True) coord = self.cube.coord("foo") @@ -41,9 +40,5 @@ def setUp(self): self.bar_index = np.arange(coord.points.size + 1) self.data = self.cube.data self.dataT = self.data.T - self.mpl_patch = self.patch("matplotlib.pyplot.pcolormesh") + self.mpl_patch = mocker.patch("matplotlib.pyplot.pcolormesh") self.draw_func = qplt.outline - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_pcolor.py b/lib/iris/tests/unit/quickplot/test_pcolor.py index fc2ce83f0b..87d9b73530 100644 --- a/lib/iris/tests/unit/quickplot/test_pcolor.py +++ b/lib/iris/tests/unit/quickplot/test_pcolor.py @@ -4,33 +4,32 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.quickplot.pcolor` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.quickplot as qplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): def test_yaxis_labels(self): qplt.pcolor(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") + self.assert_bounds_tick_labels("yaxis") def test_xaxis_labels(self): qplt.pcolor(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): +@_shared_utils.skip_plot +class TestCoords(MixinCoords): + @pytest.fixture(autouse=True) + def _setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=True) coord = self.cube.coord("foo") @@ -41,9 +40,5 @@ def setUp(self): self.bar_index = np.arange(coord.points.size + 1) self.data = self.cube.data self.dataT = self.data.T - self.mpl_patch = self.patch("matplotlib.pyplot.pcolor", return_value=None) + self.mpl_patch = mocker.patch("matplotlib.pyplot.pcolor", return_value=None) self.draw_func = qplt.pcolor - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_pcolormesh.py b/lib/iris/tests/unit/quickplot/test_pcolormesh.py index 6ce9d07406..b9476c0c16 100644 --- a/lib/iris/tests/unit/quickplot/test_pcolormesh.py +++ b/lib/iris/tests/unit/quickplot/test_pcolormesh.py @@ -4,33 +4,32 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.quickplot.pcolormesh` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.quickplot as qplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): def test_yaxis_labels(self): qplt.pcolormesh(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") + self.assert_bounds_tick_labels("yaxis") def test_xaxis_labels(self): qplt.pcolormesh(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): +@_shared_utils.skip_plot +class TestCoords(MixinCoords): + @pytest.fixture(autouse=True) + def _setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=True) coord = self.cube.coord("foo") @@ -41,9 +40,5 @@ def setUp(self): self.bar_index = np.arange(coord.points.size + 1) self.data = self.cube.data self.dataT = self.data.T - self.mpl_patch = self.patch("matplotlib.pyplot.pcolormesh", return_value=None) + self.mpl_patch = mocker.patch("matplotlib.pyplot.pcolormesh", return_value=None) self.draw_func = qplt.pcolormesh - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_plot.py b/lib/iris/tests/unit/quickplot/test_plot.py index 35e1eae470..6f4c71a4f1 100644 --- a/lib/iris/tests/unit/quickplot/test_plot.py +++ b/lib/iris/tests/unit/quickplot/test_plot.py @@ -4,40 +4,42 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.quickplot.plot` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip +import pytest + +from iris.tests import _shared_utils from iris.tests.stock import simple_1d from iris.tests.unit.plot import TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.quickplot as qplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): - def setUp(self): - super().setUp() + parent_setup = TestGraphicStringCoord._setup + + @pytest.fixture(autouse=True) + def _setup(self, parent_setup): self.cube = self.cube[0, :] def test_yaxis_labels(self): qplt.plot(self.cube, self.cube.coord("str_coord")) - self.assertBoundsTickLabels("yaxis") + self.assert_bounds_tick_labels("yaxis") def test_xaxis_labels(self): qplt.plot(self.cube.coord("str_coord"), self.cube) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") -class TestAxisLabels(tests.GraphicsTest): +class TestAxisLabels(_shared_utils.GraphicsTest): def test_xy_cube(self): c = simple_1d() qplt.plot(c) ax = qplt.plt.gca() x = ax.xaxis.get_label().get_text() - self.assertEqual(x, "Foo") + assert x == "Foo" y = ax.yaxis.get_label().get_text() - self.assertEqual(y, "Thingness") + assert y == "Thingness" def test_yx_cube(self): c = simple_1d() @@ -48,10 +50,6 @@ def test_yx_cube(self): qplt.plot(c) ax = qplt.plt.gca() x = ax.xaxis.get_label().get_text() - self.assertEqual(x, "Thingness") + assert x == "Thingness" y = ax.yaxis.get_label().get_text() - self.assertEqual(y, "Foo") - - -if __name__ == "__main__": - tests.main() + assert y == "Foo" diff --git a/lib/iris/tests/unit/quickplot/test_points.py b/lib/iris/tests/unit/quickplot/test_points.py index b28c37bf87..d55b3daadf 100644 --- a/lib/iris/tests/unit/quickplot/test_points.py +++ b/lib/iris/tests/unit/quickplot/test_points.py @@ -4,33 +4,32 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.quickplot.points` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import simple_2d from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.quickplot as qplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): def test_yaxis_labels(self): qplt.points(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") + self.assert_bounds_tick_labels("yaxis") def test_xaxis_labels(self): qplt.points(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): +@_shared_utils.skip_plot +class TestCoords(MixinCoords): + @pytest.fixture(autouse=True) + def _setup(self, mocker): # We have a 2d cube with dimensionality (bar: 3; foo: 4) self.cube = simple_2d(with_bounds=False) self.foo = self.cube.coord("foo").points @@ -39,9 +38,5 @@ def setUp(self): self.bar_index = np.arange(self.bar.size) self.data = None self.dataT = None - self.mpl_patch = self.patch("matplotlib.pyplot.scatter") + self.mpl_patch = mocker.patch("matplotlib.pyplot.scatter") self.draw_func = qplt.points - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_scatter.py b/lib/iris/tests/unit/quickplot/test_scatter.py index db3e9948a0..e6c7177860 100644 --- a/lib/iris/tests/unit/quickplot/test_scatter.py +++ b/lib/iris/tests/unit/quickplot/test_scatter.py @@ -4,29 +4,27 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.quickplot.scatter` function.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip +import pytest + +from iris.tests import _shared_utils from iris.tests.unit.plot import TestGraphicStringCoord -if tests.MPL_AVAILABLE: +if _shared_utils.MPL_AVAILABLE: import iris.quickplot as qplt -@tests.skip_plot +@_shared_utils.skip_plot class TestStringCoordPlot(TestGraphicStringCoord): - def setUp(self): - super().setUp() + parent_setup = TestGraphicStringCoord._setup + + @pytest.fixture(autouse=True) + def _setup(self, parent_setup): self.cube = self.cube[0, :] def test_xaxis_labels(self): qplt.scatter(self.cube.coord("str_coord"), self.cube) - self.assertBoundsTickLabels("xaxis") + self.assert_bounds_tick_labels("xaxis") def test_yaxis_labels(self): qplt.scatter(self.cube, self.cube.coord("str_coord")) - self.assertBoundsTickLabels("yaxis") - - -if __name__ == "__main__": - tests.main() + self.assert_bounds_tick_labels("yaxis") diff --git a/lib/iris/tests/unit/util/test__coord_regular.py b/lib/iris/tests/unit/util/test__coord_regular.py index a772833972..cbfe5074e6 100644 --- a/lib/iris/tests/unit/util/test__coord_regular.py +++ b/lib/iris/tests/unit/util/test__coord_regular.py @@ -12,99 +12,92 @@ """ -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import numpy as np +import pytest from iris.coords import AuxCoord, DimCoord from iris.exceptions import CoordinateMultiDimError, CoordinateNotRegularError from iris.util import is_regular, points_step, regular_step -class Test_is_regular(tests.IrisTest): +class Test_is_regular: def test_coord_with_regular_step(self): coord = DimCoord(np.arange(5)) result = is_regular(coord) - self.assertTrue(result) + assert result def test_coord_with_irregular_step(self): # Check that a `CoordinateNotRegularError` is captured. coord = AuxCoord(np.array([2, 5, 1, 4])) result = is_regular(coord) - self.assertFalse(result) + assert not result def test_scalar_coord(self): # Check that a `ValueError` is captured. coord = DimCoord(5) result = is_regular(coord) - self.assertFalse(result) + assert not result def test_coord_with_string_points(self): # Check that a `TypeError` is captured. coord = AuxCoord(["a", "b", "c"]) result = is_regular(coord) - self.assertFalse(result) + assert not result -class Test_regular_step(tests.IrisTest): +class Test_regular_step: def test_basic(self): dtype = np.float64 points = np.arange(5, dtype=dtype) coord = DimCoord(points) expected = np.mean(np.diff(points)) result = regular_step(coord) - self.assertEqual(expected, result) - self.assertEqual(result.dtype, dtype) + assert expected == result + assert result.dtype == dtype def test_2d_coord(self): coord = AuxCoord(np.arange(8).reshape(2, 4)) exp_emsg = "Expected 1D coord" - with self.assertRaisesRegex(CoordinateMultiDimError, exp_emsg): + with pytest.raises(CoordinateMultiDimError, match=exp_emsg): regular_step(coord) def test_scalar_coord(self): coord = DimCoord(5) exp_emsg = "non-scalar coord" - with self.assertRaisesRegex(ValueError, exp_emsg): + with pytest.raises(ValueError, match=exp_emsg): regular_step(coord) def test_coord_with_irregular_step(self): name = "latitude" coord = AuxCoord(np.array([2, 5, 1, 4]), standard_name=name) exp_emsg = "{} is not regular".format(name) - with self.assertRaisesRegex(CoordinateNotRegularError, exp_emsg): + with pytest.raises(CoordinateNotRegularError, match=exp_emsg): regular_step(coord) -class Test_points_step(tests.IrisTest): +class Test_points_step: def test_regular_points(self): regular_points = np.arange(5) exp_avdiff = np.mean(np.diff(regular_points)) result_avdiff, result = points_step(regular_points) - self.assertEqual(exp_avdiff, result_avdiff) - self.assertTrue(result) + assert exp_avdiff == result_avdiff + assert result def test_irregular_points(self): irregular_points = np.array([2, 5, 1, 4]) exp_avdiff = np.mean(np.diff(irregular_points)) result_avdiff, result = points_step(irregular_points) - self.assertEqual(exp_avdiff, result_avdiff) - self.assertFalse(result) + assert exp_avdiff == result_avdiff + assert not result def test_single_point(self): lone_point = np.array([4]) result_avdiff, result = points_step(lone_point) - self.assertTrue(np.isnan(result_avdiff)) - self.assertTrue(result) + assert np.isnan(result_avdiff) + assert result def test_no_points(self): no_points = np.array([]) result_avdiff, result = points_step(no_points) - self.assertTrue(np.isnan(result_avdiff)) - self.assertTrue(result) - - -if __name__ == "__main__": - tests.main() + assert np.isnan(result_avdiff) + assert result diff --git a/lib/iris/tests/unit/util/test__is_circular.py b/lib/iris/tests/unit/util/test__is_circular.py index 67099f49d6..8dc7d6f2a4 100644 --- a/lib/iris/tests/unit/util/test__is_circular.py +++ b/lib/iris/tests/unit/util/test__is_circular.py @@ -4,24 +4,16 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util._is_circular`.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import numpy as np from iris.util import _is_circular -class Test(tests.IrisTest): +class Test: def test_simple(self): data = np.arange(12) * 30 - self.assertTrue(_is_circular(data, 360)) + assert _is_circular(data, 360) def test_negative_diff(self): data = (np.arange(96) * -3.749998) + 3.56249908e02 - self.assertTrue(_is_circular(data, 360)) - - -if __name__ == "__main__": - tests.main() + assert _is_circular(data, 360) diff --git a/lib/iris/tests/unit/util/test__mask_array.py b/lib/iris/tests/unit/util/test__mask_array.py index 355730c166..990c17f3be 100644 --- a/lib/iris/tests/unit/util/test__mask_array.py +++ b/lib/iris/tests/unit/util/test__mask_array.py @@ -34,7 +34,7 @@ ) @pytest.mark.parametrize("lazy_mask", [False, True], ids=["real", "lazy"]) @pytest.mark.parametrize( - "array, expected", array_choices, ids=["plain-array", "masked-array"] + ("array", "expected"), array_choices, ids=["plain-array", "masked-array"] ) @pytest.mark.parametrize("lazy_array", [False, True], ids=["real", "lazy"]) def test_1d_not_in_place(array, mask, expected, lazy_array, lazy_mask): diff --git a/lib/iris/tests/unit/util/test__slice_data_with_keys.py b/lib/iris/tests/unit/util/test__slice_data_with_keys.py index eda4f91055..fd692e2076 100644 --- a/lib/iris/tests/unit/util/test__slice_data_with_keys.py +++ b/lib/iris/tests/unit/util/test__slice_data_with_keys.py @@ -12,13 +12,11 @@ """ -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import numpy as np +import pytest from iris._lazy_data import as_concrete_data, as_lazy_data +from iris.tests import _shared_utils from iris.util import _slice_data_with_keys @@ -77,15 +75,12 @@ def showkeys(keys_list): msg += "\n]" return msg - self.assertTrue( - equal, - errmsg.format(showkeys(calls_got), showkeys(expect_call_keys)), - ) + assert equal, errmsg.format(showkeys(calls_got), showkeys(expect_call_keys)) if expect_map is not None: - self.assertEqual(dim_map, expect_map) + assert dim_map == expect_map -class Test_indexing(MixinIndexingTest, tests.IrisTest): +class Test_indexing(MixinIndexingTest): # Check the indexing operations performed for various requested keys. def test_0d_nokeys(self): @@ -105,12 +100,12 @@ def test_1d_tuple(self): def test_fail_1d_2keys(self): msg = "More slices .* than dimensions" - with self.assertRaisesRegex(IndexError, msg): + with pytest.raises(IndexError, match=msg): self.check((3,), Index[1, 2]) def test_fail_empty_slice(self): msg = "Cannot index with zero length slice" - with self.assertRaisesRegex(IndexError, msg): + with pytest.raises(IndexError, match=msg): self.check((3,), Index[1:1]) def test_2d_tuple(self): @@ -192,7 +187,7 @@ def test_3d_multiple_tuples(self): # That's just what it does at present. -class Test_dimensions_mapping(MixinIndexingTest, tests.IrisTest): +class Test_dimensions_mapping(MixinIndexingTest): # Check the dimensions map returned for various requested keys. def test_1d_nochange(self): @@ -236,7 +231,7 @@ def test_3d_losedim1(self): ) -class TestResults(tests.IrisTest): +class TestResults: # Integration-style test, exercising (mostly) the same cases as above, # but checking actual results, for both real and lazy array inputs. @@ -246,10 +241,10 @@ def check(self, real_data, keys, expect_result, expect_map): real_dim_map, real_result = _slice_data_with_keys(real_data, keys) lazy_dim_map, lazy_result = _slice_data_with_keys(lazy_data, keys) lazy_result = as_concrete_data(lazy_result) - self.assertArrayEqual(real_result, expect_result) - self.assertArrayEqual(lazy_result, expect_result) - self.assertEqual(real_dim_map, expect_map) - self.assertEqual(lazy_dim_map, expect_map) + _shared_utils.assert_array_equal(real_result, expect_result) + _shared_utils.assert_array_equal(lazy_result, expect_result) + assert real_dim_map == expect_map + assert lazy_dim_map == expect_map def test_1d_int(self): self.check([1, 2, 3, 4], Index[2], [3], {None: None, 0: None}) @@ -262,12 +257,12 @@ def test_1d_tuple(self): def test_fail_1d_2keys(self): msg = "More slices .* than dimensions" - with self.assertRaisesRegex(IndexError, msg): + with pytest.raises(IndexError, match=msg): self.check([1, 2, 3], Index[1, 2], None, None) def test_fail_empty_slice(self): msg = "Cannot index with zero length slice" - with self.assertRaisesRegex(IndexError, msg): + with pytest.raises(IndexError, match=msg): self.check([1, 2, 3], Index[1:1], None, None) def test_2d_tuple(self): @@ -418,7 +413,3 @@ def test_3d_multiple_tuples(self): ) # NOTE: there seem to be an extra initial [:, :, :]. # That's just what it does at present. - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_array_equal.py b/lib/iris/tests/unit/util/test_array_equal.py index d463ca6a4f..3e1aaf1bfb 100644 --- a/lib/iris/tests/unit/util/test_array_equal.py +++ b/lib/iris/tests/unit/util/test_array_equal.py @@ -4,141 +4,133 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.array_equal`.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import numpy as np import numpy.ma as ma from iris.util import array_equal -class Test(tests.IrisTest): +class Test: def test_0d(self): array_a = np.array(23) array_b = np.array(23) array_c = np.array(7) - self.assertTrue(array_equal(array_a, array_b)) - self.assertFalse(array_equal(array_a, array_c)) + assert array_equal(array_a, array_b) + assert not array_equal(array_a, array_c) def test_0d_and_scalar(self): array_a = np.array(23) - self.assertTrue(array_equal(array_a, 23)) - self.assertFalse(array_equal(array_a, 45)) + assert array_equal(array_a, 23) + assert not array_equal(array_a, 45) def test_1d_and_sequences(self): for sequence_type in (list, tuple): seq_a = sequence_type([1, 2, 3]) array_a = np.array(seq_a) - self.assertTrue(array_equal(array_a, seq_a)) - self.assertFalse(array_equal(array_a, seq_a[:-1])) + assert array_equal(array_a, seq_a) + assert not array_equal(array_a, seq_a[:-1]) array_a[1] = 45 - self.assertFalse(array_equal(array_a, seq_a)) + assert not array_equal(array_a, seq_a) def test_nd(self): array_a = np.array(np.arange(24).reshape(2, 3, 4)) array_b = np.array(np.arange(24).reshape(2, 3, 4)) array_c = np.array(np.arange(24).reshape(2, 3, 4)) array_c[0, 1, 2] = 100 - self.assertTrue(array_equal(array_a, array_b)) - self.assertFalse(array_equal(array_a, array_c)) + assert array_equal(array_a, array_b) + assert not array_equal(array_a, array_c) def test_masked_is_not_ignored(self): array_a = ma.masked_array([1, 2, 3], mask=[1, 0, 1]) array_b = ma.masked_array([2, 2, 2], mask=[1, 0, 1]) - self.assertTrue(array_equal(array_a, array_b)) + assert array_equal(array_a, array_b) def test_masked_is_different(self): array_a = ma.masked_array([1, 2, 3], mask=[1, 0, 1]) array_b = ma.masked_array([1, 2, 3], mask=[0, 0, 1]) - self.assertFalse(array_equal(array_a, array_b)) + assert not array_equal(array_a, array_b) def test_masked_isnt_unmasked(self): array_a = np.array([1, 2, 2]) array_b = ma.masked_array([1, 2, 2], mask=[0, 0, 1]) - self.assertFalse(array_equal(array_a, array_b)) + assert not array_equal(array_a, array_b) def test_masked_unmasked_equivelance(self): array_a = np.array([1, 2, 2]) array_b = ma.masked_array([1, 2, 2]) array_c = ma.masked_array([1, 2, 2], mask=[0, 0, 0]) - self.assertTrue(array_equal(array_a, array_b)) - self.assertTrue(array_equal(array_a, array_c)) + assert array_equal(array_a, array_b) + assert array_equal(array_a, array_c) def test_fully_masked_arrays(self): array_a = ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True) array_b = ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True) - self.assertTrue(array_equal(array_a, array_b)) + assert array_equal(array_a, array_b) def test_fully_masked_0d_arrays(self): array_a = ma.masked_array(3, mask=True) array_b = ma.masked_array(3, mask=True) - self.assertTrue(array_equal(array_a, array_b)) + assert array_equal(array_a, array_b) def test_fully_masked_string_arrays(self): array_a = ma.masked_array(["a", "b", "c"], mask=True) array_b = ma.masked_array(["a", "b", "c"], mask=[1, 1, 1]) - self.assertTrue(array_equal(array_a, array_b)) + assert array_equal(array_a, array_b) def test_partially_masked_string_arrays(self): array_a = ma.masked_array(["a", "b", "c"], mask=[1, 0, 1]) array_b = ma.masked_array(["a", "b", "c"], mask=[1, 0, 1]) - self.assertTrue(array_equal(array_a, array_b)) + assert array_equal(array_a, array_b) def test_string_arrays_equal(self): array_a = np.array(["abc", "def", "efg"]) array_b = np.array(["abc", "def", "efg"]) - self.assertTrue(array_equal(array_a, array_b)) + assert array_equal(array_a, array_b) def test_string_arrays_different_contents(self): array_a = np.array(["abc", "def", "efg"]) array_b = np.array(["abc", "de", "efg"]) - self.assertFalse(array_equal(array_a, array_b)) + assert not array_equal(array_a, array_b) def test_string_arrays_subset(self): array_a = np.array(["abc", "def", "efg"]) array_b = np.array(["abc", "def"]) - self.assertFalse(array_equal(array_a, array_b)) - self.assertFalse(array_equal(array_b, array_a)) + assert not array_equal(array_a, array_b) + assert not array_equal(array_b, array_a) def test_string_arrays_unequal_dimensionality(self): array_a = np.array("abc") array_b = np.array(["abc"]) array_c = np.array([["abc"]]) - self.assertFalse(array_equal(array_a, array_b)) - self.assertFalse(array_equal(array_b, array_a)) - self.assertFalse(array_equal(array_a, array_c)) - self.assertFalse(array_equal(array_b, array_c)) + assert not array_equal(array_a, array_b) + assert not array_equal(array_b, array_a) + assert not array_equal(array_a, array_c) + assert not array_equal(array_b, array_c) def test_string_arrays_0d_and_scalar(self): array_a = np.array("foobar") - self.assertTrue(array_equal(array_a, "foobar")) - self.assertFalse(array_equal(array_a, "foo")) - self.assertFalse(array_equal(array_a, "foobar.")) + assert array_equal(array_a, "foobar") + assert not array_equal(array_a, "foo") + assert not array_equal(array_a, "foobar.") def test_nan_equality_nan_ne_nan(self): array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0]) array_b = array_a.copy() - self.assertFalse(array_equal(array_a, array_a)) - self.assertFalse(array_equal(array_a, array_b)) + assert not array_equal(array_a, array_a) + assert not array_equal(array_a, array_b) def test_nan_equality_nan_naneq_nan(self): array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0]) array_b = np.array([1.0, np.nan, 2.0, np.nan, 3.0]) - self.assertTrue(array_equal(array_a, array_a, withnans=True)) - self.assertTrue(array_equal(array_a, array_b, withnans=True)) + assert array_equal(array_a, array_a, withnans=True) + assert array_equal(array_a, array_b, withnans=True) def test_nan_equality_nan_nanne_a(self): array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0]) array_b = np.array([1.0, np.nan, 2.0, 0.0, 3.0]) - self.assertFalse(array_equal(array_a, array_b, withnans=True)) + assert not array_equal(array_a, array_b, withnans=True) def test_nan_equality_a_nanne_b(self): array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0]) array_b = np.array([1.0, np.nan, 2.0, np.nan, 4.0]) - self.assertFalse(array_equal(array_a, array_b, withnans=True)) - - -if __name__ == "__main__": - tests.main() + assert not array_equal(array_a, array_b, withnans=True) diff --git a/lib/iris/tests/unit/util/test_broadcast_to_shape.py b/lib/iris/tests/unit/util/test_broadcast_to_shape.py index 183f6f8d93..75dd2c6f97 100644 --- a/lib/iris/tests/unit/util/test_broadcast_to_shape.py +++ b/lib/iris/tests/unit/util/test_broadcast_to_shape.py @@ -4,10 +4,6 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.broadcast_to_shape`.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - from unittest import mock import dask @@ -15,17 +11,18 @@ import numpy as np import numpy.ma as ma +from iris.tests import _shared_utils from iris.util import broadcast_to_shape -class Test_broadcast_to_shape(tests.IrisTest): +class Test_broadcast_to_shape: def test_same_shape(self): # broadcast to current shape should result in no change rng = np.random.default_rng() a = rng.random((2, 3)) b = broadcast_to_shape(a, a.shape, (0, 1)) - self.assertArrayEqual(b, a) + _shared_utils.assert_array_equal(b, a) def test_added_dimensions(self): # adding two dimensions, on at the front and one in the middle of @@ -35,7 +32,7 @@ def test_added_dimensions(self): b = broadcast_to_shape(a, (5, 2, 4, 3), (1, 3)) for i in range(5): for j in range(4): - self.assertArrayEqual(b[i, :, j, :], a) + _shared_utils.assert_array_equal(b[i, :, j, :], a) def test_added_dimensions_transpose(self): # adding dimensions and having the dimensions of the input @@ -45,7 +42,7 @@ def test_added_dimensions_transpose(self): b = broadcast_to_shape(a, (5, 3, 4, 2), (3, 1)) for i in range(5): for j in range(4): - self.assertArrayEqual(b[i, :, j, :].T, a) + _shared_utils.assert_array_equal(b[i, :, j, :].T, a) @mock.patch.object(dask.base, "compute", wraps=dask.base.compute) def test_lazy_added_dimensions_transpose(self, mocked_compute): @@ -57,7 +54,7 @@ def test_lazy_added_dimensions_transpose(self, mocked_compute): mocked_compute.assert_not_called() for i in range(5): for j in range(4): - self.assertArrayEqual(b[i, :, j, :].T.compute(), a.compute()) + _shared_utils.assert_array_equal(b[i, :, j, :].T.compute(), a.compute()) def test_masked(self): # masked arrays are also accepted @@ -67,7 +64,7 @@ def test_masked(self): b = broadcast_to_shape(m, (5, 3, 4, 2), (3, 1)) for i in range(5): for j in range(4): - self.assertMaskedArrayEqual(b[i, :, j, :].T, m) + _shared_utils.assert_masked_array_equal(b[i, :, j, :].T, m) @mock.patch.object(dask.base, "compute", wraps=dask.base.compute) def test_lazy_masked(self, mocked_compute): @@ -79,7 +76,9 @@ def test_lazy_masked(self, mocked_compute): mocked_compute.assert_not_called() for i in range(5): for j in range(4): - self.assertMaskedArrayEqual(b[i, :, j, :].compute().T, m.compute()) + _shared_utils.assert_masked_array_equal( + b[i, :, j, :].compute().T, m.compute() + ) @mock.patch.object(dask.base, "compute", wraps=dask.base.compute) def test_lazy_chunks(self, mocked_compute): @@ -103,7 +102,9 @@ def test_lazy_chunks(self, mocked_compute): mocked_compute.assert_not_called() for i in range(3): for j in range(4): - self.assertMaskedArrayEqual(b[i, j, :].compute(), m[0].compute()) + _shared_utils.assert_masked_array_equal( + b[i, j, :].compute(), m[0].compute() + ) assert b.chunks == ((1, 1, 1), (2, 2), (2, 2, 1)) def test_masked_degenerate(self): @@ -114,8 +115,4 @@ def test_masked_degenerate(self): b = broadcast_to_shape(m, (5, 3, 4, 2), (3, 1)) for i in range(5): for j in range(4): - self.assertMaskedArrayEqual(b[i, :, j, :].T, m) - - -if __name__ == "__main__": - tests.main() + _shared_utils.assert_masked_array_equal(b[i, :, j, :].T, m) diff --git a/lib/iris/tests/unit/util/test_column_slices_generator.py b/lib/iris/tests/unit/util/test_column_slices_generator.py index fbb5a8f588..3dae8f72b5 100644 --- a/lib/iris/tests/unit/util/test_column_slices_generator.py +++ b/lib/iris/tests/unit/util/test_column_slices_generator.py @@ -4,32 +4,17 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.column_slices_generator`.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import numpy as np +import pytest from iris.util import column_slices_generator -class Test_int_types(tests.IrisTest): - def _test(self, key): +class Test_int_types: + @pytest.mark.parametrize("key", [0, np.int32(0), np.int64(0)]) + def test(self, key): full_slice = (key,) ndims = 1 mapping, iterable = column_slices_generator(full_slice, ndims) - self.assertEqual(mapping, {0: None, None: None}) - self.assertEqual(list(iterable), [(0,)]) - - def test_int(self): - self._test(0) - - def test_int_32(self): - self._test(np.int32(0)) - - def test_int_64(self): - self._test(np.int64(0)) - - -if __name__ == "__main__": - tests.main() + assert mapping == {0: None, None: None} + assert list(iterable) == [(0,)] diff --git a/lib/iris/tests/unit/util/test_demote_dim_coord_to_aux_coord.py b/lib/iris/tests/unit/util/test_demote_dim_coord_to_aux_coord.py index 65e3dec93b..2e06a75fc7 100644 --- a/lib/iris/tests/unit/util/test_demote_dim_coord_to_aux_coord.py +++ b/lib/iris/tests/unit/util/test_demote_dim_coord_to_aux_coord.py @@ -4,51 +4,47 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.demote_dim_coord_to_aux_coord`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import unittest +import pytest + import iris +from iris.tests import _shared_utils import iris.tests.stock as stock from iris.util import demote_dim_coord_to_aux_coord -class Test(tests.IrisTest): +class Test: def test_argument_is_basestring(self): cube_a = stock.simple_3d() cube_b = cube_a.copy() demote_dim_coord_to_aux_coord(cube_b, cube_b.coord("wibble")) - self.assertEqual( - cube_b.dim_coords, - (cube_a.coord("latitude"), cube_a.coord("longitude")), + assert cube_b.dim_coords == ( + cube_a.coord("latitude"), + cube_a.coord("longitude"), ) - @tests.skip_data + @_shared_utils.skip_data def test_argument_is_coord_instance(self): cube_a = stock.realistic_4d() cube_b = cube_a.copy() coord = cube_b.coord("model_level_number").copy() demote_dim_coord_to_aux_coord(cube_b, coord) - self.assertEqual( - cube_b.dim_coords, - ( - cube_a.coord("time"), - cube_a.coord("grid_latitude"), - cube_a.coord("grid_longitude"), - ), + assert cube_b.dim_coords == ( + cube_a.coord("time"), + cube_a.coord("grid_latitude"), + cube_a.coord("grid_longitude"), ) def test_old_dim_coord_is_now_aux_coord(self): cube_a = stock.hybrid_height() cube_b = cube_a.copy() demote_dim_coord_to_aux_coord(cube_b, "level_height") - self.assertTrue(cube_a.coord("level_height") in cube_b.aux_coords) + assert cube_a.coord("level_height") in cube_b.aux_coords def test_coord_of_that_name_does_not_exist(self): cube_a = stock.simple_2d_w_multidim_and_scalars() - with self.assertRaises(iris.exceptions.CoordinateNotFoundError): + with pytest.raises(iris.exceptions.CoordinateNotFoundError): demote_dim_coord_to_aux_coord(cube_a, "wibble") def test_coord_does_not_exist(self): @@ -57,18 +53,18 @@ def test_coord_does_not_exist(self): coord = cube_b.coord("dim1").copy() coord.rename("new") demote_dim_coord_to_aux_coord(cube_b, coord) - self.assertEqual(cube_a, cube_b) + assert cube_a == cube_b def test_argument_is_wrong_type(self): cube_a = stock.simple_1d() - with self.assertRaises(TypeError): + with pytest.raises(TypeError): demote_dim_coord_to_aux_coord(cube_a, 0.0) def test_trying_to_demote_a_scalar_coord(self): cube_a = stock.simple_2d_w_multidim_and_scalars() cube_b = cube_a.copy() demote_dim_coord_to_aux_coord(cube_b, "an_other") - self.assertEqual(cube_a, cube_b) + assert cube_a == cube_b if __name__ == "__main__": diff --git a/lib/iris/tests/unit/util/test_describe_diff.py b/lib/iris/tests/unit/util/test_describe_diff.py index 74bd71389e..0263a0de27 100644 --- a/lib/iris/tests/unit/util/test_describe_diff.py +++ b/lib/iris/tests/unit/util/test_describe_diff.py @@ -4,20 +4,20 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.describe_diff`.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - from io import StringIO import numpy as np +import pytest import iris.cube +from iris.tests import _shared_utils from iris.util import describe_diff -class Test(iris.tests.IrisTest): - def setUp(self): +class Test: + @pytest.fixture(autouse=True) + def _setup(self, request): + self.request = request self.cube_a = iris.cube.Cube([]) self.cube_b = self.cube_a.copy() @@ -30,21 +30,26 @@ def test_noncommon_array_attributes(self): # test non-common array attribute self.cube_a.attributes["test_array"] = np.array([1, 2, 3]) return_str = self._compare_result(self.cube_a, self.cube_b) - self.assertString(return_str, ["compatible_cubes.str.txt"]) + _shared_utils.assert_string( + self.request, return_str, ["compatible_cubes.str.txt"] + ) def test_same_array_attributes(self): # test matching array attribute self.cube_a.attributes["test_array"] = np.array([1, 2, 3]) self.cube_b.attributes["test_array"] = np.array([1, 2, 3]) return_str = self._compare_result(self.cube_a, self.cube_b) - self.assertString(return_str, ["compatible_cubes.str.txt"]) + _shared_utils.assert_string( + self.request, return_str, ["compatible_cubes.str.txt"] + ) def test_different_array_attributes(self): # test non-matching array attribute self.cube_a.attributes["test_array"] = np.array([1, 2, 3]) self.cube_b.attributes["test_array"] = np.array([1, 7, 3]) return_str = self._compare_result(self.cube_a, self.cube_b) - self.assertString( + _shared_utils.assert_string( + self.request, return_str, [ "unit", @@ -53,7 +58,3 @@ def test_different_array_attributes(self): "incompatible_array_attrs.str.txt", ], ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_equalise_attributes.py b/lib/iris/tests/unit/util/test_equalise_attributes.py index 9b09c84dd4..1392f9cff8 100644 --- a/lib/iris/tests/unit/util/test_equalise_attributes.py +++ b/lib/iris/tests/unit/util/test_equalise_attributes.py @@ -4,14 +4,12 @@ # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :func:`iris.util.equalise_attributes` function.""" -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest from iris.coords import AuxCoord from iris.cube import Cube, CubeAttrsDict +from iris.tests import _shared_utils import iris.tests.stock from iris.tests.unit.common.metadata.test_CubeMetadata import ( _TEST_ATTRNAME, @@ -20,8 +18,9 @@ from iris.util import equalise_attributes -class TestEqualiseAttributes(tests.IrisTest): - def setUp(self): +class TestEqualiseAttributes: + @pytest.fixture(autouse=True) + def _setup(self): empty = Cube([]) self.cube_no_attrs = empty.copy() @@ -66,21 +65,25 @@ def _test(self, cubes, expect_attributes, expect_removed): # Exercise basic operation actual_removed = equalise_attributes(working_cubes) # Check they are the same cubes - self.assertEqual(working_cubes, original_working_list) + assert working_cubes == original_working_list # Check resulting attributes all match the expected set for cube in working_cubes: - self.assertEqual(cube.attributes, expect_attributes) + assert cube.attributes == expect_attributes # Check removed attributes all match as expected - self.assertEqual(len(actual_removed), len(expect_removed)) + assert len(actual_removed) == len(expect_removed) for actual, expect in zip(actual_removed, expect_removed): - self.assertEqual(actual, expect) + if isinstance(actual, dict): + _shared_utils.assert_dict_equal(actual, expect) + else: + _shared_utils.assert_array_equal(actual, expect) + # Check everything else remains the same for new_cube, old_cube in zip(working_cubes, cubes): cube_before_noatts = old_cube.copy() cube_before_noatts.attributes.clear() cube_after_noatts = new_cube.copy() cube_after_noatts.attributes.clear() - self.assertEqual(cube_after_noatts, cube_before_noatts) + assert cube_after_noatts == cube_before_noatts def test_no_attrs(self): cubes = [self.cube_no_attrs] @@ -126,7 +129,7 @@ def test_array_same(self): cubes = [self.cube_a1b5v1, self.cube_a1b6v1] self._test(cubes, {"a": 1, "v": self.v1}, [{"b": 5}, {"b": 6}]) - @tests.skip_data + @_shared_utils.skip_data def test_complex_nonecommon(self): # Example with cell methods and factories, but no common attributes. cubes = [ @@ -136,7 +139,7 @@ def test_complex_nonecommon(self): removed = cubes[0].attributes.copy() self._test(cubes, {}, [removed, {}]) - @tests.skip_data + @_shared_utils.skip_data def test_complex_somecommon(self): # Example with cell methods and factories, plus some common attributes. cubes = [iris.tests.stock.global_pp(), iris.tests.stock.simple_pp()] @@ -250,7 +253,3 @@ def test(self): ] equalise_attributes(coords) assert all(coord.attributes == {"b": "all_the_same"} for coord in coords) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_file_is_newer_than.py b/lib/iris/tests/unit/util/test_file_is_newer_than.py index 567b2a1439..bba3f1fe37 100644 --- a/lib/iris/tests/unit/util/test_file_is_newer_than.py +++ b/lib/iris/tests/unit/util/test_file_is_newer_than.py @@ -4,28 +4,26 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.test_file_is_newer`.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import os import os.path -import shutil -import tempfile + +import pytest from iris.util import file_is_newer_than -class TestFileIsNewer(tests.IrisTest): +class TestFileIsNewer: """Test the :func:`iris.util.file_is_newer_than` function.""" def _name2path(self, filename): """Add the temporary dirpath to a filename to make a full path.""" return os.path.join(self.temp_dir, filename) - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self, tmp_path): # make a temporary directory with testfiles of known timestamp order. - self.temp_dir = tempfile.mkdtemp("_testfiles_tempdir") + self.temp_dir = tmp_path / "_testfiles_tempdir" + self.temp_dir.mkdir() # define the names of some files to create create_file_names = [ "older_source_1", @@ -44,10 +42,6 @@ def setUp(self): mtime += 5.0 + 10.0 * i_file os.utime(file_path, (mtime, mtime)) - def tearDown(self): - # destroy whole contents of temporary directory - shutil.rmtree(self.temp_dir) - def _test(self, boolean_result, result_name, source_names): """Test expected result of executing with given args.""" # Make args into full paths @@ -57,7 +51,7 @@ def _test(self, boolean_result, result_name, source_names): else: source_paths = [self._name2path(name) for name in source_names] # Check result is as expected. - self.assertEqual(boolean_result, file_is_newer_than(result_path, source_paths)) + assert boolean_result == file_is_newer_than(result_path, source_paths) def test_no_sources(self): self._test(True, "example_result", []) @@ -95,28 +89,22 @@ def test_wild_fail(self): self._test(False, "example_result", ["older_sour*", "newer_sour*"]) def test_error_missing_result(self): - with self.assertRaises(OSError) as error_trap: + with pytest.raises(OSError) as error_trap: self._test(False, "non_exist", ["older_sour*"]) - error = error_trap.exception - self.assertEqual(error.strerror, "No such file or directory") - self.assertEqual(error.filename, self._name2path("non_exist")) + error = error_trap.value + assert error.strerror == "No such file or directory" + assert error.filename == self._name2path("non_exist") def test_error_missing_source(self): - with self.assertRaises(IOError) as error_trap: + with pytest.raises(IOError) as error_trap: self._test(False, "example_result", ["older_sour*", "non_exist"]) - self.assertIn( - "One or more of the files specified did not exist", - str(error_trap.exception), + assert ( + "One or more of the files specified did not exist" in error_trap.exconly() ) def test_error_missing_wild(self): - with self.assertRaises(IOError) as error_trap: + with pytest.raises(IOError) as error_trap: self._test(False, "example_result", ["older_sour*", "unknown_*"]) - self.assertIn( - "One or more of the files specified did not exist", - str(error_trap.exception), + assert ( + "One or more of the files specified did not exist" in error_trap.exconly() ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_find_discontiguities.py b/lib/iris/tests/unit/util/test_find_discontiguities.py index e3e824e442..6623121317 100644 --- a/lib/iris/tests/unit/util/test_find_discontiguities.py +++ b/lib/iris/tests/unit/util/test_find_discontiguities.py @@ -4,12 +4,10 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.find_discontiguities.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import numpy as np +import pytest +from iris.tests import _shared_utils from iris.tests.stock import ( make_bounds_discontiguous_at_point, sample_2d_latlons, @@ -22,9 +20,10 @@ def full2d_global(): return sample_2d_latlons(transformed=True) -@tests.skip_data -class Test(tests.IrisTest): - def setUp(self): +@_shared_utils.skip_data +class Test: + @pytest.fixture(autouse=True) + def _setup(self): # Set up a 2d lat-lon cube with 2d coordinates that have been # transformed so they are not in a regular lat-lon grid. # Then generate a discontiguity at a single lat-lon point. @@ -53,7 +52,7 @@ def test_find_discontiguities_right(self): cube = self.testcube_discontig_right expected = cube.data.mask returned = find_discontiguities(cube) - self.assertTrue(np.all(expected == returned)) + assert np.all(expected == returned) def test_find_discontiguities_left(self): # Check that the mask we generate when making the discontiguity @@ -61,7 +60,7 @@ def test_find_discontiguities_left(self): cube = self.testcube_discontig_left expected = cube.data.mask returned = find_discontiguities(cube) - self.assertTrue(np.all(expected == returned)) + assert np.all(expected == returned) def test_find_discontiguities_top(self): # Check that the mask we generate when making the discontiguity @@ -69,7 +68,7 @@ def test_find_discontiguities_top(self): cube = self.testcube_discontig_top expected = cube.data.mask returned = find_discontiguities(cube) - self.assertTrue(np.all(expected == returned)) + assert np.all(expected == returned) def test_find_discontiguities_bottom(self): # Check that the mask we generate when making the discontiguity @@ -77,13 +76,13 @@ def test_find_discontiguities_bottom(self): cube = self.testcube_discontig_along_bottom expected = cube.data.mask returned = find_discontiguities(cube) - self.assertTrue(np.all(expected == returned)) + assert np.all(expected == returned) def test_find_discontiguities_1d_coord(self): # Check that an error is raised when we try and use # find_discontiguities on 1D coordinates: cube = simple_3d() - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): find_discontiguities(cube) def test_find_discontiguities_with_atol(self): @@ -95,7 +94,7 @@ def test_find_discontiguities_with_atol(self): # to represent a mask showing no discontiguities expected = np.zeros(cube.shape, dtype=bool) returned = find_discontiguities(cube, abs_tol=atol) - self.assertTrue(np.all(expected == returned)) + assert np.all(expected == returned) def test_find_discontiguities_with_rtol(self): cube = self.testcube_discontig_right @@ -106,8 +105,4 @@ def test_find_discontiguities_with_rtol(self): # to represent a mask showing no discontiguities expected = np.zeros(cube.shape, dtype=bool) returned = find_discontiguities(cube, rel_tol=rtol) - self.assertTrue(np.all(expected == returned)) - - -if __name__ == "__main__": - tests.main() + assert np.all(expected == returned) diff --git a/lib/iris/tests/unit/util/test_guess_coord_axis.py b/lib/iris/tests/unit/util/test_guess_coord_axis.py index d946565196..5f4d60883e 100644 --- a/lib/iris/tests/unit/util/test_guess_coord_axis.py +++ b/lib/iris/tests/unit/util/test_guess_coord_axis.py @@ -11,7 +11,7 @@ class TestGuessCoord: @pytest.mark.parametrize( - "coordinate, axis", + ("coordinate", "axis"), [ ("longitude", "X"), ("grid_longitude", "X"), @@ -26,7 +26,7 @@ def test_coord(self, coordinate, axis, sample_coord): assert guess_coord_axis(sample_coord) == axis @pytest.mark.parametrize( - "units, axis", + ("units", "axis"), [ ("hPa", "Z"), ("days since 1970-01-01 00:00:00", "T"), @@ -37,7 +37,7 @@ def test_units(self, units, axis, sample_coord): assert guess_coord_axis(sample_coord) == axis @pytest.mark.parametrize( - "ignore_axis, result", + ("ignore_axis", "result"), [ (True, None), (False, "X"), diff --git a/lib/iris/tests/unit/util/test_mask_cube.py b/lib/iris/tests/unit/util/test_mask_cube.py index 47f2774b95..29fd2785a8 100644 --- a/lib/iris/tests/unit/util/test_mask_cube.py +++ b/lib/iris/tests/unit/util/test_mask_cube.py @@ -4,16 +4,13 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.mask_cube.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import pathlib import dask.array as da -import numpy as np import numpy.ma as ma +import pytest +from iris.tests import _shared_utils from iris.tests.stock import ( make_bounds_discontiguous_at_point, sample_2d_latlons, @@ -29,24 +26,30 @@ def full2d_global(): class MaskCubeMixin: - def assertOriginalMetadata(self, cube, func): + @pytest.fixture(autouse=True) + def _get_request(self, request): + self.request = request + + def assert_original_metadata(self, cube, func): """Check metadata matches that of input cube. func is a string indicating which function created the original cube. """ reference_dir = pathlib.Path("unit/util/mask_cube") reference_fname = reference_dir / f"original_cube_{func}.cml" - self.assertCML( + _shared_utils.assert_CML( + self.request, cube, reference_filename=str(reference_fname), checksum=False, ) -class TestArrayMask(tests.IrisTest, MaskCubeMixin): +class TestArrayMask(MaskCubeMixin): """Tests with mask specified as numpy array.""" - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): # Set up a 2d cube with a masked discontiguity to test masking # of 2-dimensional cubes self.cube_2d = full2d_global() @@ -63,9 +66,9 @@ def test_mask_cube_2d_in_place(self): # comparing with masked data cube.data = cube.data.data returned = mask_cube(cube, discontiguity_array, in_place=True) - np.testing.assert_array_equal(expected.data.mask, cube.data.mask) - self.assertOriginalMetadata(cube, "full2d_global") - self.assertIs(returned, None) + _shared_utils.assert_array_equal(expected.data.mask, cube.data.mask) + self.assert_original_metadata(cube, "full2d_global") + assert returned is None def test_mask_cube_2d_not_in_place(self): # This tests the masking of a 2d data array @@ -78,29 +81,30 @@ def test_mask_cube_2d_not_in_place(self): # comparing with masked data cube.data = cube.data.data returned = mask_cube(cube, discontiguity_array, in_place=False) - np.testing.assert_array_equal(expected.data.mask, returned.data.mask) - self.assertOriginalMetadata(returned, "full2d_global") - self.assertFalse(ma.is_masked(cube.data)) + _shared_utils.assert_array_equal(expected.data.mask, returned.data.mask) + self.assert_original_metadata(returned, "full2d_global") + assert not ma.is_masked(cube.data) def test_mask_cube_lazy_in_place_broadcast(self): cube = simple_2d() cube.data = cube.lazy_data() mask = [0, 1, 1, 0] returned = mask_cube(cube, mask, in_place=True) - self.assertTrue(cube.has_lazy_data()) + assert cube.has_lazy_data() # Touch the data so lazyness status doesn't affect CML check. cube.data - self.assertOriginalMetadata(cube, "simple_2d") + self.assert_original_metadata(cube, "simple_2d") for subcube in cube.slices("foo"): # Mask should have been broadcast across "bar" dimension. - np.testing.assert_array_equal(subcube.data.mask, mask) - self.assertIs(returned, None) + _shared_utils.assert_array_equal(subcube.data.mask, mask) + assert returned is None -class TestCoordMask(tests.IrisTest, MaskCubeMixin): +class TestCoordMask(MaskCubeMixin): """Tests with mask specified as a Coord.""" - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.cube = simple_2d() def test_mask_cube_2d_first_dim(self): @@ -110,24 +114,25 @@ def test_mask_cube_2d_first_dim(self): returned = mask_cube(self.cube, mask_coord, in_place=False) # Remove extra coord so we can check against original metadata. returned.remove_coord(mask_coord) - self.assertOriginalMetadata(returned, "simple_2d") + self.assert_original_metadata(returned, "simple_2d") for subcube in returned.slices("bar"): # Mask should have been broadcast across "foo" dimension. - np.testing.assert_array_equal(subcube.data.mask, mask_coord.points) + _shared_utils.assert_array_equal(subcube.data.mask, mask_coord.points) def test_mask_cube_2d_second_dim(self): mask_coord = iris.coords.AuxCoord([0, 0, 1, 1], long_name="mask", units=1) returned = mask_cube(self.cube, mask_coord, in_place=False, dim=1) - self.assertOriginalMetadata(returned, "simple_2d") + self.assert_original_metadata(returned, "simple_2d") for subcube in returned.slices("foo"): # Mask should have been broadcast across "bar" dimension. - np.testing.assert_array_equal(subcube.data.mask, mask_coord.points) + _shared_utils.assert_array_equal(subcube.data.mask, mask_coord.points) -class TestCubeMask(tests.IrisTest, MaskCubeMixin): +class TestCubeMask(MaskCubeMixin): """Tests with mask specified as a Cube.""" - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): self.cube = simple_2d() def test_mask_cube_2d_first_dim_not_in_place(self): @@ -135,21 +140,21 @@ def test_mask_cube_2d_first_dim_not_in_place(self): mask.add_dim_coord(self.cube.coord("bar"), 0) returned = mask_cube(self.cube, mask, in_place=False) - self.assertOriginalMetadata(returned, "simple_2d") + self.assert_original_metadata(returned, "simple_2d") for subcube in returned.slices("bar"): # Mask should have been broadcast across 'foo' dimension. - np.testing.assert_array_equal(subcube.data.mask, mask.data) + _shared_utils.assert_array_equal(subcube.data.mask, mask.data) def test_mask_cube_2d_first_dim_in_place(self): mask = iris.cube.Cube([0, 1, 0], long_name="mask", units=1) mask.add_dim_coord(self.cube.coord("bar"), 0) returned = mask_cube(self.cube, mask, in_place=True) - self.assertOriginalMetadata(self.cube, "simple_2d") + self.assert_original_metadata(self.cube, "simple_2d") for subcube in self.cube.slices("bar"): # Mask should have been broadcast across 'foo' dimension. - np.testing.assert_array_equal(subcube.data.mask, mask.data) - self.assertIs(returned, None) + _shared_utils.assert_array_equal(subcube.data.mask, mask.data) + assert returned is None def test_mask_cube_2d_create_new_dim(self): mask = iris.cube.Cube([[0, 1, 0], [0, 0, 1]], long_name="mask", units=1) @@ -163,27 +168,23 @@ def test_mask_cube_2d_create_new_dim(self): cube = iris.util.new_axis(self.cube, "baz") returned = mask_cube(cube, mask, in_place=False) - self.assertCML(cube, checksum=False) + _shared_utils.assert_CML(self.request, cube, checksum=False) for subcube in returned.slices_over("baz"): # Underlying data should have been broadcast across 'baz' dimension. - np.testing.assert_array_equal(subcube.data, self.cube.data) + _shared_utils.assert_array_equal(subcube.data, self.cube.data) for subcube in returned.slices_over("foo"): # Mask should have been broadcast across 'foo' dimension. - np.testing.assert_array_equal(subcube.data.mask, mask.data) + _shared_utils.assert_array_equal(subcube.data.mask, mask.data) def test_mask_cube_1d_lazy_mask_in_place(self): cube = simple_1d() mask = cube.copy(da.from_array([0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1])) returned = mask_cube(cube, mask, in_place=True) - self.assertIs(returned, None) - self.assertTrue(cube.has_lazy_data()) + assert returned is None + assert cube.has_lazy_data() # Touch the data so lazyness status doesn't interfere with CML check. cube.data - self.assertOriginalMetadata(cube, "simple_1d") - np.testing.assert_array_equal(cube.data.mask, mask.data) - - -if __name__ == "__main__": - tests.main() + self.assert_original_metadata(cube, "simple_1d") + _shared_utils.assert_array_equal(cube.data.mask, mask.data) diff --git a/lib/iris/tests/unit/util/test_mask_cube_from_shapefile.py b/lib/iris/tests/unit/util/test_mask_cube_from_shapefile.py index 7a03ea91aa..bdd5c5fc56 100644 --- a/lib/iris/tests/unit/util/test_mask_cube_from_shapefile.py +++ b/lib/iris/tests/unit/util/test_mask_cube_from_shapefile.py @@ -11,15 +11,15 @@ from iris.coord_systems import RotatedGeogCS from iris.coords import DimCoord import iris.cube -import iris.tests as tests from iris.util import mask_cube_from_shapefile from iris.warnings import IrisUserWarning -class TestBasicCubeMasking(tests.IrisTest): +class TestBasicCubeMasking: """Unit tests for mask_cube_from_shapefile function.""" - def setUp(self): + @pytest.fixture(autouse=True) + def _setup(self): basic_data = np.array([[1, 2, 3], [4, 8, 12]]) self.basic_cube = iris.cube.Cube(basic_data) coord = DimCoord( diff --git a/lib/iris/tests/unit/util/test_new_axis.py b/lib/iris/tests/unit/util/test_new_axis.py index 5ba0496854..4ade2eb61c 100644 --- a/lib/iris/tests/unit/util/test_new_axis.py +++ b/lib/iris/tests/unit/util/test_new_axis.py @@ -4,12 +4,6 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.new_axis`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -# isort: off -import iris.tests as tests # noqa - -# isort: on import copy import numpy as np @@ -24,7 +18,7 @@ class Test: - @pytest.fixture + @pytest.fixture() def stock_cube(self): cube = stock.simple_2d_w_cell_measure_ancil_var() time = iris.coords.DimCoord([1], standard_name="time") diff --git a/lib/iris/tests/unit/util/test_promote_aux_coord_to_dim_coord.py b/lib/iris/tests/unit/util/test_promote_aux_coord_to_dim_coord.py index 4631f910a9..bceffe700d 100644 --- a/lib/iris/tests/unit/util/test_promote_aux_coord_to_dim_coord.py +++ b/lib/iris/tests/unit/util/test_promote_aux_coord_to_dim_coord.py @@ -4,97 +4,90 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.promote_aux_coord_to_dim_coord`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - import unittest +import pytest + import iris +from iris.tests import _shared_utils import iris.tests.stock as stock from iris.util import promote_aux_coord_to_dim_coord -class Test(tests.IrisTest): +class Test: def test_dimension_already_has_dimcoord(self): cube_a = stock.hybrid_height() cube_b = cube_a.copy() promote_aux_coord_to_dim_coord(cube_b, "model_level_number") - self.assertEqual(cube_b.dim_coords, (cube_a.coord("model_level_number"),)) + assert cube_b.dim_coords == (cube_a.coord("model_level_number"),) def test_old_dim_coord_is_now_aux_coord(self): cube_a = stock.hybrid_height() cube_b = cube_a.copy() promote_aux_coord_to_dim_coord(cube_b, "model_level_number") - self.assertTrue(cube_a.coord("level_height") in cube_b.aux_coords) + assert cube_a.coord("level_height") in cube_b.aux_coords - @tests.skip_data + @_shared_utils.skip_data def test_argument_is_coord_instance(self): cube_a = stock.realistic_4d() cube_b = cube_a.copy() promote_aux_coord_to_dim_coord(cube_b, cube_b.coord("level_height")) - self.assertEqual( - cube_b.dim_coords, - ( - cube_a.coord("time"), - cube_a.coord("level_height"), - cube_a.coord("grid_latitude"), - cube_a.coord("grid_longitude"), - ), + assert cube_b.dim_coords == ( + cube_a.coord("time"), + cube_a.coord("level_height"), + cube_a.coord("grid_latitude"), + cube_a.coord("grid_longitude"), ) - @tests.skip_data + @_shared_utils.skip_data def test_dimension_is_anonymous(self): cube_a = stock.realistic_4d() cube_b = cube_a.copy() cube_b.remove_coord("model_level_number") promote_aux_coord_to_dim_coord(cube_b, "level_height") - self.assertEqual( - cube_b.dim_coords, - ( - cube_a.coord("time"), - cube_a.coord("level_height"), - cube_a.coord("grid_latitude"), - cube_a.coord("grid_longitude"), - ), + assert cube_b.dim_coords == ( + cube_a.coord("time"), + cube_a.coord("level_height"), + cube_a.coord("grid_latitude"), + cube_a.coord("grid_longitude"), ) def test_already_a_dim_coord(self): cube_a = stock.simple_2d_w_multidim_and_scalars() cube_b = cube_a.copy() promote_aux_coord_to_dim_coord(cube_b, "dim1") - self.assertEqual(cube_a, cube_b) + assert cube_a == cube_b def test_coord_of_that_name_does_not_exist(self): cube_a = stock.simple_2d_w_multidim_and_scalars() - with self.assertRaises(iris.exceptions.CoordinateNotFoundError): + with pytest.raises(iris.exceptions.CoordinateNotFoundError): promote_aux_coord_to_dim_coord(cube_a, "wibble") def test_coord_does_not_exist(self): cube_a = stock.simple_2d_w_multidim_and_scalars() coord = cube_a.coord("dim1").copy() coord.rename("new") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): promote_aux_coord_to_dim_coord(cube_a, coord) def test_argument_is_wrong_type(self): cube_a = stock.simple_1d() - with self.assertRaises(TypeError): + with pytest.raises(TypeError): promote_aux_coord_to_dim_coord(cube_a, 0.0) def test_trying_to_promote_a_multidim_coord(self): cube_a = stock.simple_2d_w_multidim_coords() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): promote_aux_coord_to_dim_coord(cube_a, "bar") def test_trying_to_promote_a_scalar_coord(self): cube_a = stock.simple_2d_w_multidim_and_scalars() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): promote_aux_coord_to_dim_coord(cube_a, "an_other") def test_trying_to_promote_a_nonmonotonic_coord(self): cube_a = stock.hybrid_height() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): promote_aux_coord_to_dim_coord(cube_a, "surface_altitude") diff --git a/lib/iris/tests/unit/util/test_reverse.py b/lib/iris/tests/unit/util/test_reverse.py index 562447aaf7..77d6df0251 100644 --- a/lib/iris/tests/unit/util/test_reverse.py +++ b/lib/iris/tests/unit/util/test_reverse.py @@ -4,71 +4,68 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.reverse`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import unittest - import numpy as np +import pytest import iris +from iris.tests import _shared_utils from iris.util import reverse -class Test_array(tests.IrisTest): +class Test_array: def test_simple_array(self): a = np.arange(12).reshape(3, 4) - self.assertArrayEqual(a[::-1], reverse(a, 0)) - self.assertArrayEqual(a[::-1, ::-1], reverse(a, [0, 1])) - self.assertArrayEqual(a[:, ::-1], reverse(a, 1)) - self.assertArrayEqual(a[:, ::-1], reverse(a, [1])) + _shared_utils.assert_array_equal(a[::-1], reverse(a, 0)) + _shared_utils.assert_array_equal(a[::-1, ::-1], reverse(a, [0, 1])) + _shared_utils.assert_array_equal(a[:, ::-1], reverse(a, 1)) + _shared_utils.assert_array_equal(a[:, ::-1], reverse(a, [1])) msg = "Reverse was expecting a single axis or a 1d array *" - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(a, []) msg = "An axis value out of range for the number of dimensions *" - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(a, -1) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(a, 10) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(a, [-1]) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(a, [0, -1]) msg = "To reverse an array, provide an int *" - with self.assertRaisesRegex(TypeError, msg): + with pytest.raises(TypeError, match=msg): reverse(a, "latitude") def test_single_array(self): a = np.arange(36).reshape(3, 4, 3) - self.assertArrayEqual(a[::-1], reverse(a, 0)) - self.assertArrayEqual(a[::-1, ::-1], reverse(a, [0, 1])) - self.assertArrayEqual(a[:, ::-1, ::-1], reverse(a, [1, 2])) - self.assertArrayEqual(a[..., ::-1], reverse(a, 2)) + _shared_utils.assert_array_equal(a[::-1], reverse(a, 0)) + _shared_utils.assert_array_equal(a[::-1, ::-1], reverse(a, [0, 1])) + _shared_utils.assert_array_equal(a[:, ::-1, ::-1], reverse(a, [1, 2])) + _shared_utils.assert_array_equal(a[..., ::-1], reverse(a, 2)) msg = "Reverse was expecting a single axis or a 1d array *" - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(a, []) msg = "An axis value out of range for the number of dimensions *" - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(a, -1) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(a, 10) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(a, [-1]) - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(a, [0, -1]) - with self.assertRaisesRegex(TypeError, "To reverse an array, provide an int *"): + with pytest.raises(TypeError, match="To reverse an array, provide an int *"): reverse(a, "latitude") -class Test_cube(tests.IrisTest): - def setUp(self): +class Test_cube: + @pytest.fixture(autouse=True) + def _setup(self): # On this cube pair, the coordinates to perform operations on have # matching long names but the points array on one cube is reversed # with respect to that on the other. @@ -95,81 +92,99 @@ def setUp(self): self.cube2 = iris.cube.Cube(data, dim_coords_and_dims=[(a2, 0), (b2, 1)]) def check_coorda_reversed(self, result): - self.assertArrayEqual(self.cube2.coord("a").points, result.coord("a").points) - self.assertArrayEqual(self.cube2.coord("a").bounds, result.coord("a").bounds) + _shared_utils.assert_array_equal( + self.cube2.coord("a").points, result.coord("a").points + ) + _shared_utils.assert_array_equal( + self.cube2.coord("a").bounds, result.coord("a").bounds + ) def check_coorda_unchanged(self, result): - self.assertArrayEqual(self.cube1.coord("a").points, result.coord("a").points) - self.assertArrayEqual(self.cube1.coord("a").bounds, result.coord("a").bounds) + _shared_utils.assert_array_equal( + self.cube1.coord("a").points, result.coord("a").points + ) + _shared_utils.assert_array_equal( + self.cube1.coord("a").bounds, result.coord("a").bounds + ) def check_coordb_reversed(self, result): - self.assertArrayEqual(self.cube2.coord("b").points, result.coord("b").points) + _shared_utils.assert_array_equal( + self.cube2.coord("b").points, result.coord("b").points + ) def check_coordb_unchanged(self, result): - self.assertArrayEqual(self.cube1.coord("b").points, result.coord("b").points) + _shared_utils.assert_array_equal( + self.cube1.coord("b").points, result.coord("b").points + ) def test_cube_dim0(self): cube1_reverse0 = reverse(self.cube1, 0) - self.assertArrayEqual(self.cube1.data[::-1], cube1_reverse0.data) + _shared_utils.assert_array_equal(self.cube1.data[::-1], cube1_reverse0.data) self.check_coorda_reversed(cube1_reverse0) self.check_coordb_unchanged(cube1_reverse0) def test_cube_dim1(self): cube1_reverse1 = reverse(self.cube1, 1) - self.assertArrayEqual(self.cube1.data[:, ::-1], cube1_reverse1.data) + _shared_utils.assert_array_equal(self.cube1.data[:, ::-1], cube1_reverse1.data) self.check_coordb_reversed(cube1_reverse1) self.check_coorda_unchanged(cube1_reverse1) def test_cube_dim_both(self): cube1_reverse_both = reverse(self.cube1, (0, 1)) - self.assertArrayEqual(self.cube1.data[::-1, ::-1], cube1_reverse_both.data) + _shared_utils.assert_array_equal( + self.cube1.data[::-1, ::-1], cube1_reverse_both.data + ) self.check_coorda_reversed(cube1_reverse_both) self.check_coordb_reversed(cube1_reverse_both) def test_cube_coord0(self): cube1_reverse0 = reverse(self.cube1, self.a1) - self.assertArrayEqual(self.cube1.data[::-1], cube1_reverse0.data) + _shared_utils.assert_array_equal(self.cube1.data[::-1], cube1_reverse0.data) self.check_coorda_reversed(cube1_reverse0) self.check_coordb_unchanged(cube1_reverse0) def test_cube_coord1(self): cube1_reverse1 = reverse(self.cube1, "b") - self.assertArrayEqual(self.cube1.data[:, ::-1], cube1_reverse1.data) + _shared_utils.assert_array_equal(self.cube1.data[:, ::-1], cube1_reverse1.data) self.check_coordb_reversed(cube1_reverse1) self.check_coorda_unchanged(cube1_reverse1) def test_cube_coord_both(self): cube1_reverse_both = reverse(self.cube1, (self.a1, self.b1)) - self.assertArrayEqual(self.cube1.data[::-1, ::-1], cube1_reverse_both.data) + _shared_utils.assert_array_equal( + self.cube1.data[::-1, ::-1], cube1_reverse_both.data + ) self.check_coorda_reversed(cube1_reverse_both) self.check_coordb_reversed(cube1_reverse_both) def test_cube_coord_spanning(self): cube1_reverse_spanning = reverse(self.cube1, "spanning") - self.assertArrayEqual(self.cube1.data[::-1, ::-1], cube1_reverse_spanning.data) + _shared_utils.assert_array_equal( + self.cube1.data[::-1, ::-1], cube1_reverse_spanning.data + ) self.check_coorda_reversed(cube1_reverse_spanning) self.check_coordb_reversed(cube1_reverse_spanning) - self.assertArrayEqual( + _shared_utils.assert_array_equal( self.span.points[::-1, ::-1], cube1_reverse_spanning.coord("spanning").points, ) def test_wrong_coord_name(self): msg = "Expected to find exactly 1 'latitude' coordinate, but found none." - with self.assertRaisesRegex(iris.exceptions.CoordinateNotFoundError, msg): + with pytest.raises(iris.exceptions.CoordinateNotFoundError, match=msg): reverse(self.cube1, "latitude") def test_empty_list(self): msg = "Reverse was expecting a single axis or a 1d array *" - with self.assertRaisesRegex(ValueError, msg): + with pytest.raises(ValueError, match=msg): reverse(self.cube1, []) def test_wrong_type_cube(self): @@ -177,14 +192,10 @@ def test_wrong_type_cube(self): "coords_or_dims must be int, str, coordinate or sequence of " "these. Got cube." ) - with self.assertRaisesRegex(TypeError, msg): + with pytest.raises(TypeError, match=msg): reverse(self.cube1, self.cube1) def test_wrong_type_float(self): msg = "coords_or_dims must be int, str, coordinate or sequence of these." - with self.assertRaisesRegex(TypeError, msg): + with pytest.raises(TypeError, match=msg): reverse(self.cube1, 3.0) - - -if __name__ == "__main__": - unittest.main() diff --git a/lib/iris/tests/unit/util/test_rolling_window.py b/lib/iris/tests/unit/util/test_rolling_window.py index d70b398ed5..c2e5bdbb6c 100644 --- a/lib/iris/tests/unit/util/test_rolling_window.py +++ b/lib/iris/tests/unit/util/test_rolling_window.py @@ -4,24 +4,22 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.rolling_window`.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import dask.array as da import numpy as np import numpy.ma as ma +import pytest +from iris.tests import _shared_utils from iris.util import rolling_window -class Test_rolling_window(tests.IrisTest): +class Test_rolling_window: def test_1d(self): # 1-d array input a = np.array([0, 1, 2, 3, 4], dtype=np.int32) expected_result = np.array([[0, 1], [1, 2], [2, 3], [3, 4]], dtype=np.int32) result = rolling_window(a, window=2) - self.assertArrayEqual(result, expected_result) + _shared_utils.assert_array_equal(result, expected_result) def test_2d(self): # 2-d array input @@ -34,13 +32,13 @@ def test_2d(self): dtype=np.int32, ) result = rolling_window(a, window=3, axis=1) - self.assertArrayEqual(result, expected_result) + _shared_utils.assert_array_equal(result, expected_result) def test_3d_lazy(self): a = da.arange(2 * 3 * 4).reshape((2, 3, 4)) expected_result = np.arange(2 * 3 * 4).reshape((1, 2, 3, 4)) result = rolling_window(a, window=2, axis=0).compute() - self.assertArrayEqual(result, expected_result) + _shared_utils.assert_array_equal(result, expected_result) def test_1d_masked(self): # 1-d masked array input @@ -51,7 +49,7 @@ def test_1d_masked(self): dtype=np.int32, ) result = rolling_window(a, window=2) - self.assertMaskedArrayEqual(result, expected_result) + _shared_utils.assert_masked_array_equal(result, expected_result) def test_2d_masked(self): # 2-d masked array input @@ -72,7 +70,7 @@ def test_2d_masked(self): dtype=np.int32, ) result = rolling_window(a, window=3, axis=1) - self.assertMaskedArrayEqual(result, expected_result) + _shared_utils.assert_masked_array_equal(result, expected_result) def test_degenerate_mask(self): a = ma.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=np.int32) @@ -88,7 +86,7 @@ def test_degenerate_mask(self): dtype=np.int32, ) result = rolling_window(a, window=3, axis=1) - self.assertMaskedArrayEqual(result, expected_result) + _shared_utils.assert_masked_array_equal(result, expected_result) def test_step(self): # step should control how far apart consecutive windows are @@ -97,27 +95,23 @@ def test_step(self): [[[0, 1, 2], [2, 3, 4]], [[5, 6, 7], [7, 8, 9]]], dtype=np.int32 ) result = rolling_window(a, window=3, step=2, axis=1) - self.assertArrayEqual(result, expected_result) + _shared_utils.assert_array_equal(result, expected_result) def test_window_too_short(self): # raise an error if the window length is less than 1 a = np.empty([5]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): rolling_window(a, window=0) def test_window_too_long(self): # raise an error if the window length is longer than the # corresponding array dimension a = np.empty([7, 5]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): rolling_window(a, window=6, axis=1) def test_invalid_step(self): # raise an error if the step between windows is less than 1 a = np.empty([5]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): rolling_window(a, step=0) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_squeeze.py b/lib/iris/tests/unit/util/test_squeeze.py index cb4b55c1e6..c1a63a64b6 100644 --- a/lib/iris/tests/unit/util/test_squeeze.py +++ b/lib/iris/tests/unit/util/test_squeeze.py @@ -4,47 +4,40 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.squeeze`.""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import unittest +import pytest import iris import iris.tests.stock as stock -class Test(tests.IrisTest): - def setUp(self): +class Test: + @pytest.fixture(autouse=True) + def _setup(self): self.cube = stock.simple_2d_w_multidim_and_scalars() def test_no_change(self): - self.assertEqual(self.cube, iris.util.squeeze(self.cube)) + assert self.cube == iris.util.squeeze(self.cube) def test_squeeze_one_dim(self): cube_3d = iris.util.new_axis(self.cube, scalar_coord="an_other") cube_2d = iris.util.squeeze(cube_3d) - self.assertEqual(self.cube, cube_2d) + assert self.cube == cube_2d def test_squeeze_two_dims(self): cube_3d = iris.util.new_axis(self.cube, scalar_coord="an_other") cube_4d = iris.util.new_axis(cube_3d, scalar_coord="air_temperature") - self.assertEqual(self.cube, iris.util.squeeze(cube_4d)) + assert self.cube == iris.util.squeeze(cube_4d) def test_squeeze_one_anonymous_dim(self): cube_3d = iris.util.new_axis(self.cube) cube_2d = iris.util.squeeze(cube_3d) - self.assertEqual(self.cube, cube_2d) + assert self.cube == cube_2d def test_squeeze_to_scalar_cube(self): cube_scalar = self.cube[0, 0] cube_1d = iris.util.new_axis(cube_scalar) - self.assertEqual(cube_scalar, iris.util.squeeze(cube_1d)) - - -if __name__ == "__main__": - unittest.main() + assert cube_scalar == iris.util.squeeze(cube_1d) diff --git a/lib/iris/tests/unit/util/test_unify_time_units.py b/lib/iris/tests/unit/util/test_unify_time_units.py index c70bb25a0f..ceccc459ce 100644 --- a/lib/iris/tests/unit/util/test_unify_time_units.py +++ b/lib/iris/tests/unit/util/test_unify_time_units.py @@ -4,21 +4,18 @@ # See LICENSE in the root of the repository for full licensing details. """Test function :func:`iris.util.array_equal`.""" -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - import copy import cf_units import numpy as np import iris +from iris.tests import _shared_utils import iris.tests.stock as stock from iris.util import unify_time_units -class Test(tests.IrisTest): +class Test: def simple_1d_time_cubes(self, calendar="standard"): coord_points = [1, 2, 3, 4, 5] data_points = [273, 275, 278, 277, 274] @@ -51,7 +48,7 @@ def _common(self, expected, result, coord_name="time"): except iris.exceptions.CoordinateNotFoundError: pass else: - self.assertEqual(expected, epoch) + assert expected == epoch def test_cubelist_with_time_coords(self): # Tests an :class:`iris.cube.CubeList` containing cubes with time @@ -69,16 +66,16 @@ def test_list_of_cubes_with_time_coords(self): unify_time_units(list_of_cubes) self._common(expected, list_of_cubes) - @tests.skip_data + @_shared_utils.skip_data def test_no_time_coord_in_cubes(self): - path0 = tests.get_data_path(("PP", "aPPglob1", "global.pp")) - path1 = tests.get_data_path(("PP", "aPPglob1", "global_t_forecast.pp")) + path0 = _shared_utils.get_data_path(("PP", "aPPglob1", "global.pp")) + path1 = _shared_utils.get_data_path(("PP", "aPPglob1", "global_t_forecast.pp")) cube0 = iris.load_cube(path0) cube1 = iris.load_cube(path1) cubes = iris.cube.CubeList([cube0, cube1]) result = copy.copy(cubes) unify_time_units(result) - self.assertEqual(cubes, result) + assert cubes == result def test_time_coord_only_in_some_cubes(self): list_of_cubes = self.simple_1d_time_cubes() @@ -136,7 +133,3 @@ def test_units_dtype_int_float(self): cubelist = iris.cube.CubeList([cube0, cube1]) unify_time_units(cubelist) assert len(cubelist.concatenate()) == 1 - - -if __name__ == "__main__": - tests.main() diff --git a/pyproject.toml b/pyproject.toml index b849dae9e6..74e514ad20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,6 +134,7 @@ version_scheme = "release-branch-semver" [tool.pytest.ini_options] addopts = "-ra --durations=25" +required_plugins = "pytest-mock" testpaths = "lib/iris" [tool.coverage.run] diff --git a/requirements/locks/py310-linux-64.lock b/requirements/locks/py310-linux-64.lock index a67480e8a2..97972bfa1f 100644 --- a/requirements/locks/py310-linux-64.lock +++ b/requirements/locks/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 372c3b278b46d5c658024f7b6b47d7b92266bb7ca5a25b0eb4f67e055b8a02a7 +# input_hash: 3a1bed2476064df92c4edecb4c0b462e6b4ecaa37082bd1ee61a2502ff4671a1 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda#c27d1c142233b5bc9ca570c6e2e0c244 @@ -57,7 +57,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.5-h4ab18f5_0.conda#60 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hd590300_0.conda#48f4330bfcd959c3cfb704d424903c82 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.44-hadc24fc_0.conda#f4cc49d7aa68316213e4b12be35308d1 -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.0-hadc24fc_0.conda#540296f0ce9d3352188c15a89b30b9ac +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.0-hadc24fc_1.conda#b6f02b52a174e612e89548f4663ce56a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.0-h0841786_0.conda#1f5a58e686b13bcfde88b93f547d23fe https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda#8371ac6457591af2cf6159439c1fd051 https://conda.anaconda.org/conda-forge/linux-64/libudunits2-2.2.28-h40f5838_3.conda#4bdace082e911a3e1f1f0b721bed5b56 @@ -117,17 +117,17 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.10-h4f16b4b_0.co https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda#4d056880988120e29d75bfff282e0f45 https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-hef167b5_0.conda#54fe76ab3d0189acaef95156874db7f9 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.1.0-hb9d3cd8_2.conda#98514fe74548d768907ce7a13f680e8f -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda#0f69b688f52ff6da70bccb7ff7001d1d +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda#8f5b0b297b59e1ac160ad4beec99dbee https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda#3f43953b7d3fb3aaa1d0d0723d91e368 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.82.2-h2ff4ddf_0.conda#13e8e54035ddd2b91875ba399f0f7c04 https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_1.conda#80a57756c545ad11f9847835aa21e6b2 https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_0.conda#9ebc9aedafaa2515ab247ff6bb509458 https://conda.anaconda.org/conda-forge/linux-64/libtheora-1.1.1-h4ab18f5_1006.conda#553281a034e9cf8693c9df49f6c78ea1 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-he137b08_1.conda#63872517c98aa305da58a757c443698e -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-he7c6b58_4.conda#08a9265c637230c37cb1be4a6cad4536 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.4-hb346dea_2.conda#69b90b70c434b916abf5a1d5ee5d55fb https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-9.0.1-he0572af_2.conda#57a9e7ee3c0840d3c8c9012473978629 https://conda.anaconda.org/conda-forge/linux-64/python-3.10.15-h4a871b0_2_cpython.conda#98059097f62e97be9aed7ec904055825 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.47.0-h9eae976_0.conda#c4cb444844615e1cd4c9d989f770bcc5 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.47.0-h9eae976_1.conda#53abf1ef70b9ae213b22caa5350f97a9 https://conda.anaconda.org/conda-forge/noarch/wayland-protocols-1.37-hd8ed1ab_0.conda#73ec79a77d31eb7e4a3276cd246b776c https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda#a0901183f08b6c7107aab109733a3c91 https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.43-hb9d3cd8_0.conda#f725c7425d6d7c15e31f3b99a88ea02f @@ -177,7 +177,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-hd3e95f3_10.conda#30 https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_1.conda#204892bce2e44252b5cf272712f10bdd https://conda.anaconda.org/conda-forge/linux-64/libglu-9.0.0-ha6d2627_1004.conda#df069bea331c8486ac21814969301c1f https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.1-default_hecaa2ac_1000.conda#f54aeebefb5c5ff84eca4fb05ca8aa3a -https://conda.anaconda.org/conda-forge/linux-64/libllvm19-19.1.2-ha7bfdaf_0.conda#128e74a4f8f4fef4dc5130a8bbccc15d +https://conda.anaconda.org/conda-forge/linux-64/libllvm19-19.1.3-ha7bfdaf_0.conda#8bd654307c455162668cd66e36494000 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.7.0-h2c5496b_1.conda#e2eaefa4de2b7237af7c907b8bbc760a https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/linux-64/loguru-0.7.2-py310hff52083_2.conda#4e8b2a2851668c8ad4d5360845281be9 @@ -200,7 +200,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.5.0-py310ha75aee https://conda.anaconda.org/conda-forge/noarch/pytz-2024.1-pyhd8ed1ab_0.conda#3eeeeb9e4827ace8c0c1419c85d590ad https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py310ha75aee5_1.conda#0d4c5c76ae5f5aac6f0be419963a19dd https://conda.anaconda.org/conda-forge/noarch/scooby-0.10.0-pyhd8ed1ab_0.conda#9e57330f431abbb4c88a5f898a4ba223 -https://conda.anaconda.org/conda-forge/noarch/setuptools-75.1.0-pyhd8ed1ab_0.conda#d5cd48392c67fb6849ba459c2c2b671f +https://conda.anaconda.org/conda-forge/noarch/setuptools-75.3.0-pyhd8ed1ab_0.conda#2ce9825396daf72baabaade36cee16da https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2#6d6552722448103793743dabfbda532d @@ -239,21 +239,21 @@ https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.4-nompi_h2d575fe_101.c https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda#54198435fce4d64d8a89af22573012a8 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda#7b86ecb7d3557821c649b3c31e3eb9f2 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-25_linux64_openblas.conda#5dbd1b0fc0d01ec5e0e1fbe667281a11 -https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp19.1-19.1.2-default_hb5137d0_1.conda#7e574c7499bc41f92537634a23fed79a -https://conda.anaconda.org/conda-forge/linux-64/libclang13-19.1.2-default_h9c6a7e4_1.conda#cb5c5ff12b37aded00d9aaa7b9a86a78 +https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp19.1-19.1.3-default_hb5137d0_0.conda#311e6a1d041db3d6a8a8437750d4234f +https://conda.anaconda.org/conda-forge/linux-64/libclang13-19.1.3-default_h9c6a7e4_0.conda#b8a8cd77810b20754f358f2327812552 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-25_linux64_openblas.conda#4dc03a53fc69371a6158d0ed37214cd3 https://conda.anaconda.org/conda-forge/linux-64/libva-2.22.0-h8a09558_1.conda#139262125a3eac8ff6eef898598745a3 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_0.conda#dfe0528d0f1c16c1f7c528ea5536ab30 https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.8-hedd0468_0.conda#dcd0ed5147d8876b0848a552b416ce76 https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda#0badf9c54e24cecfb0ad2f99d680c163 https://conda.anaconda.org/conda-forge/linux-64/pillow-11.0.0-py310hfeaa1f3_0.conda#1947280342c7259b82a707e38ebc212e -https://conda.anaconda.org/conda-forge/noarch/pip-24.2-pyh8b19718_1.conda#6c78fbb8ddfd64bcb55b5cbafd2d2c43 +https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_0.conda#5dd546fe99b44fda83963d15f84263b7 https://conda.anaconda.org/conda-forge/linux-64/proj-9.5.0-h12925eb_0.conda#8c29983ebe50cc7e0998c34bc7614222 https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.3-pyhd8ed1ab_0.conda#c03d61f31f38fdb9facf70c29958bf7a https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda#2cf4264fffb9e6eff6031c5b6884d61c https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-h84d6215_0.conda#ee6f7fd1e76061ef1fa307d41fa86a96 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_0.conda#52d648bd608f5737b123f510bb5514b5 -https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.27.0-pyhd8ed1ab_0.conda#a6ed1227ba6ec37cfc2b25e6512f729f +https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.27.1-pyhd8ed1ab_0.conda#dae21509d62aa7bf676279ced3edcb3f https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda#7bbe9a0cc0df0ac5f5a8ad6d6a11af2f https://conda.anaconda.org/conda-forge/noarch/asv_runner-0.2.1-pyhd8ed1ab_0.conda#fdcbeb072c80c805a2ededaa5f91cd79 https://conda.anaconda.org/conda-forge/noarch/async-timeout-4.0.3-pyhd8ed1ab_0.conda#3ce482ec3066e6d809dbbb1d1679f215 @@ -268,7 +268,8 @@ https://conda.anaconda.org/conda-forge/linux-64/numpy-2.1.2-py310hd6e36ab_0.cond https://conda.anaconda.org/conda-forge/linux-64/pango-1.54.0-h4c5309f_1.conda#7df02e445367703cd87a574046e3a6f0 https://conda.anaconda.org/conda-forge/noarch/pbr-6.1.0-pyhd8ed1ab_0.conda#5a166b998fd17cdaaaadaccdd71a363f https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.7.0-py310h2e9f774_0.conda#42a3ea3c283d930ae6d156b97ffe4740 -https://conda.anaconda.org/conda-forge/noarch/pytest-cov-5.0.0-pyhd8ed1ab_0.conda#c54c0107057d67ddf077751339ec2c63 +https://conda.anaconda.org/conda-forge/noarch/pytest-cov-6.0.0-pyhd8ed1ab_0.conda#cb8a11b6d209e3d85e5094bdbd9ebd9c +https://conda.anaconda.org/conda-forge/noarch/pytest-mock-3.14.0-pyhd8ed1ab_0.conda#4b9b5e086812283c052a9105ab1e254e https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_0.conda#b39568655c127a9c4a44d178ac99b6d0 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-8.1.0-pyhd8ed1ab_0.conda#ba9f7f0ec4f2a18de3e7bce67c4a431e https://conda.anaconda.org/conda-forge/linux-64/tbb-devel-2021.13.0-h94b29a5_0.conda#4431bd4ace17dd09b97caf68509b016b @@ -308,7 +309,7 @@ https://conda.anaconda.org/conda-forge/linux-64/yarl-1.16.0-py310ha75aee5_0.cond https://conda.anaconda.org/conda-forge/linux-64/aiohttp-3.10.10-py310h89163eb_0.conda#cdc075f4328556adf4dde97b4f4a0532 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.2.0-py310hf462985_6.conda#b8ad2d561f4e0db4f09d06cc0e73e0b0 https://conda.anaconda.org/conda-forge/noarch/distributed-2024.10.0-pyhd8ed1ab_0.conda#b3b498f7bcc9a2543ad72a3501f3d87b -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.6.1-nompi_h6063b07_4.conda#3108bfa76cd8a3ebc5546797106946e5 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.7.0-nompi_h6063b07_0.conda#d5ee837e9e21dabb505a010c6a196fa6 https://conda.anaconda.org/conda-forge/linux-64/ffmpeg-6.1.2-gpl_h8657690_705.conda#bba34ade586dc53222d5e0387f7733c2 https://conda.anaconda.org/conda-forge/linux-64/graphviz-12.0.0-hba01fac_0.conda#953e31ea00d46beb7e64a79fc291ec44 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a @@ -319,7 +320,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.3.0-py310hf462 https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda#5ede4753180c7a550a443c430dc8ab52 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.24.0-py310h5eaa309_0.conda#ca4d935c1715f95b6e86846ad1675a2b https://conda.anaconda.org/conda-forge/noarch/cmocean-4.0.3-pyhd8ed1ab_0.conda#53df00540de0348ed1b2a62684dd912b -https://conda.anaconda.org/conda-forge/noarch/esmpy-8.6.1-pyhc1e730c_0.conda#25a9661177fd68bfdb4314fd658e5c3b +https://conda.anaconda.org/conda-forge/noarch/esmpy-8.7.0-pyhecae5ae_0.conda#80851ac5ec3916496d7f353351c48846 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_0.conda#8dab97d8a9616e07d779782995710aed https://conda.anaconda.org/conda-forge/noarch/wslink-2.2.1-pyhd8ed1ab_0.conda#74674b93806167c26da4eca7613bc225 @@ -338,3 +339,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_0.conda#d6e5ea5fe00164ac6c2dcc5d76a42192 https://conda.anaconda.org/conda-forge/noarch/sphinx-8.1.3-pyhd8ed1ab_0.conda#05706dd5a145a9c91861495cd435409a https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.10-pyhd8ed1ab_0.conda#e507335cb4ca9cff4c3d0fa9cdab255e + diff --git a/requirements/locks/py311-linux-64.lock b/requirements/locks/py311-linux-64.lock index 60a47736e4..1304a69343 100644 --- a/requirements/locks/py311-linux-64.lock +++ b/requirements/locks/py311-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 58de0176aff465b8a68544e32553b8c5648f581ece5d2c4df0d333dd456ea851 +# input_hash: 35b01abef89f7af6fc6928cd3e505497158c7209f4f0efed8da35047fdabdc86 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda#c27d1c142233b5bc9ca570c6e2e0c244 @@ -57,7 +57,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.5-h4ab18f5_0.conda#60 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hd590300_0.conda#48f4330bfcd959c3cfb704d424903c82 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.44-hadc24fc_0.conda#f4cc49d7aa68316213e4b12be35308d1 -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.0-hadc24fc_0.conda#540296f0ce9d3352188c15a89b30b9ac +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.0-hadc24fc_1.conda#b6f02b52a174e612e89548f4663ce56a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.0-h0841786_0.conda#1f5a58e686b13bcfde88b93f547d23fe https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda#8371ac6457591af2cf6159439c1fd051 https://conda.anaconda.org/conda-forge/linux-64/libudunits2-2.2.28-h40f5838_3.conda#4bdace082e911a3e1f1f0b721bed5b56 @@ -117,17 +117,17 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.10-h4f16b4b_0.co https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda#4d056880988120e29d75bfff282e0f45 https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-hef167b5_0.conda#54fe76ab3d0189acaef95156874db7f9 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.1.0-hb9d3cd8_2.conda#98514fe74548d768907ce7a13f680e8f -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda#0f69b688f52ff6da70bccb7ff7001d1d +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda#8f5b0b297b59e1ac160ad4beec99dbee https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda#3f43953b7d3fb3aaa1d0d0723d91e368 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.82.2-h2ff4ddf_0.conda#13e8e54035ddd2b91875ba399f0f7c04 https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_1.conda#80a57756c545ad11f9847835aa21e6b2 https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_0.conda#9ebc9aedafaa2515ab247ff6bb509458 https://conda.anaconda.org/conda-forge/linux-64/libtheora-1.1.1-h4ab18f5_1006.conda#553281a034e9cf8693c9df49f6c78ea1 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-he137b08_1.conda#63872517c98aa305da58a757c443698e -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-he7c6b58_4.conda#08a9265c637230c37cb1be4a6cad4536 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.4-hb346dea_2.conda#69b90b70c434b916abf5a1d5ee5d55fb https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-9.0.1-he0572af_2.conda#57a9e7ee3c0840d3c8c9012473978629 https://conda.anaconda.org/conda-forge/linux-64/python-3.11.10-hc5c86c4_3_cpython.conda#9e1ad55c87368e662177661a998feed5 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.47.0-h9eae976_0.conda#c4cb444844615e1cd4c9d989f770bcc5 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.47.0-h9eae976_1.conda#53abf1ef70b9ae213b22caa5350f97a9 https://conda.anaconda.org/conda-forge/noarch/wayland-protocols-1.37-hd8ed1ab_0.conda#73ec79a77d31eb7e4a3276cd246b776c https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda#a0901183f08b6c7107aab109733a3c91 https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.43-hb9d3cd8_0.conda#f725c7425d6d7c15e31f3b99a88ea02f @@ -177,7 +177,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-hd3e95f3_10.conda#30 https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_1.conda#204892bce2e44252b5cf272712f10bdd https://conda.anaconda.org/conda-forge/linux-64/libglu-9.0.0-ha6d2627_1004.conda#df069bea331c8486ac21814969301c1f https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.1-default_hecaa2ac_1000.conda#f54aeebefb5c5ff84eca4fb05ca8aa3a -https://conda.anaconda.org/conda-forge/linux-64/libllvm19-19.1.2-ha7bfdaf_0.conda#128e74a4f8f4fef4dc5130a8bbccc15d +https://conda.anaconda.org/conda-forge/linux-64/libllvm19-19.1.3-ha7bfdaf_0.conda#8bd654307c455162668cd66e36494000 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.7.0-h2c5496b_1.conda#e2eaefa4de2b7237af7c907b8bbc760a https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/linux-64/loguru-0.7.2-py311h38be061_2.conda#733b481d20ff260a34f2b0003ff4fbb3 @@ -201,7 +201,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.5.0-py311h9ecbd0 https://conda.anaconda.org/conda-forge/noarch/pytz-2024.1-pyhd8ed1ab_0.conda#3eeeeb9e4827ace8c0c1419c85d590ad https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py311h9ecbd09_1.conda#abeb54d40f439b86f75ea57045ab8496 https://conda.anaconda.org/conda-forge/noarch/scooby-0.10.0-pyhd8ed1ab_0.conda#9e57330f431abbb4c88a5f898a4ba223 -https://conda.anaconda.org/conda-forge/noarch/setuptools-75.1.0-pyhd8ed1ab_0.conda#d5cd48392c67fb6849ba459c2c2b671f +https://conda.anaconda.org/conda-forge/noarch/setuptools-75.3.0-pyhd8ed1ab_0.conda#2ce9825396daf72baabaade36cee16da https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2#6d6552722448103793743dabfbda532d @@ -240,21 +240,21 @@ https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.4-nompi_h2d575fe_101.c https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda#54198435fce4d64d8a89af22573012a8 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda#7b86ecb7d3557821c649b3c31e3eb9f2 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-25_linux64_openblas.conda#5dbd1b0fc0d01ec5e0e1fbe667281a11 -https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp19.1-19.1.2-default_hb5137d0_1.conda#7e574c7499bc41f92537634a23fed79a -https://conda.anaconda.org/conda-forge/linux-64/libclang13-19.1.2-default_h9c6a7e4_1.conda#cb5c5ff12b37aded00d9aaa7b9a86a78 +https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp19.1-19.1.3-default_hb5137d0_0.conda#311e6a1d041db3d6a8a8437750d4234f +https://conda.anaconda.org/conda-forge/linux-64/libclang13-19.1.3-default_h9c6a7e4_0.conda#b8a8cd77810b20754f358f2327812552 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-25_linux64_openblas.conda#4dc03a53fc69371a6158d0ed37214cd3 https://conda.anaconda.org/conda-forge/linux-64/libva-2.22.0-h8a09558_1.conda#139262125a3eac8ff6eef898598745a3 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_0.conda#dfe0528d0f1c16c1f7c528ea5536ab30 https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.8-hedd0468_0.conda#dcd0ed5147d8876b0848a552b416ce76 https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda#0badf9c54e24cecfb0ad2f99d680c163 https://conda.anaconda.org/conda-forge/linux-64/pillow-11.0.0-py311h49e9ac3_0.conda#2bd3d0f839ec0d1eaca817c9d1feb7c2 -https://conda.anaconda.org/conda-forge/noarch/pip-24.2-pyh8b19718_1.conda#6c78fbb8ddfd64bcb55b5cbafd2d2c43 +https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_0.conda#5dd546fe99b44fda83963d15f84263b7 https://conda.anaconda.org/conda-forge/linux-64/proj-9.5.0-h12925eb_0.conda#8c29983ebe50cc7e0998c34bc7614222 https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.3-pyhd8ed1ab_0.conda#c03d61f31f38fdb9facf70c29958bf7a https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda#2cf4264fffb9e6eff6031c5b6884d61c https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-h84d6215_0.conda#ee6f7fd1e76061ef1fa307d41fa86a96 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_0.conda#52d648bd608f5737b123f510bb5514b5 -https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.27.0-pyhd8ed1ab_0.conda#a6ed1227ba6ec37cfc2b25e6512f729f +https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.27.1-pyhd8ed1ab_0.conda#dae21509d62aa7bf676279ced3edcb3f https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda#7bbe9a0cc0df0ac5f5a8ad6d6a11af2f https://conda.anaconda.org/conda-forge/linux-64/yarl-1.16.0-py311h9ecbd09_0.conda#d9c23163e7ac5f8926372c7d792a996f https://conda.anaconda.org/conda-forge/linux-64/aiohttp-3.10.10-py311h2dc5d0c_0.conda#4f0fa0019a6e7be77db3609a707a4581 @@ -269,7 +269,8 @@ https://conda.anaconda.org/conda-forge/linux-64/numpy-2.1.2-py311h71ddf71_0.cond https://conda.anaconda.org/conda-forge/linux-64/pango-1.54.0-h4c5309f_1.conda#7df02e445367703cd87a574046e3a6f0 https://conda.anaconda.org/conda-forge/noarch/pbr-6.1.0-pyhd8ed1ab_0.conda#5a166b998fd17cdaaaadaccdd71a363f https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.7.0-py311h0f98d5a_0.conda#22531205a97c116251713008d65dfefd -https://conda.anaconda.org/conda-forge/noarch/pytest-cov-5.0.0-pyhd8ed1ab_0.conda#c54c0107057d67ddf077751339ec2c63 +https://conda.anaconda.org/conda-forge/noarch/pytest-cov-6.0.0-pyhd8ed1ab_0.conda#cb8a11b6d209e3d85e5094bdbd9ebd9c +https://conda.anaconda.org/conda-forge/noarch/pytest-mock-3.14.0-pyhd8ed1ab_0.conda#4b9b5e086812283c052a9105ab1e254e https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_0.conda#b39568655c127a9c4a44d178ac99b6d0 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-8.1.0-pyhd8ed1ab_0.conda#ba9f7f0ec4f2a18de3e7bce67c4a431e https://conda.anaconda.org/conda-forge/linux-64/tbb-devel-2021.13.0-h94b29a5_0.conda#4431bd4ace17dd09b97caf68509b016b @@ -308,7 +309,7 @@ https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.3-pyhd8ed1ab_0.conda#6 https://conda.anaconda.org/conda-forge/noarch/wslink-2.2.1-pyhd8ed1ab_0.conda#74674b93806167c26da4eca7613bc225 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.2.0-py311h9f3472d_6.conda#ac7dc7f70f8d2c1d96ecb7e4cb196498 https://conda.anaconda.org/conda-forge/noarch/distributed-2024.10.0-pyhd8ed1ab_0.conda#b3b498f7bcc9a2543ad72a3501f3d87b -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.6.1-nompi_h6063b07_4.conda#3108bfa76cd8a3ebc5546797106946e5 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.7.0-nompi_h6063b07_0.conda#d5ee837e9e21dabb505a010c6a196fa6 https://conda.anaconda.org/conda-forge/linux-64/ffmpeg-6.1.2-gpl_h8657690_705.conda#bba34ade586dc53222d5e0387f7733c2 https://conda.anaconda.org/conda-forge/linux-64/graphviz-12.0.0-hba01fac_0.conda#953e31ea00d46beb7e64a79fc291ec44 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a @@ -320,7 +321,7 @@ https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda https://conda.anaconda.org/conda-forge/linux-64/vtk-base-9.3.1-qt_py311h7158b74_209.conda#011801a68c022cf9692a4567d84678ca https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.24.0-py311h7db5c69_0.conda#20ba399d57a2b5de789a5b24341481a1 https://conda.anaconda.org/conda-forge/noarch/cmocean-4.0.3-pyhd8ed1ab_0.conda#53df00540de0348ed1b2a62684dd912b -https://conda.anaconda.org/conda-forge/noarch/esmpy-8.6.1-pyhc1e730c_0.conda#25a9661177fd68bfdb4314fd658e5c3b +https://conda.anaconda.org/conda-forge/noarch/esmpy-8.7.0-pyhecae5ae_0.conda#80851ac5ec3916496d7f353351c48846 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_0.conda#8dab97d8a9616e07d779782995710aed https://conda.anaconda.org/conda-forge/linux-64/vtk-io-ffmpeg-9.3.1-qt_py311hc8241c7_209.conda#13fdaae5c7c5c76089ca76f63b287ef5 @@ -337,3 +338,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_0.conda#d6e5ea5fe00164ac6c2dcc5d76a42192 https://conda.anaconda.org/conda-forge/noarch/sphinx-8.1.3-pyhd8ed1ab_0.conda#05706dd5a145a9c91861495cd435409a https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.10-pyhd8ed1ab_0.conda#e507335cb4ca9cff4c3d0fa9cdab255e + diff --git a/requirements/locks/py312-linux-64.lock b/requirements/locks/py312-linux-64.lock index 99dc274e80..cbec79a901 100644 --- a/requirements/locks/py312-linux-64.lock +++ b/requirements/locks/py312-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: c193458a42ce9c0214cd77bd4813343270edb438eceaf46d40cf7ea29a433b56 +# input_hash: 8cb273c57f190b95e7db1b3aae01b38ca08c48334bd8a71035d82f412ddd84bc @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda#c27d1c142233b5bc9ca570c6e2e0c244 @@ -57,7 +57,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.5-h4ab18f5_0.conda#60 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hd590300_0.conda#48f4330bfcd959c3cfb704d424903c82 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.44-hadc24fc_0.conda#f4cc49d7aa68316213e4b12be35308d1 -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.0-hadc24fc_0.conda#540296f0ce9d3352188c15a89b30b9ac +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.0-hadc24fc_1.conda#b6f02b52a174e612e89548f4663ce56a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.0-h0841786_0.conda#1f5a58e686b13bcfde88b93f547d23fe https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda#8371ac6457591af2cf6159439c1fd051 https://conda.anaconda.org/conda-forge/linux-64/libudunits2-2.2.28-h40f5838_3.conda#4bdace082e911a3e1f1f0b721bed5b56 @@ -117,17 +117,17 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.10-h4f16b4b_0.co https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda#4d056880988120e29d75bfff282e0f45 https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-hef167b5_0.conda#54fe76ab3d0189acaef95156874db7f9 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.1.0-hb9d3cd8_2.conda#98514fe74548d768907ce7a13f680e8f -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda#0f69b688f52ff6da70bccb7ff7001d1d +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda#8f5b0b297b59e1ac160ad4beec99dbee https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda#3f43953b7d3fb3aaa1d0d0723d91e368 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.82.2-h2ff4ddf_0.conda#13e8e54035ddd2b91875ba399f0f7c04 https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_1.conda#80a57756c545ad11f9847835aa21e6b2 https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_0.conda#9ebc9aedafaa2515ab247ff6bb509458 https://conda.anaconda.org/conda-forge/linux-64/libtheora-1.1.1-h4ab18f5_1006.conda#553281a034e9cf8693c9df49f6c78ea1 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-he137b08_1.conda#63872517c98aa305da58a757c443698e -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-he7c6b58_4.conda#08a9265c637230c37cb1be4a6cad4536 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.4-hb346dea_2.conda#69b90b70c434b916abf5a1d5ee5d55fb https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-9.0.1-he0572af_2.conda#57a9e7ee3c0840d3c8c9012473978629 https://conda.anaconda.org/conda-forge/linux-64/python-3.12.7-hc5c86c4_0_cpython.conda#0515111a9cdf69f83278f7c197db9807 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.47.0-h9eae976_0.conda#c4cb444844615e1cd4c9d989f770bcc5 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.47.0-h9eae976_1.conda#53abf1ef70b9ae213b22caa5350f97a9 https://conda.anaconda.org/conda-forge/noarch/wayland-protocols-1.37-hd8ed1ab_0.conda#73ec79a77d31eb7e4a3276cd246b776c https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda#a0901183f08b6c7107aab109733a3c91 https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.43-hb9d3cd8_0.conda#f725c7425d6d7c15e31f3b99a88ea02f @@ -177,7 +177,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-hd3e95f3_10.conda#30 https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_1.conda#204892bce2e44252b5cf272712f10bdd https://conda.anaconda.org/conda-forge/linux-64/libglu-9.0.0-ha6d2627_1004.conda#df069bea331c8486ac21814969301c1f https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.1-default_hecaa2ac_1000.conda#f54aeebefb5c5ff84eca4fb05ca8aa3a -https://conda.anaconda.org/conda-forge/linux-64/libllvm19-19.1.2-ha7bfdaf_0.conda#128e74a4f8f4fef4dc5130a8bbccc15d +https://conda.anaconda.org/conda-forge/linux-64/libllvm19-19.1.3-ha7bfdaf_0.conda#8bd654307c455162668cd66e36494000 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.7.0-h2c5496b_1.conda#e2eaefa4de2b7237af7c907b8bbc760a https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/linux-64/loguru-0.7.2-py312h7900ff3_2.conda#fddd3092f921be8e01b18f2a0266d98f @@ -201,7 +201,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.5.0-py312h66e93f https://conda.anaconda.org/conda-forge/noarch/pytz-2024.1-pyhd8ed1ab_0.conda#3eeeeb9e4827ace8c0c1419c85d590ad https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py312h66e93f0_1.conda#549e5930e768548a89c23f595dac5a95 https://conda.anaconda.org/conda-forge/noarch/scooby-0.10.0-pyhd8ed1ab_0.conda#9e57330f431abbb4c88a5f898a4ba223 -https://conda.anaconda.org/conda-forge/noarch/setuptools-75.1.0-pyhd8ed1ab_0.conda#d5cd48392c67fb6849ba459c2c2b671f +https://conda.anaconda.org/conda-forge/noarch/setuptools-75.3.0-pyhd8ed1ab_0.conda#2ce9825396daf72baabaade36cee16da https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2#6d6552722448103793743dabfbda532d @@ -240,21 +240,21 @@ https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.4-nompi_h2d575fe_101.c https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda#54198435fce4d64d8a89af22573012a8 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda#7b86ecb7d3557821c649b3c31e3eb9f2 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-25_linux64_openblas.conda#5dbd1b0fc0d01ec5e0e1fbe667281a11 -https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp19.1-19.1.2-default_hb5137d0_1.conda#7e574c7499bc41f92537634a23fed79a -https://conda.anaconda.org/conda-forge/linux-64/libclang13-19.1.2-default_h9c6a7e4_1.conda#cb5c5ff12b37aded00d9aaa7b9a86a78 +https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp19.1-19.1.3-default_hb5137d0_0.conda#311e6a1d041db3d6a8a8437750d4234f +https://conda.anaconda.org/conda-forge/linux-64/libclang13-19.1.3-default_h9c6a7e4_0.conda#b8a8cd77810b20754f358f2327812552 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-25_linux64_openblas.conda#4dc03a53fc69371a6158d0ed37214cd3 https://conda.anaconda.org/conda-forge/linux-64/libva-2.22.0-h8a09558_1.conda#139262125a3eac8ff6eef898598745a3 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_0.conda#dfe0528d0f1c16c1f7c528ea5536ab30 https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.8-hedd0468_0.conda#dcd0ed5147d8876b0848a552b416ce76 https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda#0badf9c54e24cecfb0ad2f99d680c163 https://conda.anaconda.org/conda-forge/linux-64/pillow-11.0.0-py312h7b63e92_0.conda#385f46a4df6f97892503a841121a9acf -https://conda.anaconda.org/conda-forge/noarch/pip-24.2-pyh8b19718_1.conda#6c78fbb8ddfd64bcb55b5cbafd2d2c43 +https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_0.conda#5dd546fe99b44fda83963d15f84263b7 https://conda.anaconda.org/conda-forge/linux-64/proj-9.5.0-h12925eb_0.conda#8c29983ebe50cc7e0998c34bc7614222 https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.3-pyhd8ed1ab_0.conda#c03d61f31f38fdb9facf70c29958bf7a https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda#2cf4264fffb9e6eff6031c5b6884d61c https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-h84d6215_0.conda#ee6f7fd1e76061ef1fa307d41fa86a96 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_0.conda#52d648bd608f5737b123f510bb5514b5 -https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.27.0-pyhd8ed1ab_0.conda#a6ed1227ba6ec37cfc2b25e6512f729f +https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.27.1-pyhd8ed1ab_0.conda#dae21509d62aa7bf676279ced3edcb3f https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda#7bbe9a0cc0df0ac5f5a8ad6d6a11af2f https://conda.anaconda.org/conda-forge/linux-64/yarl-1.16.0-py312h66e93f0_0.conda#c3f4a6b56026c22319bf31514662b283 https://conda.anaconda.org/conda-forge/linux-64/aiohttp-3.10.10-py312h178313f_0.conda#d2f9e490ab2eae3e661b281346618a82 @@ -269,7 +269,8 @@ https://conda.anaconda.org/conda-forge/linux-64/numpy-2.1.2-py312h58c1407_0.cond https://conda.anaconda.org/conda-forge/linux-64/pango-1.54.0-h4c5309f_1.conda#7df02e445367703cd87a574046e3a6f0 https://conda.anaconda.org/conda-forge/noarch/pbr-6.1.0-pyhd8ed1ab_0.conda#5a166b998fd17cdaaaadaccdd71a363f https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.7.0-py312he630544_0.conda#427799f15b36751761941f4cbd7d780f -https://conda.anaconda.org/conda-forge/noarch/pytest-cov-5.0.0-pyhd8ed1ab_0.conda#c54c0107057d67ddf077751339ec2c63 +https://conda.anaconda.org/conda-forge/noarch/pytest-cov-6.0.0-pyhd8ed1ab_0.conda#cb8a11b6d209e3d85e5094bdbd9ebd9c +https://conda.anaconda.org/conda-forge/noarch/pytest-mock-3.14.0-pyhd8ed1ab_0.conda#4b9b5e086812283c052a9105ab1e254e https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_0.conda#b39568655c127a9c4a44d178ac99b6d0 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-8.1.0-pyhd8ed1ab_0.conda#ba9f7f0ec4f2a18de3e7bce67c4a431e https://conda.anaconda.org/conda-forge/linux-64/tbb-devel-2021.13.0-h94b29a5_0.conda#4431bd4ace17dd09b97caf68509b016b @@ -308,7 +309,7 @@ https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.3-pyhd8ed1ab_0.conda#6 https://conda.anaconda.org/conda-forge/noarch/wslink-2.2.1-pyhd8ed1ab_0.conda#74674b93806167c26da4eca7613bc225 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.2.0-py312hc0a28a1_6.conda#fa4853d25b6fbfef5eb7b3e1b5616dd5 https://conda.anaconda.org/conda-forge/noarch/distributed-2024.10.0-pyhd8ed1ab_0.conda#b3b498f7bcc9a2543ad72a3501f3d87b -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.6.1-nompi_h6063b07_4.conda#3108bfa76cd8a3ebc5546797106946e5 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.7.0-nompi_h6063b07_0.conda#d5ee837e9e21dabb505a010c6a196fa6 https://conda.anaconda.org/conda-forge/linux-64/ffmpeg-6.1.2-gpl_h8657690_705.conda#bba34ade586dc53222d5e0387f7733c2 https://conda.anaconda.org/conda-forge/linux-64/graphviz-12.0.0-hba01fac_0.conda#953e31ea00d46beb7e64a79fc291ec44 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a @@ -320,7 +321,7 @@ https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda https://conda.anaconda.org/conda-forge/linux-64/vtk-base-9.3.1-qt_py312hc73667e_209.conda#e2967eddf4ea06a8b645da9967f370be https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.24.0-py312hf9745cd_0.conda#ea213e31805199cb7d0da457b879ceed https://conda.anaconda.org/conda-forge/noarch/cmocean-4.0.3-pyhd8ed1ab_0.conda#53df00540de0348ed1b2a62684dd912b -https://conda.anaconda.org/conda-forge/noarch/esmpy-8.6.1-pyhc1e730c_0.conda#25a9661177fd68bfdb4314fd658e5c3b +https://conda.anaconda.org/conda-forge/noarch/esmpy-8.7.0-pyhecae5ae_0.conda#80851ac5ec3916496d7f353351c48846 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_0.conda#8dab97d8a9616e07d779782995710aed https://conda.anaconda.org/conda-forge/linux-64/vtk-io-ffmpeg-9.3.1-qt_py312hc8241c7_209.conda#1354402d09a8614821d6d3c13d826863 @@ -337,3 +338,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_0.conda#d6e5ea5fe00164ac6c2dcc5d76a42192 https://conda.anaconda.org/conda-forge/noarch/sphinx-8.1.3-pyhd8ed1ab_0.conda#05706dd5a145a9c91861495cd435409a https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.10-pyhd8ed1ab_0.conda#e507335cb4ca9cff4c3d0fa9cdab255e + diff --git a/requirements/py310.yml b/requirements/py310.yml index f7285938f6..d81d4c0d42 100644 --- a/requirements/py310.yml +++ b/requirements/py310.yml @@ -44,6 +44,7 @@ dependencies: - psutil - pytest - pytest-cov + - pytest-mock - pytest-xdist - requests diff --git a/requirements/py311.yml b/requirements/py311.yml index e6f5e62a2b..b12c46c87f 100644 --- a/requirements/py311.yml +++ b/requirements/py311.yml @@ -44,6 +44,7 @@ dependencies: - psutil - pytest - pytest-cov + - pytest-mock - pytest-xdist - requests diff --git a/requirements/py312.yml b/requirements/py312.yml index b16f25b501..74277e417f 100644 --- a/requirements/py312.yml +++ b/requirements/py312.yml @@ -44,6 +44,7 @@ dependencies: - psutil - pytest - pytest-cov + - pytest-mock - pytest-xdist - requests