Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional tests for loading controls. #79

Open
wants to merge 2 commits into
base: load_factory_dims_rearrange
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 29 additions & 5 deletions lib/iris/io/loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,10 @@ def set(self, options: str | dict | None = None, **kwargs):
options = self.SETTINGS[options]
elif not isinstance(options, Mapping):
msg = (
f"Invalid arg options='{options!r}' : "
f"must be a dict, or one of {self.SETTINGS.keys()}"
f"Invalid arg options={options!r} : "
f"must be a dict, or one of {tuple(self.SETTINGS.keys())}"
)
raise ValueError(msg)
raise TypeError(msg)

# Override any options with keywords
options.update(**kwargs)
Expand Down Expand Up @@ -405,17 +405,41 @@ def combine_cubes(cubes, options=None, merge_require_unique=False, **kwargs):

Returns
-------
list of :class:`~iris.cube.Cube`
:class:`~iris.cube.CubeList`

.. Note::
The ``support_multiple_references`` keyword/property has no effect on the
:func:`combine_cubes` operation : it only takes effect during a load operation.

"""
if not options:
from iris.cube import CubeList

if isinstance(options, str):
# string arg names a standard "settings"
options = LoadPolicy.SETTINGS.get(options)
if options is None:
msg = (
f"options={options!r} is not a valid settings name : "
f"must be one of {tuple(LoadPolicy.SETTINGS)}."
)
raise ValueError(msg)
elif options is None:
# empty arg gets current defaults
options = LOAD_POLICY.settings()
elif isinstance(options, dict):
# dict arg overrides the current defaults
settings = LOAD_POLICY.settings()
settings.update(options)
options = settings
else:
msg = f"Bad 'options' arg {options!r} : " "type must be None, dict or str."
raise TypeError(msg)

options.update(kwargs)

if not isinstance(cubes, CubeList):
cubes = CubeList(cubes)

while True:
n_original_cubes = len(cubes)
sequence = options["merge_concat_sequence"]
Expand Down
144 changes: 144 additions & 0 deletions lib/iris/tests/unit/io/loading/test_LoadPolicy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# 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.
"""Unit tests for the :mod:`iris.io.loading.LoadPolicy` package."""

from unittest import mock

import pytest

from iris.io.loading import LoadPolicy


class TestInit:
def test_init_empty(self):
# Check how a bare init works
options = LoadPolicy()
assert options.settings() == LoadPolicy.SETTINGS["default"]

def test_init_args_kwargs(self):
# Check that init with args, kwargs equates to a pair of set() calls.
with mock.patch("iris.io.loading.LoadPolicy.set") as mock_set:
test_option = mock.sentinel.option
test_kwargs = {"junk": "invalid"}
LoadPolicy(options=test_option, **test_kwargs)
assert mock_set.call_args_list == [
mock.call("default"),
mock.call(test_option, **test_kwargs),
]


class Test_settings:
"""The .settings() returns a dict full of the settings."""

def test_settings(self):
options = LoadPolicy()
settings = options.settings()
assert isinstance(settings, dict)
assert tuple(settings.keys()) == LoadPolicy.OPTION_KEYS
for key in LoadPolicy.OPTION_KEYS:
assert settings[key] == getattr(options, key)


class Test_set:
"""Check the .set(arg, **kwargs) behaviour."""

def test_empty(self):
options = LoadPolicy()
orig_settings = options.settings()
options.set()
assert options.settings() == orig_settings

def test_arg_dict(self):
options = LoadPolicy()
assert options.settings()["merge_concat_sequence"] == "m"
assert options.settings()["repeat_until_unchanged"] is False
options.set({"merge_concat_sequence": "c", "repeat_until_unchanged": True})
assert options.settings()["merge_concat_sequence"] == "c"
assert options.settings()["repeat_until_unchanged"] is True

def test_arg_string(self):
options = LoadPolicy()
assert options.settings()["merge_concat_sequence"] == "m"
assert options.settings()["repeat_until_unchanged"] is False
options.set("comprehensive")
assert options.settings()["merge_concat_sequence"] == "mc"
assert options.settings()["repeat_until_unchanged"] is True

def test_arg_bad_dict(self):
options = LoadPolicy()
expected = "Unknown options.*'junk'.* : valid options are"
with pytest.raises(ValueError, match=expected):
options.set({"junk": "invalid"})

def test_arg_bad_string(self):
options = LoadPolicy()
expected = "Invalid arg options='unknown' : must be a dict, or one of"
with pytest.raises(TypeError, match=expected):
options.set("unknown")

def test_arg_bad_type(self):
options = LoadPolicy()
expected = "must be a dict, or one of"
with pytest.raises(TypeError, match=expected):
options.set((1, 2, 3))

def test_kwargs(self):
options = LoadPolicy()
assert options.settings()["merge_concat_sequence"] == "m"
assert options.settings()["repeat_until_unchanged"] is False
options.set(merge_concat_sequence="c", repeat_until_unchanged=True)
assert options.settings()["merge_concat_sequence"] == "c"
assert options.settings()["repeat_until_unchanged"] is True

def test_arg_kwargs(self):
# Show that kwargs override arg
options = LoadPolicy(
support_multiple_references=False,
merge_concat_sequence="",
repeat_until_unchanged=False,
)
options.set(
dict(merge_concat_sequence="c", repeat_until_unchanged=True),
merge_concat_sequence="mc",
)
assert options.merge_concat_sequence == "mc"
assert options.repeat_until_unchanged is True

def test_bad_kwarg(self):
options = LoadPolicy()
expected = "Unknown options.*'junk'.* : valid options are"
with pytest.raises(ValueError, match=expected):
options.set({"junk": "invalid"})


class Test_AttributeAccess:
"""Check operation of direct property access (with ".")."""

def test_getattr(self):
options = LoadPolicy(merge_concat_sequence="m")
assert options.merge_concat_sequence == "m"

def test_getattr_badname(self):
options = LoadPolicy()
expected = "'LoadPolicy' object has no attribute 'unknown'"
with pytest.raises(AttributeError, match=expected):
options.unknown

def test_setattr(self):
options = LoadPolicy(merge_concat_sequence="m")
options.merge_concat_sequence = "mc"
assert options.merge_concat_sequence == "mc"

def test_setattr_badname(self):
options = LoadPolicy()
expected = "LoadPolicy object has no property 'anyold_property'"
with pytest.raises(KeyError, match=expected):
options.anyold_property = "x"

def test_setattr_badvalue(self):
options = LoadPolicy()
expected = "'mcm' is not a valid.*merge_concat_sequence : must be one of"
with pytest.raises(ValueError, match=expected):
options.merge_concat_sequence = "mcm"
76 changes: 76 additions & 0 deletions lib/iris/tests/unit/io/loading/test_combine_cubes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 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.
"""Unit tests for the :func:`iris.io.loading.combine_cubes` function."""

import pytest

from iris.io.loading import LoadPolicy, combine_cubes

from .test_load_functions import cu


@pytest.fixture(params=list(LoadPolicy.SETTINGS.keys()))
def options(request):
# N.B. "request" is a standard PyTest fixture
return request.param # Return the name of the attribute to test.


class Test:
def test_mergeable(self, options):
c1, c2 = cu(t=1), cu(t=2)
c12 = cu(t=(1, 2))
input_cubes = [c1, c2]
result = combine_cubes(input_cubes, options)
expected = [c12] # same in all cases
assert result == expected

def test_catable(self, options):
c1, c2 = cu(t=(1, 2)), cu(t=(3, 4))
c12 = cu(t=(1, 2, 3, 4))
input_cubes = [c1, c2]
result = combine_cubes(input_cubes, options)
expected = {
"legacy": [c1, c2], # standard options can't do this ..
"default": [c1, c2],
"recommended": [c12], # .. but it works if you enable concatenate
"comprehensive": [c12],
}[options]
assert result == expected

def test_cat_enables_merge(self, options):
c1, c2 = cu(t=(1, 2), z=1), cu(t=(3, 4, 5), z=1)
c3, c4 = cu(t=(1, 2, 3), z=2), cu(t=(4, 5), z=2)
c1234 = cu(t=(1, 2, 3, 4, 5), z=(1, 2))
c12 = cu(t=(1, 2, 3, 4, 5), z=1)
c34 = cu(t=(1, 2, 3, 4, 5), z=2)
input_cubes = [c1, c2, c3, c4]
result = combine_cubes(input_cubes, options)
expected = {
"legacy": input_cubes,
"default": input_cubes,
"recommended": [c12, c34], # standard "mc" sequence can't do this one..
"comprehensive": [c1234], # .. but works if you repeat
}[options]
assert result == expected

def test_cat_enables_merge__custom(self):
c1, c2 = cu(t=(1, 2), z=1), cu(t=(3, 4, 5), z=1)
c3, c4 = cu(t=(1, 2, 3), z=2), cu(t=(4, 5), z=2)
c1234 = cu(t=(1, 2, 3, 4, 5), z=(1, 2))
input_cubes = [c1, c2, c3, c4]
result = combine_cubes(input_cubes, dict(merge_concat_sequence="cm"))
assert result == [c1234]

def test_nocombine_overlapping(self, options):
c1, c2 = cu(t=(1, 3)), cu(t=(2, 4))
input_cubes = [c1, c2]
result = combine_cubes(input_cubes, options)
assert result == input_cubes # same in all cases : can't do this

def test_nocombine_dim_scalar(self, options):
c1, c2 = cu(t=(1,)), cu(t=2)
input_cubes = [c1, c2]
result = combine_cubes(input_cubes, options)
assert result == input_cubes # can't do this at present
Loading