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

Fix tests of process to work with the new FCIS restructure #148

Merged
merged 3 commits into from
Nov 6, 2024
Merged
Changes from 1 commit
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
154 changes: 83 additions & 71 deletions test/test_um2netcdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ def coord(self, _name):
msg = f"{self.__class__}[{self.var_name}]: lacks coord for '{_name}'"
raise CoordinateNotFoundError(msg)

def coords(self):
return self._coordinates.values()

def coord_dims(self):
raise NotImplementedError

def remove_coord(self, coord):
del self._coordinates[coord]

Expand Down Expand Up @@ -206,124 +212,130 @@ def fake_out_path():
return "/tmp-does-not-exist/fake_input_fields_file.nc"
atteggiani marked this conversation as resolved.
Show resolved Hide resolved


def mock_fix_time_no_time_dim(cube, verbose):
"""
Side effect for fix_time_coord() mocks in process_cube() tests.
Replicates fix_time_coord()'s behaviour on cube's with no
time dimension.
"""
return cube, None


# FIXME: the convoluted setup in test_process_...() is a code smell
# use the following tests to gradually refactor process()
# TODO: evolve towards design where input & output file I/O is extracted from
# process() & the function takes *raw data only* (is highly testable)
def test_process_no_heaviside_drop_cubes(ta_plev_cube, precipitation_flux_cube,
geo_potential_cube, mule_vars, std_args,
fake_in_path, fake_out_path):
"""Attempt end-to-end process() test, dropping cubes requiring masking."""
def test_process_cubes_no_heaviside_drop_cubes(ta_plev_cube, precipitation_flux_cube,
geo_potential_cube, mule_vars, std_args,
fake_in_path, fake_out_path):
blimlim marked this conversation as resolved.
Show resolved Hide resolved
"""Attempt end-to-end process_cubes() test, dropping cubes requiring masking."""

# include cubes requiring both heaviside uv & t cubes to filter, to
# ensure both uv/t dependent cubes are dropped
cubes = [ta_plev_cube, precipitation_flux_cube, geo_potential_cube]

# mock fix_time_coord to avoid adding difficult to replicate
# iris methods in DummyCube.
with (
# use mocks to prevent mule data extraction file I/O
mock.patch("mule.load_umfile"),
mock.patch("umpost.um2netcdf.process_mule_vars") as m_mule_vars,
mock.patch("iris.load") as m_iris_load,
mock.patch("iris.fileformats.netcdf.Saver") as m_saver, # prevent I/O
mock.patch("umpost.um2netcdf.cubewrite"),
mock.patch("umpost.um2netcdf.fix_fill_value"),
mock.patch("umpost.um2netcdf.fix_time_coord") as m_time_coord,
):
m_mule_vars.return_value = mule_vars
m_time_coord.side_effect = mock_fix_time_no_time_dim

# include cubes requiring both heaviside uv & t cubes to filter, to
# ensure both uv/t dependent cubes are dropped
cubes = [ta_plev_cube, precipitation_flux_cube, geo_potential_cube]

m_iris_load.return_value = cubes
m_saver().__enter__ = mock.Mock(name="mock_sman")
std_args.verbose = True # test some warning branches

# trying to mask None will break in numpy
assert precipitation_flux_cube.data is None

# air temp & geo potential should be dropped in process()
with pytest.warns(RuntimeWarning):
processed = um2nc.process(fake_in_path, fake_out_path, std_args)
processed = tuple(um2nc.process_cubes(cubes, mule_vars, std_args))

assert len(processed) == 1
cube, _, dim = processed[0]

assert len(processed) == 1
cube = processed[0]
assert cube.name() == precipitation_flux_cube.name()

assert cube is precipitation_flux_cube
# contrived testing: if the masking code was reached for some reason,
# the test would fail during process()
assert cube.data is None # masking wasn't called/nothing changed

# contrived testing: if the masking code was reached for some reason,
# the test would fail during process()
assert cube.data is None # masking wasn't called/nothing changed

def test_process_cubes_all_cubes_filtered(ta_plev_cube, geo_potential_cube,
mule_vars, std_args,
fake_in_path, fake_out_path):
blimlim marked this conversation as resolved.
Show resolved Hide resolved
"""
Ensure process_cubes() exits early if all cubes are removed in filtering.
"""

def test_process_all_cubes_filtered(ta_plev_cube, geo_potential_cube,
mule_vars, std_args,
fake_in_path, fake_out_path):
"""Ensure process() exits early if all cubes are removed in filtering."""
cubes = [ta_plev_cube, geo_potential_cube]
# mock fix_time_coord to avoid adding difficult to replicate
# iris methods in DummyCube.
with (
mock.patch("mule.load_umfile"),
mock.patch("umpost.um2netcdf.process_mule_vars") as m_mule_vars,
mock.patch("iris.load") as m_iris_load,
mock.patch("iris.fileformats.netcdf.Saver") as m_saver, # prevent I/O
mock.patch("umpost.um2netcdf.fix_fill_value"),
mock.patch("umpost.um2netcdf.fix_time_coord") as m_time_coord,
):
m_mule_vars.return_value = mule_vars
m_iris_load.return_value = [ta_plev_cube, geo_potential_cube]
m_saver().__enter__ = mock.Mock(name="mock_sman")
m_time_coord.side_effect = mock_fix_time_no_time_dim

# all cubes should be dropped
assert um2nc.process(fake_in_path, fake_out_path, std_args) == []
# all cubes should be dropped
assert list(um2nc.process_cubes(cubes, mule_vars, std_args)) == []


def test_process_mask_with_heaviside(ta_plev_cube, precipitation_flux_cube,
heaviside_uv_cube, heaviside_t_cube,
geo_potential_cube, mule_vars,
std_args, fake_in_path, fake_out_path):
blimlim marked this conversation as resolved.
Show resolved Hide resolved
"""Run process() with pressure level masking cubes present."""

# air temp requires heaviside_uv & geo_potential_cube requires heaviside_t
# masking, include both to enable code execution for both masks
cubes = [ta_plev_cube, precipitation_flux_cube, geo_potential_cube,
heaviside_uv_cube, heaviside_t_cube]

# mock fix_time_coord to avoid adding difficult to replicate
# iris methods in DummyCube.
with (
mock.patch("mule.load_umfile"),
mock.patch("umpost.um2netcdf.process_mule_vars") as m_mule_vars,
mock.patch("iris.load") as m_iris_load,
mock.patch("iris.fileformats.netcdf.Saver") as m_saver, # prevent I/O
mock.patch("umpost.um2netcdf.apply_mask"), # TODO: eventually call real version
mock.patch("umpost.um2netcdf.cubewrite"),
mock.patch("umpost.um2netcdf.fix_fill_value"),
mock.patch("umpost.um2netcdf.fix_time_coord") as m_time_coord,
):
m_mule_vars.return_value = mule_vars

# air temp requires heaviside_uv & geo_potential_cube requires heaviside_t
# masking, include both to enable code execution for both masks
cubes = [ta_plev_cube, precipitation_flux_cube, geo_potential_cube,
heaviside_uv_cube, heaviside_t_cube]

m_iris_load.return_value = cubes
m_saver().__enter__ = mock.Mock(name="mock_sman")
m_time_coord.side_effect = mock_fix_time_no_time_dim

# all cubes should be processed & not dropped
processed = um2nc.process(fake_in_path, fake_out_path, std_args)
assert len(processed) == len(cubes)
processed = list(um2nc.process_cubes(cubes, mule_vars, std_args))

assert len(processed) == len(cubes)

for pc in processed:
assert pc in cubes
cube_names = {cube.name() for cube in cubes}
processed_names = {cube.name() for cube, _, _ in processed}
assert processed_names == cube_names


def test_process_no_masking_keep_all_cubes(ta_plev_cube, precipitation_flux_cube,
geo_potential_cube, mule_vars, std_args,
fake_in_path, fake_out_path):
blimlim marked this conversation as resolved.
Show resolved Hide resolved
"""Run process() with masking off, ensuring all cubes are kept & modified."""

# air temp and geo potential would need heaviside uv & t respectively
cubes = [ta_plev_cube, precipitation_flux_cube, geo_potential_cube]

# mock fix_time_coord to avoid adding difficult to replicate
# iris methods in DummyCube.
with (
mock.patch("mule.load_umfile"),
mock.patch("umpost.um2netcdf.process_mule_vars") as m_mule_vars,
mock.patch("iris.load") as m_iris_load,
mock.patch("iris.fileformats.netcdf.Saver") as m_saver, # prevent I/O
mock.patch("umpost.um2netcdf.cubewrite"),
mock.patch("umpost.um2netcdf.fix_fill_value"),
mock.patch("umpost.um2netcdf.fix_time_coord") as m_time_coord,
):
m_mule_vars.return_value = mule_vars

# air temp and geo potential would need heaviside uv & t respectively
cubes = [ta_plev_cube, precipitation_flux_cube, geo_potential_cube]
m_time_coord.side_effect = mock_fix_time_no_time_dim

m_iris_load.return_value = cubes
m_saver().__enter__ = mock.Mock(name="mock_sman")
std_args.nomask = True
processed = list(um2nc.process_cubes(cubes, mule_vars, std_args))

# all cubes should be kept with masking off
processed = um2nc.process(fake_in_path, fake_out_path, std_args)
assert len(processed) == len(cubes)
# all cubes should be kept with masking off
assert len(processed) == len(cubes)

for pc in processed:
assert pc in cubes
cube_names = {cube.name() for cube in cubes}
processed_names = {cube.name() for cube, _, _ in processed}
assert processed_names == cube_names


def test_to_stash_code():
Expand Down