-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathtest_api.py
More file actions
284 lines (208 loc) · 10.5 KB
/
test_api.py
File metadata and controls
284 lines (208 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
import numpy.testing as nt
import uxarray as ux
import numpy as np
import pytest
import tempfile
import xarray as xr
from pathlib import Path
from unittest.mock import patch
from uxarray.core.utils import _open_dataset_with_fallback
import os
TEST_MESHFILES = Path(__file__).resolve().parent.parent / "meshfiles"
def test_open_geoflow_dataset(gridpath, datasetpath):
"""Loads a single dataset with its grid topology file using uxarray's
open_dataset call."""
# Paths to Data Variable files
data_paths = [
datasetpath("ugrid", "geoflow-small", "v1.nc"),
datasetpath("ugrid", "geoflow-small", "v2.nc"),
datasetpath("ugrid", "geoflow-small", "v3.nc")
]
uxds_v1 = ux.open_dataset(gridpath("ugrid", "geoflow-small", "grid.nc"), data_paths[0])
# Ideally uxds_v1.uxgrid should NOT be None
nt.assert_equal(uxds_v1.uxgrid is not None, True)
def test_open_dataset(gridpath, datasetpath, mesh_constants):
"""Loads a single dataset with its grid topology file using uxarray's
open_dataset call."""
grid_path = gridpath("ugrid", "outCSne30", "outCSne30.ug")
data_path = datasetpath("ugrid", "outCSne30", "outCSne30_var2.nc")
uxds_var2_ne30 = ux.open_dataset(grid_path, data_path)
nt.assert_equal(uxds_var2_ne30.uxgrid.node_lon.size, mesh_constants['NNODES_outCSne30'])
nt.assert_equal(len(uxds_var2_ne30.uxgrid._ds.data_vars), mesh_constants['DATAVARS_outCSne30'])
nt.assert_equal(uxds_var2_ne30.source_datasets, str(data_path))
def test_open_dataset_single_combined_mpas_file(gridpath):
"""Loads a combined MPAS grid-and-data file with a single argument."""
# Use a known combined grid-and-data MPAS file.
file_path = gridpath("mpas", "QU", "oQU480.231010.nc")
uxds_single = ux.open_dataset(file_path)
uxds_pair = ux.open_dataset(file_path, file_path)
# Ensure that the single-argument path actually loads data variables
assert len(uxds_single.data_vars) > 0
nt.assert_equal(uxds_single.uxgrid.source_grid_spec, "MPAS")
nt.assert_equal(uxds_single.source_datasets, str(file_path))
nt.assert_equal(uxds_single.sizes["n_face"], uxds_pair.sizes["n_face"])
nt.assert_equal(set(uxds_single.data_vars), set(uxds_pair.data_vars))
assert "ssh" in uxds_single.data_vars
def test_open_dataset_single_combined_xarray_dataset(gridpath):
"""Loads a combined MPAS grid-and-data xarray.Dataset with a single argument."""
file_path = gridpath("mpas", "QU", "oQU480.231010.nc")
with xr.open_dataset(file_path) as ds:
uxds = ux.open_dataset(ds)
nt.assert_equal(uxds.uxgrid.source_grid_spec, "MPAS")
nt.assert_equal(uxds.source_datasets, None)
assert "ssh" in uxds.data_vars
def test_open_dataset_single_argument_rejects_directory_grid(tmp_path):
"""Requires a separate data file for directory-based grids."""
with pytest.raises(ValueError, match="Directory-based grids require a separate data file"):
ux.open_dataset(tmp_path)
def test_open_dataset_single_argument_rejects_invalid_combined_file(datasetpath):
"""Rejects one-file inputs that do not contain recognizable grid metadata."""
data_path = datasetpath("ugrid", "outCSne30", "outCSne30_var2.nc")
with pytest.raises(RuntimeError, match="Could not recognize dataset format"):
ux.open_dataset(data_path)
def test_open_mf_dataset(gridpath, datasetpath, mesh_constants):
"""Loads multiple datasets with their grid topology file using
uxarray's open_dataset call."""
grid_path = gridpath("ugrid", "outCSne30", "outCSne30.ug")
dsfiles_mf_ne30 = datasetpath(
"ugrid",
"outCSne30",
["outCSne30_var2.nc", "outCSne30_vortex.nc"],
)
uxds_mf_ne30 = ux.open_mfdataset(grid_path, dsfiles_mf_ne30)
nt.assert_equal(uxds_mf_ne30.uxgrid.node_lon.size, mesh_constants['NNODES_outCSne30'])
nt.assert_equal(len(uxds_mf_ne30.uxgrid._ds.data_vars), mesh_constants['DATAVARS_outCSne30'])
nt.assert_equal(uxds_mf_ne30.source_datasets, str(dsfiles_mf_ne30))
def test_open_grid(gridpath, mesh_constants):
"""Loads only a grid topology file using uxarray's open_grid call."""
uxgrid = ux.open_grid(gridpath("ugrid", "geoflow-small", "grid.nc"))
nt.assert_almost_equal(uxgrid.calculate_total_face_area(), mesh_constants['MESH30_AREA'], decimal=3)
def test_copy_dataset(gridpath, datasetpath):
"""Loads a single dataset with its grid topology file using uxarray's
open_dataset call and make a copy of the object."""
uxds_var2_ne30 = ux.open_dataset(
gridpath("ugrid", "outCSne30", "outCSne30.ug"),
datasetpath("ugrid", "outCSne30", "outCSne30_var2.nc")
)
# make a shallow and deep copy of the dataset object
uxds_var2_ne30_copy_deep = uxds_var2_ne30.copy(deep=True)
uxds_var2_ne30_copy = uxds_var2_ne30.copy(deep=False)
# Ideally uxds_var2_ne30_copy.uxgrid should NOT be None
nt.assert_equal(uxds_var2_ne30_copy.uxgrid is not None, True)
# Check that the copy is a shallow copy
assert uxds_var2_ne30_copy.uxgrid is uxds_var2_ne30.uxgrid
assert uxds_var2_ne30_copy.uxgrid == uxds_var2_ne30.uxgrid
# Check that the deep copy is a deep copy
assert uxds_var2_ne30_copy_deep.uxgrid == uxds_var2_ne30.uxgrid
assert uxds_var2_ne30_copy_deep.uxgrid is not uxds_var2_ne30.uxgrid
def test_copy_dataarray(gridpath, datasetpath):
"""Loads an unstructured grid and data using uxarray's open_dataset
call and make a copy of the dataarray object."""
# Paths to Data Variable files
data_paths = [
datasetpath("ugrid", "geoflow-small", "v1.nc"),
datasetpath("ugrid", "geoflow-small", "v2.nc"),
datasetpath("ugrid", "geoflow-small", "v3.nc")
]
uxds_v1 = ux.open_dataset(gridpath("ugrid", "geoflow-small", "grid.nc"), data_paths[0])
# get the uxdataarray object
v1_uxdata_array = uxds_v1['v1']
# make a shallow and deep copy of the dataarray object
v1_uxdata_array_copy_deep = v1_uxdata_array.copy(deep=True)
v1_uxdata_array_copy = v1_uxdata_array.copy(deep=False)
# Check that the copy is a shallow copy
assert v1_uxdata_array_copy.uxgrid is v1_uxdata_array.uxgrid
assert v1_uxdata_array_copy.uxgrid == v1_uxdata_array.uxgrid
# Check that the deep copy is a deep copy
assert v1_uxdata_array_copy_deep.uxgrid == v1_uxdata_array.uxgrid
assert v1_uxdata_array_copy_deep.uxgrid is not v1_uxdata_array.uxgrid
def test_open_dataset_grid_kwargs(gridpath, datasetpath):
"""Drops ``Mesh2_face_nodes`` from the inputted grid file using
``grid_kwargs``"""
with pytest.raises(ValueError):
# attempt to open a dataset after dropping face nodes should raise a KeyError
uxds = ux.open_dataset(
gridpath("ugrid", "outCSne30", "outCSne30.ug"),
datasetpath("ugrid", "outCSne30", "outCSne30_var2.nc"),
grid_kwargs={"drop_variables": "Mesh2_face_nodes"}
)
def test_open_dataset_with_fallback():
"""Test that the fallback mechanism works when the default engine fails."""
tmp_path = ""
ds = None
ds_fallback = None
try:
# Create a simple test dataset
with tempfile.NamedTemporaryFile(suffix='.nc', delete=False) as tmp:
data = xr.Dataset({'temp': (['x', 'y'], np.random.rand(5, 5))})
data.to_netcdf(tmp.name)
tmp_path = tmp.name
# Test normal case
ds = _open_dataset_with_fallback(tmp_path)
assert isinstance(ds, xr.Dataset)
assert 'temp' in ds.data_vars
# Test fallback mechanism with mocked failure
original_open = xr.open_dataset
call_count = 0
def mock_open_dataset(*args, **kwargs):
nonlocal call_count
call_count += 1
if call_count == 1 and 'engine' not in kwargs:
raise Exception("Simulated engine failure")
return original_open(*args, **kwargs)
with patch('uxarray.core.utils.xr.open_dataset', side_effect=mock_open_dataset):
ds_fallback = _open_dataset_with_fallback(tmp_path)
assert isinstance(ds_fallback, xr.Dataset)
assert call_count == 2 # First failed, second succeeded
finally:
if ds is not None:
ds.close()
if ds_fallback is not None:
ds_fallback.close()
if os.path.exists(tmp_path):
os.unlink(tmp_path)
def test_list_grid_names_multigrid(gridpath):
"""List grids from an OASIS-style multi-grid file."""
grid_file = gridpath("scrip", "oasis", "grids.nc")
grid_names = ux.list_grid_names(grid_file)
assert isinstance(grid_names, list)
assert set(grid_names) == {"ocn", "atm"}
def test_list_grid_names_single_scrip():
"""List grids from a standard single-grid SCRIP file."""
grid_path = TEST_MESHFILES / "scrip" / "outCSne8" / "outCSne8.nc"
grid_names = ux.list_grid_names(grid_path)
assert isinstance(grid_names, list)
assert grid_names == ["grid"]
def test_open_multigrid_all_grids(gridpath):
"""Open all grids from a multi-grid file."""
grid_file = gridpath("scrip", "oasis", "grids.nc")
grids = ux.open_multigrid(grid_file)
assert isinstance(grids, dict)
assert set(grids.keys()) == {"ocn", "atm"}
assert grids["ocn"].n_face == 12
assert grids["atm"].n_face == 20
def test_open_multigrid_specific_grids(gridpath):
"""Open a subset of grids from a multi-grid file."""
grid_file = gridpath("scrip", "oasis", "grids.nc")
grids = ux.open_multigrid(grid_file, gridnames=["ocn"])
assert set(grids.keys()) == {"ocn"}
assert grids["ocn"].n_face == 12
def test_open_multigrid_with_masks(gridpath):
"""Open grids with a companion mask file."""
grid_file = gridpath("scrip", "oasis", "grids.nc")
mask_file = gridpath("scrip", "oasis", "masks.nc")
grids = ux.open_multigrid(grid_file, mask_filename=mask_file)
assert grids["ocn"].n_face == 8
assert grids["atm"].n_face == 20
def test_open_multigrid_mask_zero_faces(gridpath):
"""Applying masks that deactivate an entire grid should not fail."""
grid_file = gridpath("scrip", "oasis", "grids.nc")
mask_file = gridpath("scrip", "oasis", "masks_no_atm.nc")
grids = ux.open_multigrid(grid_file, mask_filename=mask_file)
assert grids["ocn"].n_face == 8
assert grids["atm"].n_face == 0
def test_open_multigrid_missing_grid_error(gridpath):
"""Requesting a missing grid should raise."""
grid_file = gridpath("scrip", "oasis", "grids.nc")
with pytest.raises(ValueError, match="Grid 'land' not found"):
ux.open_multigrid(grid_file, gridnames=["land"])