Skip to content

Commit e60f58c

Browse files
authored
Cover read_geotiff_gpu/dask name= and read_geotiff_gpu max_pixels= kwargs (#1627)
Test coverage gap sweep 2026-05-11 (pass 4) on the geotiff module. The prior pass closed the VRT backend coverage gaps; this pass closes the remaining MEDIUM Cat 4 (parameter coverage) gaps on the non-VRT GPU and dask read paths. Adds 12 tests covering: - read_geotiff_dask(name=...) -- direct, plus default-name-from-path. - read_geotiff_gpu(name=...) -- direct eager and dask+GPU paths, plus the default-name-from-path branch. - read_geotiff_gpu(max_pixels=...) -- accept and reject branches, plus the dask+GPU chunked reject branch. - open_geotiff(chunks=..., name=...), open_geotiff(gpu=True, name=...), open_geotiff(gpu=True, chunks=..., name=...) -- dispatch forwards name= through every backend branch. - open_geotiff(gpu=True, max_pixels=...) -- dispatch forwards max_pixels= into the GPU pipeline reject branch. All 12 tests pass on a CUDA host. GPU-only tests are guarded by the project's standard _gpu_only marker so non-GPU CI runs them as skip. No source changes; this is a test-only coverage sweep. State row updated in .claude/sweep-test-coverage-state.csv.
1 parent 8d17a84 commit e60f58c

2 files changed

Lines changed: 178 additions & 1 deletion

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module,last_inspected,issue,severity_max,categories_found,notes
2-
geotiff,2026-05-11,,MEDIUM,1;4,"Sweep 3 (2026-05-11 r3): added test_vrt_backend_coverage_2026_05_11.py closing read_vrt(gpu=True) + read_vrt(gpu=True, chunks=N) (Cat 1, dask+cupy backend), read_vrt(dtype=) safe-widening and float->int validation (Cat 4), read_vrt(name=) override (Cat 4), and open_geotiff(BytesIO, gpu=True) / open_geotiff(BytesIO, chunks=N) error-path coverage (Cat 4). 11 tests, all passing on GPU host. No HIGH gaps remain."
2+
geotiff,2026-05-11,,MEDIUM,1;4,"Sweep 4 (2026-05-11 r4): added test_kwarg_coverage_2026_05_11_r4.py closing read_geotiff_gpu(name=) + read_geotiff_gpu(chunks=, name=) (Cat 4, dask+cupy backend), read_geotiff_dask(name=) (Cat 4), read_geotiff_gpu(max_pixels=) accept/reject + chunks+max_pixels reject (Cat 4), and open_geotiff(chunks=, name=) / open_geotiff(gpu=True, name=) / open_geotiff(gpu=True, chunks=, name=) / open_geotiff(gpu=True, max_pixels=) dispatch coverage. 12 tests, all passing on GPU host. Pass 3 (r3) added test_vrt_backend_coverage_2026_05_11.py covering read_vrt gpu/chunks/dtype/name (11 tests). No HIGH gaps remain."
33
reproject,2026-05-10,,HIGH,1;4;5,"Added 39 tests: LiteCRS direct coverage, itrf_transform behaviour/roundtrip/array, itrf_frames, geoid_height numerical correctness + raster happy-path, vertical helpers (ellipsoidal<->orthometric/depth), reproject() lat/lon and latitude/longitude dim propagation. Note: _merge_arrays_cupy is imported but unused (no cupy merge dispatch in merge()); flagged as feature gap not test gap."
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
"""Parameter coverage for ``read_geotiff_gpu`` / ``read_geotiff_dask``.
2+
3+
The ``name=`` and ``max_pixels=`` kwargs flow through ``open_geotiff``'s
4+
dispatch into the GPU and dask backends. The eager numpy path tests
5+
both kwargs directly (e.g. ``test_cog::test_open_geotiff_custom_name``,
6+
``test_security`` for ``max_pixels``). The dask backend covers
7+
``max_pixels`` in ``test_backend_kwarg_parity_1561``. The remaining
8+
gaps that this sweep (test coverage gap sweep 2026-05-11, pass 4)
9+
closes are:
10+
11+
* ``read_geotiff_gpu(name=...)`` -- direct test on the GPU eager path
12+
and the dask+GPU path.
13+
* ``read_geotiff_dask(name=...)`` -- direct test on the dask-on-CPU
14+
path.
15+
* ``read_geotiff_gpu(max_pixels=...)`` -- both the accept and reject
16+
branches; the GPU pipeline calls ``_check_dimensions`` twice (once
17+
for the full raster, once per tile) and neither call had regression
18+
coverage.
19+
* ``open_geotiff(chunks=..., name=...)`` /
20+
``open_geotiff(gpu=True, name=...)`` /
21+
``open_geotiff(gpu=True, chunks=..., name=...)`` -- the dispatcher
22+
forwards ``name=`` through three distinct branches and a silent
23+
drop would only show up in user code.
24+
25+
Adding these closes the MEDIUM Cat 4 (parameter coverage) gap that
26+
was open after pass 3.
27+
"""
28+
from __future__ import annotations
29+
30+
import importlib.util
31+
32+
import numpy as np
33+
import pytest
34+
35+
from xrspatial.geotiff import (
36+
open_geotiff,
37+
read_geotiff_dask,
38+
read_geotiff_gpu,
39+
to_geotiff,
40+
)
41+
42+
43+
def _gpu_available() -> bool:
44+
if importlib.util.find_spec("cupy") is None:
45+
return False
46+
try:
47+
import cupy
48+
return bool(cupy.cuda.is_available())
49+
except Exception:
50+
return False
51+
52+
53+
_HAS_GPU = _gpu_available()
54+
_gpu_only = pytest.mark.skipif(not _HAS_GPU, reason="cupy + CUDA required")
55+
56+
57+
@pytest.fixture
58+
def small_tiff_path(tmp_path):
59+
arr = np.arange(64, dtype=np.float32).reshape(8, 8)
60+
p = tmp_path / "small.tif"
61+
to_geotiff(arr, str(p), tile_size=4)
62+
return str(p), arr
63+
64+
65+
# ---------------------------------------------------------------------------
66+
# read_geotiff_dask(name=...) -- direct
67+
# ---------------------------------------------------------------------------
68+
69+
70+
def test_read_geotiff_dask_name_kwarg_sets_name(small_tiff_path):
71+
path, arr = small_tiff_path
72+
da = read_geotiff_dask(path, chunks=4, name="custom_dask")
73+
assert da.name == "custom_dask"
74+
np.testing.assert_array_equal(da.values, arr)
75+
76+
77+
def test_read_geotiff_dask_default_name_from_path(small_tiff_path):
78+
path, _ = small_tiff_path
79+
da = read_geotiff_dask(path, chunks=4)
80+
# Default name is filename stem when no override is supplied.
81+
assert da.name == "small"
82+
83+
84+
# ---------------------------------------------------------------------------
85+
# read_geotiff_gpu(name=...) -- direct
86+
# ---------------------------------------------------------------------------
87+
88+
89+
@_gpu_only
90+
def test_read_geotiff_gpu_name_kwarg_sets_name(small_tiff_path):
91+
path, arr = small_tiff_path
92+
da = read_geotiff_gpu(path, name="custom_gpu")
93+
assert da.name == "custom_gpu"
94+
np.testing.assert_array_equal(da.data.get(), arr)
95+
96+
97+
@_gpu_only
98+
def test_read_geotiff_gpu_default_name_from_path(small_tiff_path):
99+
path, _ = small_tiff_path
100+
da = read_geotiff_gpu(path)
101+
assert da.name == "small"
102+
103+
104+
@_gpu_only
105+
def test_read_geotiff_gpu_chunks_name_kwarg_sets_name(small_tiff_path):
106+
path, arr = small_tiff_path
107+
da = read_geotiff_gpu(path, chunks=4, name="custom_dask_gpu")
108+
assert da.name == "custom_dask_gpu"
109+
np.testing.assert_array_equal(da.data.compute().get(), arr)
110+
111+
112+
# ---------------------------------------------------------------------------
113+
# read_geotiff_gpu(max_pixels=...) -- accept + reject
114+
# ---------------------------------------------------------------------------
115+
116+
117+
@_gpu_only
118+
def test_read_geotiff_gpu_max_pixels_accepts_within_budget(small_tiff_path):
119+
path, arr = small_tiff_path
120+
# 8 * 8 = 64 pixels. 100 leaves room.
121+
da = read_geotiff_gpu(path, max_pixels=100)
122+
np.testing.assert_array_equal(da.data.get(), arr)
123+
124+
125+
@_gpu_only
126+
def test_read_geotiff_gpu_max_pixels_rejects_oversized(small_tiff_path):
127+
path, _ = small_tiff_path
128+
with pytest.raises(ValueError, match="safety limit|exceeds max_pixels"):
129+
read_geotiff_gpu(path, max_pixels=10)
130+
131+
132+
@_gpu_only
133+
def test_read_geotiff_gpu_chunks_max_pixels_rejects_oversized(small_tiff_path):
134+
"""Dask+GPU path also enforces ``max_pixels``."""
135+
path, _ = small_tiff_path
136+
with pytest.raises(ValueError, match="safety limit|exceeds max_pixels"):
137+
read_geotiff_gpu(path, chunks=4, max_pixels=10)
138+
139+
140+
# ---------------------------------------------------------------------------
141+
# open_geotiff dispatch: name= flows through every backend branch
142+
# ---------------------------------------------------------------------------
143+
144+
145+
def test_open_geotiff_chunks_name_flows_through(small_tiff_path):
146+
path, arr = small_tiff_path
147+
da = open_geotiff(path, chunks=4, name="dispatch_dask")
148+
assert da.name == "dispatch_dask"
149+
np.testing.assert_array_equal(da.values, arr)
150+
151+
152+
@_gpu_only
153+
def test_open_geotiff_gpu_name_flows_through(small_tiff_path):
154+
path, arr = small_tiff_path
155+
da = open_geotiff(path, gpu=True, name="dispatch_gpu")
156+
assert da.name == "dispatch_gpu"
157+
np.testing.assert_array_equal(da.data.get(), arr)
158+
159+
160+
@_gpu_only
161+
def test_open_geotiff_gpu_chunks_name_flows_through(small_tiff_path):
162+
path, arr = small_tiff_path
163+
da = open_geotiff(path, gpu=True, chunks=4, name="dispatch_dask_gpu")
164+
assert da.name == "dispatch_dask_gpu"
165+
np.testing.assert_array_equal(da.data.compute().get(), arr)
166+
167+
168+
# ---------------------------------------------------------------------------
169+
# open_geotiff dispatch: max_pixels reject flows through GPU branch
170+
# ---------------------------------------------------------------------------
171+
172+
173+
@_gpu_only
174+
def test_open_geotiff_gpu_max_pixels_rejects(small_tiff_path):
175+
path, _ = small_tiff_path
176+
with pytest.raises(ValueError, match="safety limit|exceeds max_pixels"):
177+
open_geotiff(path, gpu=True, max_pixels=10)

0 commit comments

Comments
 (0)