diff --git a/.claude/sweep-error-handling-state.csv b/.claude/sweep-error-handling-state.csv index a5ad4dfdd..755fc03ed 100644 --- a/.claude/sweep-error-handling-state.csv +++ b/.claude/sweep-error-handling-state.csv @@ -1,2 +1,3 @@ -module,last_inspected,issue,severity_max,categories_found,notes -geotiff,2026-07-02,3604,MEDIUM,2;4,"to_geotiff 0D/1D DataArray raised opaque IndexError from _coords.py coords_to_transform (dims[-2]) instead of clean 'Expected 2D or 3D' ValueError; numpy path + 4D DataArray already clean. Fixed via early ndim guard before dispatch (eager/vrt/gpu) + 3 tests; PR #3604. Read-side param validation + typed-error hierarchy + allow_rotated/allow_invalid_nodata VRT+chunked opt-in threading verified clean (CUDA available, GPU paths run). gh issue create blocked by auto-mode; PR opened. Cat 2+4." +module,last_inspected,issue,severity_max,categories_found,notes +bump,2026-07-02,,HIGH,1;2;3;4,"bump() agg template unvalidated: plain ndarray -> ArrayTypeFunctionMapping 'Unsupported Array Type'; 3D/1D DataArray -> 'too many values to unpack'. count/spread unvalidated (internal numpy errors / silent). Added _validate_raster(agg,ndim=2) + _validate_scalar for count,spread. all 4 backends verified (CUDA present)." +geotiff,2026-07-02,3604,MEDIUM,2;4,"to_geotiff 0D/1D DataArray raised opaque IndexError from _coords.py coords_to_transform (dims[-2]) instead of clean 'Expected 2D or 3D' ValueError; numpy path + 4D DataArray already clean. Fixed via early ndim guard before dispatch (eager/vrt/gpu) + 3 tests; PR #3604. Read-side param validation + typed-error hierarchy + allow_rotated/allow_invalid_nodata VRT+chunked opt-in threading verified clean (CUDA available, GPU paths run). gh issue create blocked by auto-mode; PR opened. Cat 2+4." diff --git a/xrspatial/bump.py b/xrspatial/bump.py index b9b96b976..9bc54712f 100644 --- a/xrspatial/bump.py +++ b/xrspatial/bump.py @@ -15,8 +15,8 @@ class cupy(object): except ImportError: da = None -from xrspatial.utils import (ArrayTypeFunctionMapping, _validate_scalar, has_cuda_and_cupy, - is_cupy_array, ngjit) +from xrspatial.utils import (ArrayTypeFunctionMapping, _validate_raster, _validate_scalar, + has_cuda_and_cupy, is_cupy_array, ngjit) # Upper bound on bump count to prevent accidental OOM from the default # w*h//10 heuristic. 16 bytes per bump (int32 loc pair + float64 height), @@ -344,7 +344,15 @@ def heights(locations, src, src_range, height = 20): Description: Example Bump Map units: km """ + _validate_scalar(spread, func_name='bump', name='spread', + dtype=int, min_val=0) + if count is not None: + _validate_scalar(count, func_name='bump', name='count', + dtype=int, min_val=0) + if agg is not None: + _validate_raster(agg, func_name='bump', name='agg', ndim=2, + numeric=False) h, w = agg.shape else: _validate_scalar(width, func_name='bump', name='width', diff --git a/xrspatial/tests/test_bump.py b/xrspatial/tests/test_bump.py index 65abf26bc..8559c5532 100644 --- a/xrspatial/tests/test_bump.py +++ b/xrspatial/tests/test_bump.py @@ -379,3 +379,54 @@ def test_bump_dask_bypasses_raster_guard(): result = bump(agg=agg, count=10, spread=0) assert result.shape == (100_000, 100_000) assert isinstance(result.data, da.Array) + + +# --- Input-validation regression tests --- + +def test_bump_agg_must_be_dataarray(): + """A plain ndarray template raises a clear TypeError naming `agg`, + not an inscrutable 'Unsupported Array Type' from the dispatcher.""" + import pytest + + with pytest.raises(TypeError, match=r"bump\(\): `agg` must be an " + r"xarray\.DataArray"): + bump(agg=np.zeros((10, 10))) + + +def test_bump_agg_must_be_2d(): + """A 3D or 1D template raises a clear ValueError naming `agg` and the + 2D requirement, not 'too many values to unpack'.""" + import pytest + + with pytest.raises(ValueError, match=r"bump\(\): `agg` must be 2D"): + bump(agg=xr.DataArray(np.zeros((3, 10, 10)), dims=['b', 'y', 'x'])) + with pytest.raises(ValueError, match=r"bump\(\): `agg` must be 2D"): + bump(agg=xr.DataArray(np.zeros(10), dims=['x'])) + + +def test_bump_count_validated(): + """`count` gets the same clean validation as width/height instead of + surfacing raw numpy errors.""" + import pytest + + with pytest.raises(ValueError, match=r"bump\(\): `count` must be >= 0"): + bump(width=10, height=10, count=-5) + with pytest.raises(TypeError, match=r"bump\(\): `count` must be int"): + bump(width=10, height=10, count=5.0) + + +def test_bump_spread_validated(): + """`spread` is documented as int; negative and non-int values raise + instead of being silently ignored.""" + import pytest + + with pytest.raises(ValueError, match=r"bump\(\): `spread` must be >= 0"): + bump(width=10, height=10, spread=-3) + with pytest.raises(TypeError, match=r"bump\(\): `spread` must be int"): + bump(width=10, height=10, spread=2.5) + + +def test_bump_spread_zero_still_allowed(): + """spread=0 (single-pixel bumps) must remain valid.""" + result = bump(width=10, height=10, count=5, spread=0) + assert result.shape == (10, 10)