Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
9fd0148
Add pytest-mock dependency (#5811)
bjlittle Mar 11, 2024
7c9c4e1
Updated environment lockfiles
web-flow Mar 11, 2024
fa88fb3
added pytest iris class (#5808)
ESadek-MO Mar 11, 2024
80b42f5
Merge pull request #5812 from SciTools/auto-update-lockfiles
bjlittle Mar 11, 2024
1ca9400
make unit/config pytest (#5810)
stephenworsley Mar 11, 2024
2e7bddc
Convert all tests to pytest.
pp-mo Mar 11, 2024
58d541c
Don't use staticmethod on fixtures.
pp-mo Mar 11, 2024
40cd749
Everything in 'TestConcatenate__dask' can use the same sample cubes.
pp-mo Mar 11, 2024
0f36090
Spurious parentheses in 'pytest.fixture()'
pp-mo Mar 11, 2024
fa86a4f
Convert all tests to pytest.
pp-mo Mar 11, 2024
9aaa71f
Merge pull request #5813 from pp-mo/pytests_unit_concatenate__test_co…
bjlittle Mar 11, 2024
5586c6b
Merge pull request #5816 from pp-mo/pytests_unit_concatenate__test_cu…
bjlittle Mar 11, 2024
9f71331
Rewrite `result_path()` for pytest (#5817)
trexfeathers Mar 12, 2024
e954d73
Configure pytest-mock as pytest required-plugin (#5815)
bjlittle Mar 12, 2024
d807650
pytest migration ruff PT compliance for unit.concatenate (#5823)
bjlittle Mar 12, 2024
d0f4f2b
pytest migration for unit.common.test_Lenient (#5822)
bjlittle Mar 13, 2024
fc4d1aa
Convert to pytest.
pp-mo Mar 13, 2024
096b2b3
Better implementation of `_shared_utils.results_path()` - using PyTes…
trexfeathers Mar 13, 2024
b3eb2d1
Convert to pytest.
pp-mo Mar 13, 2024
5000fd3
Convert to pytest.
pp-mo Mar 14, 2024
f66c885
Merge pull request #5829 from pp-mo/pytests_unit_auxfact__auxcoordfac…
bjlittle Mar 14, 2024
7415bcc
Merge pull request #5834 from pp-mo/pytests_unit_auxfact__atmossigma
bjlittle Mar 14, 2024
dfd25e2
Convert graphics testing conveniences to PyTest (#5832)
trexfeathers Mar 14, 2024
6a5a19f
Simplify data fixtures.
pp-mo Mar 14, 2024
b49a508
Simplify data fixtures.
pp-mo Mar 14, 2024
909bd1a
Simplify data fixtures.
pp-mo Mar 14, 2024
8b8c32b
Simplify Mock usage.
pp-mo Mar 14, 2024
64cdf14
Tidy equality checks.
pp-mo Mar 14, 2024
bed9093
Merge pull request #5838 from pp-mo/pytests_unit_auxfact__atmossigma
bjlittle Mar 14, 2024
f652341
Merge pull request #5836 from pp-mo/pytests_unit_auxfact__hybridpressure
bjlittle Mar 14, 2024
89dd822
Convert to pytest,
pp-mo Mar 14, 2024
985b18c
Merge pull request #5840 from pp-mo/pytests_unit_auxfact__oceans
bjlittle Mar 14, 2024
be3eebf
pytest migration for unit.common.lenient.test__lenient_client (#5842)
bjlittle Mar 15, 2024
114cb5e
pytest migration for unit.common.lenient.test__Lenient (#5828)
bjlittle Mar 15, 2024
2e18ae8
Convert to pytest.
pp-mo Mar 15, 2024
2466ab0
Convert `tests/test_plot` to PyTest (#5839)
trexfeathers Mar 15, 2024
b522f3c
Test aggregate pytest (#5846)
trexfeathers Mar 15, 2024
da88a70
pytest migration for unit.common.lenient.test__lenient_service (#5843)
bjlittle Mar 16, 2024
a12d398
pytest migration for unit.common.mixin.test__get_valid_standard_name …
bjlittle Mar 16, 2024
62e289e
Merge pull request #5848 from pp-mo/pytests_unit_lazydata
bjlittle Mar 18, 2024
f60c5d4
pytest migration for unit.common.mixin.test_CFVariableMixin (#5849)
bjlittle Mar 18, 2024
ed9287f
pytest migration for unit.common.lenient.test__qualname (#5845)
bjlittle Mar 19, 2024
ca9e3f6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 19, 2024
c62264f
pytest migration for unit.common.mixin.test_LimitedAttributeDict (#5851)
bjlittle Mar 19, 2024
b19650d
pytest migration for unit.common.metadata.test_NamedTupleMeta (#5852)
bjlittle Mar 19, 2024
04002bb
Make test_analysis PyTest (#5859)
trexfeathers Mar 19, 2024
dd5036a
Convert to pytest.
pp-mo Mar 19, 2024
37bb689
Convert unit/fileformats/__init__ code to pytest + move to pp_load_ru…
pp-mo Mar 19, 2024
10b21e6
Convert unit/fileformats/pp_load_rules to pytest.
pp-mo Mar 19, 2024
dbc207e
converted cube directory tests to pytest (#5837)
ESadek-MO Mar 19, 2024
ecd1ca3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 19, 2024
8ccb587
Convert tests in unit/util to pytest (#5853)
ESadek-MO Mar 19, 2024
58bb4b1
Use mocker.spy in place of mock.patch.
pp-mo Mar 20, 2024
f906328
Merge pull request #5862 from pp-mo/pytests_unit_fileformats_abf
bjlittle Mar 20, 2024
0c36ec8
pytest migration for unit.common.metadata.test_AncillaryVariableMetad…
bjlittle Mar 20, 2024
e2ea7aa
Merge pull request #5863 from pp-mo/pytests_unit_fileformats__init
bjlittle Mar 20, 2024
de5f027
Convert `unit.plot` and `.quickplot` to PyTest (#5866)
trexfeathers Mar 20, 2024
10177e4
Convert setups to autouse fixtures.
pp-mo Mar 20, 2024
3787f9a
Convert to Pytest.
pp-mo Mar 20, 2024
8f9a226
Convert to Pytest.
pp-mo Mar 20, 2024
0b060ec
Convert to pytest.
pp-mo Mar 20, 2024
73926d2
Convert to pytest, move test_rules into /rules.
pp-mo Mar 20, 2024
2bf9a4b
Merge pull request #5869 from pp-mo/pytests_unit_fileformats_nimrod
bjlittle Mar 20, 2024
c87d500
Convert to pytest
pp-mo Mar 20, 2024
a8a051f
Merge pull request #5865 from pp-mo/pytests_unit_fileformats_pploadrules
bjlittle Mar 20, 2024
745c62f
Merge pull request #5870 from pp-mo/pytests_unit_fileformats_dotpath
bjlittle Mar 20, 2024
62906d0
Merge pull request #5871 from pp-mo/pytests_unit_fileformats_nameloaders
bjlittle Mar 20, 2024
7586325
Merge pull request #5872 from pp-mo/pytests_unit_fileformats_rules
bjlittle Mar 20, 2024
64b47fc
Convert remaining graphics tests to PyTest (#5867)
trexfeathers Mar 21, 2024
9c4a9cc
Convert unit/fileformats/structured_array_identification to pytest.
pp-mo Mar 21, 2024
f76302f
Merge pull request #5877 from pp-mo/pytests_unit_fileformats_structur…
bjlittle Mar 21, 2024
04ed352
Review changes.
pp-mo Mar 21, 2024
6b9b53a
Merge pull request #5873 from pp-mo/pytests_unit_fileformats_cf
bjlittle Mar 22, 2024
f1864a7
pytest migration for unit.common.metadata.test_BaseMetadata (#5868)
bjlittle Mar 22, 2024
ffc0abd
pytest migration of unit.common.metadata.test_hexdigest (#5874)
bjlittle Mar 22, 2024
4c3879b
pytest migration of unit.common.metadata.test_metadata_filter (#5875)
bjlittle Mar 22, 2024
b581b16
pytest migration for unit.common.metadata.test_CubeMetadata (#5881)
bjlittle Mar 22, 2024
47a0cd2
pytest migration of unit.common.metadata.test_metadata_manager_factor…
bjlittle Mar 22, 2024
bd6a9d7
More root pytest (#5883)
trexfeathers Mar 22, 2024
c9b100d
pytest migration of unit.common.metadata.test_CellMeasureMetadata (#5…
bjlittle Mar 22, 2024
fce59c7
pytest migration of unit.common.metadata.test_CoordMetadata (#5880)
bjlittle Mar 27, 2024
3d2ac71
pytest migration of unit.common.resolve.test_Resolve (#5882)
bjlittle Apr 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,000 changes: 1,000 additions & 0 deletions lib/iris/tests/_shared_utils.py

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions lib/iris/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 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

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
7 changes: 4 additions & 3 deletions lib/iris/tests/graphics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
against the existing accepted baseline image, for each failing test.
57 changes: 50 additions & 7 deletions lib/iris/tests/graphics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import sys
import threading
from typing import Callable, Dict, Union
import unittest

import filelock
import pytest

# Test for availability of matplotlib.
# (And remove matplotlib as an iris.tests dependency.)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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) -> 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()
3 changes: 2 additions & 1 deletion lib/iris/tests/graphics/idiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions lib/iris/tests/graphics/recreate_imagerepo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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"
)
Expand Down
21 changes: 7 additions & 14 deletions lib/iris/tests/integration/plot/test_animate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +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.
"""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)

Expand Down Expand Up @@ -68,7 +65,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()
49 changes: 17 additions & 32 deletions lib/iris/tests/integration/plot/test_colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,24 @@
:func:`matplotlib.pyplot.colorbar`.

"""

# 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()
Expand All @@ -46,49 +43,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
20 changes: 6 additions & 14 deletions lib/iris/tests/integration/plot/test_netcdftime.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,21 @@
# 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.
"""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"
Expand All @@ -44,8 +40,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)
Loading