Skip to content

Commit 6b0087c

Browse files
committed
Make test_merge check against dataless input in all its tests.
1 parent 37fc7f5 commit 6b0087c

File tree

2 files changed

+119
-75
lines changed

2 files changed

+119
-75
lines changed

lib/iris/tests/integration/merge/test_merge.py

Lines changed: 119 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import pytest
1111

1212
from iris.coords import AuxCoord, DimCoord
13+
import iris.cube
1314
from iris.cube import Cube, CubeList
1415
from iris.tests._shared_utils import (
1516
assert_array_equal,
@@ -19,6 +20,114 @@
1920
)
2021
import iris.tests.stock
2122

23+
_ORIGINAL_MERGE = iris.cube.CubeList.merge
24+
_ORIGINAL_MERGE_CUBE = iris.cube.CubeList.merge_cube
25+
26+
# Testing options for checking that merge works ~same when some inputs are dataless
27+
_DATALESS_TEST_OPTIONS = [
28+
"dataless_none",
29+
"dataless_one",
30+
"dataless_all",
31+
"dataless_allbut1",
32+
]
33+
34+
35+
@pytest.fixture(params=_DATALESS_TEST_OPTIONS)
36+
def dataless_option(request):
37+
return request.param
38+
39+
40+
def mangle_cubelist(cubelist, dataless_option):
41+
"""Return a modified cubelist, where some cubes are dataless.
42+
43+
'dataless_option' controls whether 0, 1, N or N-1 cubes are made dataless.
44+
"""
45+
assert isinstance(cubelist, CubeList)
46+
n_cubes = len(cubelist)
47+
result = CubeList([])
48+
ind_one = len(cubelist) // 3
49+
for i_cube, cube in enumerate(cubelist):
50+
if (
51+
(dataless_option == "dataless_one" and i_cube == ind_one)
52+
or (dataless_option == "dataless_allbut1" and i_cube != ind_one)
53+
or dataless_option == "dataless_all"
54+
):
55+
# Make this one dataless
56+
cube = cube.copy()
57+
cube.data = None
58+
59+
result.append(cube)
60+
61+
# Do a quick post-test
62+
assert len(result) == len(cubelist)
63+
count = sum([cube.is_dataless() for cube in result])
64+
expected = {
65+
"dataless_none": 0,
66+
"dataless_one": 1,
67+
"dataless_all": n_cubes,
68+
"dataless_allbut1": n_cubes - 1,
69+
}[dataless_option]
70+
assert count == expected
71+
72+
return result
73+
74+
75+
def check_merge_against_dataless_cases(
76+
function, original_input, *args, dataless_option=None
77+
):
78+
# Compute the "normal" result.
79+
original_result = function(original_input, *args)
80+
81+
if dataless_option != "dataless_none":
82+
# Re-run with "mangled" inputs, and compare the result with the normal case.
83+
mangled_input = mangle_cubelist(original_input, dataless_option)
84+
mangled_result = function(mangled_input, *args)
85+
86+
# Normalise to get a list of cubes
87+
if isinstance(original_result, Cube): # I.E. not if a single Cube
88+
result_cubes = [original_result]
89+
mangled_cubes = [mangled_result]
90+
else:
91+
result_cubes = original_result
92+
mangled_cubes = mangled_result
93+
94+
# If **all** input is dataless, all output should be dataless too
95+
if dataless_option == "dataless_all":
96+
assert all([cube.is_dataless() for cube in mangled_cubes])
97+
98+
# We should get all the same cubes, **except** for the data content
99+
assert len(mangled_cubes) == len(result_cubes)
100+
for cube1, cube2 in zip(mangled_cubes, result_cubes):
101+
cube1, cube2 = [cube.copy() for cube in (cube1, cube2)]
102+
for cube in (cube1, cube2):
103+
cube.data = None
104+
if cube1 != cube2:
105+
assert cube1 == cube2
106+
107+
return original_result
108+
109+
110+
class DatalessMixin:
111+
# Mixin class to make every merge check for operation with dataless cubes
112+
@pytest.fixture(autouse=True)
113+
def setup_patch(self, mocker, dataless_option):
114+
# NB these patch functions must be generated dynamically (for each test
115+
# parametrisation), so that they can access the 'dataless_option' switch.
116+
def patched_merge(cubelist, unique=True):
117+
return check_merge_against_dataless_cases(
118+
_ORIGINAL_MERGE, cubelist, unique, dataless_option=dataless_option
119+
)
120+
121+
def patched_merge_cube(cubelist):
122+
return check_merge_against_dataless_cases(
123+
_ORIGINAL_MERGE_CUBE, cubelist, dataless_option=dataless_option
124+
)
125+
126+
# Patch **all** uses of CubeList.merge/merge_cube within these tests, to compare
127+
# "normal" results with those which have some dataless inputs.
128+
mocker.patch("iris.cube.CubeList.merge", patched_merge)
129+
mocker.patch("iris.cube.CubeList.merge_cube", patched_merge_cube)
130+
22131

23132
class MergeMixin:
24133
"""Mix-in class for attributes & utilities common to these test cases."""
@@ -45,15 +154,15 @@ def test_duplication(self):
45154

46155

47156
@skip_data
48-
class TestSingleCube(MergeMixin):
157+
class TestSingleCube(MergeMixin, DatalessMixin):
49158
def setup_method(self):
50159
self._data_path = get_data_path(("PP", "globClim1", "theta.pp"))
51160
self._num_cubes = 1
52161
self._prefix = "theta"
53162

54163

55164
@skip_data
56-
class TestMultiCube(MergeMixin):
165+
class TestMultiCube(MergeMixin, DatalessMixin):
57166
def setup_method(self):
58167
self._data_path = get_data_path(("PP", "globClim1", "dec_subset.pp"))
59168
self._num_cubes = 4
@@ -75,7 +184,7 @@ def custom_coord_callback(cube, field, filename):
75184

76185

77186
@skip_data
78-
class TestColpex:
187+
class TestColpex(DatalessMixin):
79188
def setup_method(self):
80189
self._data_path = get_data_path(("PP", "COLPEX", "small_colpex_theta_p_alt.pp"))
81190

@@ -86,7 +195,7 @@ def test_colpex(self, request):
86195

87196

88197
@skip_data
89-
class TestDataMerge:
198+
class TestDataMerge(DatalessMixin):
90199
def test_extended_proxy_data(self, request):
91200
# Get the empty theta cubes for T+1.5 and T+2
92201
data_path = get_data_path(("PP", "COLPEX", "theta_and_orog_subset.pp"))
@@ -119,7 +228,7 @@ def test_real_data(self, request):
119228
assert_CML(request, cubes, ["merge", "theta.cml"])
120229

121230

122-
class TestDimensionSplitting:
231+
class TestDimensionSplitting(DatalessMixin):
123232
def _make_cube(self, a, b, c, data):
124233
cube_data = np.empty((4, 5), dtype=np.float32)
125234
cube_data[:] = data
@@ -182,7 +291,7 @@ def test_multi_split(self, request):
182291
assert_CML(request, cube, ("merge", "multi_split.cml"))
183292

184293

185-
class TestCombination:
294+
class TestCombination(DatalessMixin):
186295
def _make_cube(self, a, b, c, d, data=0):
187296
cube_data = np.empty((4, 5), dtype=np.float32)
188297
cube_data[:] = data
@@ -245,7 +354,7 @@ def add(*args):
245354
)
246355

247356

248-
class TestDimSelection:
357+
class TestDimSelection(DatalessMixin):
249358
def _make_cube(self, a, b, data=0, a_dim=False, b_dim=False):
250359
cube_data = np.empty((4, 5), dtype=np.float32)
251360
cube_data[:] = data
@@ -360,7 +469,7 @@ def test_a_dim_b_dim(self, request):
360469
assert cube.coord("b") in cube.aux_coords
361470

362471

363-
class TestTimeTripleMerging:
472+
class TestTimeTripleMerging(DatalessMixin):
364473
def _make_cube(self, a, b, c, data=0):
365474
cube_data = np.empty((4, 5), dtype=np.float32)
366475
cube_data[:] = data
@@ -583,7 +692,7 @@ def test_simple3(self, request):
583692
assert_CML(request, cube, ("merge", "time_triple_merging5.cml"), checksum=False)
584693

585694

586-
class TestCubeMergeTheoretical:
695+
class TestCubeMergeTheoretical(DatalessMixin):
587696
def test_simple_bounds_merge(self, request):
588697
cube1 = iris.tests.stock.simple_2d()
589698
cube2 = iris.tests.stock.simple_2d()
@@ -649,7 +758,7 @@ def test_simple_points_merge(self, request):
649758
assert_CML(request, r, ("cube_merge", "test_simple_attributes3.cml"))
650759

651760

652-
class TestContiguous:
761+
class TestContiguous(DatalessMixin):
653762
def test_form_contiguous_dimcoord(self):
654763
# Test that cube sliced up and remerged in the opposite order maintains
655764
# contiguity.

lib/iris/tests/integration/merge/test_merge_with_dataless.py

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)