Skip to content

Commit e1808f4

Browse files
committed
Add tests for bugs found
1 parent 5b2541e commit e1808f4

6 files changed

Lines changed: 112 additions & 2 deletions

File tree

‎ndcube/extra_coords/tests/test_extra_coords.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,3 +560,21 @@ def test_length1_extra_coord(wave_lut):
560560
sec = ec[item]
561561
assert (sec.wcs.pixel_to_world(0) == wave_lut[item]).all()
562562
assert (sec.wcs.world_to_pixel(wave_lut[item])[0] == [0]).all()
563+
564+
565+
def test_str_with_unnamed_table_coordinate(wave_lut):
566+
# Regression test: ExtraCoords.__str__ used to do ', '.join(table.names),
567+
# which crashes with TypeError if `names` is None. This happens whenever a
568+
# BaseTableCoordinate instance is passed directly to `.add()`, since `add`
569+
# only sets names via its own `name` argument when it constructs the
570+
# coordinate itself, not when an already-built coordinate is passed in.
571+
from ndcube.extra_coords.table_coord import QuantityTableCoordinate # NOQA
572+
573+
ec = ExtraCoords()
574+
coord = QuantityTableCoordinate(wave_lut)
575+
assert coord.names is None
576+
ec.add("wave", 0, coord)
577+
578+
# Should not raise.
579+
str(ec)
580+
repr(ec)

‎ndcube/tests/test_ndcollection.py‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,45 @@ def test_collection_pop(collection, popped_key, expected_popped, expected_collec
112112

113113
@pytest.mark.parametrize(('collection', 'key', 'expected'), [
114114
(cube_collection, "cube0", NDCollection([("cube1", cube1), ("cube2", cube2)],
115-
aligned_axes=aligned_axes[1:]))])
115+
aligned_axes=aligned_axes[1:])),
116+
# Regression test: __delitem__ used to call self.aligned_axes.__delitem__(key)
117+
# unconditionally, crashing with AttributeError on a collection with no
118+
# aligned axes since self.aligned_axes is None in that case.
119+
(unaligned_collection, "cube0", NDCollection([("cube1", cube1), ("cube2", cube2)]))])
116120
def test_del_collection(collection, key, expected):
117121
del_collection = collection.copy()
118122
del del_collection[key]
119123
helpers.assert_collections_equal(del_collection, expected)
120124

121125

126+
def test_slice_by_keys_unaligned_collection():
127+
# Regression test: slicing by a sequence of string keys used to crash with
128+
# TypeError ("'NoneType' object is not subscriptable") on a collection with
129+
# aligned_axes=None, because `self.aligned_axes[item_]` was called
130+
# unconditionally instead of being guarded by an is-None check.
131+
result = unaligned_collection[("cube0", "cube1")]
132+
assert list(result.keys()) == ["cube0", "cube1"]
133+
assert result.aligned_axes is None
134+
135+
136+
def test_collection_getitem_unsupported_type():
137+
# Regression test: the "unsupported slicing type" error message referenced
138+
# `axis_item`, a loop variable only bound inside the tuple-handling branch,
139+
# so hitting this branch raised NameError instead of the intended TypeError.
140+
with pytest.raises(TypeError, match="Unsupported slicing type"):
141+
cube_collection[1.5]
142+
143+
144+
def test_collection_slice_open_start_negative_stop_with_sliceable_meta():
145+
# Regression test: sanitizing a slice item with sliceable meta compared
146+
# `item.start < 0` even when `item.start` is None (i.e. an open-ended
147+
# slice like `slice(None, -1)`), crashing with
148+
# "'<' not supported between instances of 'NoneType' and 'int'".
149+
result = cube_collection[slice(None, -1)]
150+
expected = cube_collection[slice(0, cube_collection.aligned_dimensions[0] - 1)]
151+
helpers.assert_collections_equal(result, expected)
152+
153+
122154
@pytest.mark.parametrize(('collection', 'key', 'data', 'aligned_axes', 'expected'), [
123155
(cube_collection, "cube1", cube2, aligned_axes[2], NDCollection(
124156
[("cube0", cube0), ("cube1", cube2), ("cube2", cube2)],

‎ndcube/tests/test_ndcube_slice_and_crop.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,17 @@ def test_crop_by_values_1d_dependent(ndcube_4d_ln_lt_l_t):
423423
helpers.assert_cubes_equal(output, expected)
424424

425425

426+
def test_crop_no_points_with_extra_coords_wcs(ndcube_3d_ln_lt_l_ec_time):
427+
# Regression test: cropping with zero points is a no-op, but
428+
# sanitize_crop_inputs used to return the *un-unwrapped* ExtraCoords
429+
# object on that early-exit path, so `wcs.pixel_n_dim` crashed with
430+
# AttributeError since ExtraCoords has no such attribute (only
431+
# `ExtraCoords.wcs.pixel_n_dim` does).
432+
cube = ndcube_3d_ln_lt_l_ec_time
433+
output = cube.crop(wcs=cube.extra_coords)
434+
helpers.assert_cubes_equal(output, cube)
435+
436+
426437
def test_crop_by_extra_coords(ndcube_3d_ln_lt_l_ec_time):
427438
cube = ndcube_3d_ln_lt_l_ec_time
428439
lower_corner = (Time("2000-01-01T15:00:00", scale="utc", format="fits"), None)

‎ndcube/utils/tests/test_utils_cube.py‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from astropy.nddata import StdDevUncertainty
66

7-
from ndcube.utils.cube import propagate_rebin_uncertainties
7+
from ndcube.utils.cube import propagate_rebin_uncertainties, sanitize_crop_inputs
88

99

1010
@pytest.fixture
@@ -99,3 +99,18 @@ def test_propagate_rebin_uncertainties_nan(stacked_pixel_data):
9999
np.nanmean, operation_ignores_mask=False)
100100
assert type(output) is type(expected)
101101
assert np.allclose(output.array, expected.array)
102+
103+
104+
def test_sanitize_crop_inputs_no_op_unwraps_extra_coords(ndcube_3d_ln_lt_l_ec_time):
105+
# Regression test: on the no-op (all-points-None, including zero points)
106+
# early-exit path, sanitize_crop_inputs used to return the wcs argument
107+
# completely unprocessed, so if it was passed an ExtraCoords object it
108+
# would still be an ExtraCoords object rather than being unwrapped to a
109+
# low-level WCS like on the normal path. Callers (e.g. NDCube._get_crop_item)
110+
# immediately use low-level WCS attributes like `.pixel_n_dim` on the
111+
# returned value, which ExtraCoords does not have.
112+
cube = ndcube_3d_ln_lt_l_ec_time
113+
no_op, points, wcs = sanitize_crop_inputs((), cube.extra_coords)
114+
assert no_op is True
115+
assert points == []
116+
assert hasattr(wcs, "pixel_n_dim")

‎ndcube/wcs/wrappers/tests/test_compound_wcs.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,33 @@ def test_shared_pixel_axis_compound_3d(spectral_cube_3d_fitswcs, time_1d_fitswcs
184184

185185
with pytest.raises(ValueError):
186186
wcs.world_to_pixel_values((14, -10, -2.6e+10, -7.0))
187+
188+
189+
class _NamedPixelAxisWCS:
190+
"""
191+
A minimal low-level WCS stub with a non-empty ``pixel_axis_names``.
192+
193+
FITS WCS objects always report empty-string pixel axis names, so they
194+
can't be used to exercise the case where two WCSes sharing a pixel axis
195+
(via ``mapping``) disagree on that axis's name.
196+
"""
197+
198+
def __init__(self, pixel_n_dim, pixel_axis_names):
199+
self.pixel_n_dim = pixel_n_dim
200+
self.array_shape = None
201+
self.pixel_bounds = None
202+
self.pixel_axis_names = pixel_axis_names
203+
204+
205+
def test_pixel_axis_names_shared_axis_different_names():
206+
# Regression test: CompoundLowLevelWCS.pixel_axis_names used to build
207+
# out_names via `list(self.mapping.inverse(*pixel_names))` and then try to
208+
# mutate the *tuple* returned by `Mapping.__call__` in place, raising
209+
# "'tuple' object does not support item assignment" whenever a shared
210+
# pixel axis had different names in the contributing WCSes.
211+
wcs_a = _NamedPixelAxisWCS(1, ('a',))
212+
wcs_b = _NamedPixelAxisWCS(1, ('b',))
213+
214+
compound = CompoundLowLevelWCS(wcs_a, wcs_b, mapping=(0, 0))
215+
216+
assert compound.pixel_axis_names == ('a / b',)

‎pytest.ini‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,7 @@ filterwarnings =
6060
ignore:FigureCanvasAgg is non-interactive, and thus cannot be shown:UserWarning
6161
# wcsaxes/formatter_locator.py hates angles
6262
ignore:.*invalid value encountered in do_format.*:RuntimeWarning
63+
# asdf warning
64+
ignore:.*:asdf.exceptions.AsdfWarning
65+
# astropy
66+
ignore:.*:astropy.utils.exceptions.AstropyPendingDeprecationWarning

0 commit comments

Comments
 (0)