Skip to content

geotiff: add attrs compatibility-alias locking test (#1984)#2002

Merged
brendancol merged 1 commit into
mainfrom
1984-pr5-attrs-alias-locking-test
May 17, 2026
Merged

geotiff: add attrs compatibility-alias locking test (#1984)#2002
brendancol merged 1 commit into
mainfrom
1984-pr5-attrs-alias-locking-test

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

PR 5 of 7 for issue #1984.

Summary

Adds xrspatial/geotiff/tests/test_attrs_contract_aliases_1984.py, a single locking test that pins the compatibility alias tier of the attrs contract so future drift surfaces in one place. No production code changes; tests only.

The contract being pinned:

  • Read paths must accept attrs['nodatavals'] (rioxarray) and attrs['_FillValue'] (CF) on a foreign DataArray, falling back to them when attrs['nodata'] is absent.
  • Write paths must not emit either alias when canonical attrs['nodata'] is present; after a round-trip only nodata should appear.

Finer-grained cases continue to live in test_nodata_attr_aliases_1582.py. This file is intentionally contract-shaped and does not duplicate those cases.

Tests added

  • test_read_uses_nodatavals_when_nodata_absent: pins that a foreign DataArray carrying only the rioxarray tuple survives the writer.
  • test_read_uses_fill_value_when_nodata_absent: same for CF _FillValue.
  • test_canonical_nodata_wins_over_aliases: when all three keys are present and disagree, attrs['nodata'] wins. Asserted at both the _resolve_nodata_attr chokepoint and the round-trip layer.
  • test_write_does_not_emit_aliases_when_canonical_present: after writing a DataArray that has only nodata set, the reader returns attrs containing nodata and neither alias.
  • test_nan_in_nodatavals_is_skipped: NaN entries in a rioxarray tuple are skipped, not treated as a concrete sentinel. Pure-NaN tuples resolve to None; mixed tuples return the first concrete numeric value.

Bugs spotted

None. Reader paths in _attrs._populate_attrs_from_geo_info only set attrs['nodata']; neither nodatavals nor _FillValue is written into the output attrs after a round-trip, so all five assertions pass on the current code. No xfails needed.

Test plan

  • pytest xrspatial/geotiff/tests/test_attrs_contract_aliases_1984.py -x -q -- 5 passed.
  • pytest xrspatial/geotiff/tests -x -q sanity check. Two pre-existing failures observed on main (test_predictor2_big_endian_gpu_1517.py -- missing read_to_array attribute; test_size_param_validation_gpu_vrt_1776.py::test_tile_size_positive_works -- unrelated _validation.py ValueError). Both reproduce without this PR's changes, so unrelated.

Closes-step: 5 of 7 toward #1984.

Locks the contract for the rioxarray-style ``attrs['nodatavals']``
and CF-style ``attrs['_FillValue']`` aliases in one place so future
drift surfaces here rather than in scattered regression files.

Five cases:
- ``test_read_uses_nodatavals_when_nodata_absent``: rioxarray-style
  tuple round-trips through ``to_geotiff``.
- ``test_read_uses_fill_value_when_nodata_absent``: CF ``_FillValue``
  round-trips through ``to_geotiff``.
- ``test_canonical_nodata_wins_over_aliases``: ``attrs['nodata']``
  beats both aliases, asserted at both the resolver and the
  round-trip layer.
- ``test_write_does_not_emit_aliases_when_canonical_present``:
  ``open_geotiff`` emits only ``nodata`` after a write, not the
  aliases.
- ``test_nan_in_nodatavals_is_skipped``: NaN entries in the
  rioxarray tuple are skipped, not treated as a concrete sentinel.

Tests only. No production code changes.

PR 5 of 7 for #1984.
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 17, 2026
@brendancol
Copy link
Copy Markdown
Contributor Author

PR Review: geotiff: add attrs compatibility-alias locking test

Blockers (must fix before merge)

None.

Suggestions (should fix, not blocking)

None.

Nits (optional improvements)

  • xrspatial/geotiff/tests/test_attrs_contract_aliases_1984.py:38_SENTINEL = -9999.0 is hardcoded at module scope. A one-line comment noting the value just needs to be float-castable and distinct from data would save the next reader a moment.
  • xrspatial/geotiff/tests/test_attrs_contract_aliases_1984.py:71-72 — fixed filename alias_nodatavals.tif per tmp_path. tmp_path already isolates per-test, so this is fine; just flagging that the project convention is unique tmp-file names.

What looks good

  • Five cases lock the contract: read-side fallback for both aliases, canonical-wins precedence, write-side suppression, and the NaN/None skip semantics in nodatavals.
  • The precedence test asserts at both the _resolve_nodata_attr chokepoint and the round-trip layer, which makes a regression easier to localise.
  • "Pure-NaN tuple resolves to None" pins a real corner of the resolver.

Checklist

  • Tests for both aliases
  • Precedence (canonical wins) asserted
  • NaN/None skip semantics covered
  • Asserts on both resolver and round-trip
  • No production code touched

@brendancol brendancol merged commit 4bd3e9b into main May 17, 2026
7 checks passed
brendancol added a commit that referenced this pull request May 17, 2026
Addresses #2002 review nit. The value itself does not matter; only that
it is float-castable and distinct from any data value the tests use.
brendancol added a commit that referenced this pull request May 18, 2026
* geotiff: add attrs canonical-tier locking test (#1984)

PR 4 of 7 from the attrs-contract plan in #1984. Adds
test_attrs_contract_canonical_1984.py: a single fixture that exercises
every canonical attr (crs, crs_wkt, transform, nodata, raster_type,
extra_tags, gdal_metadata, gdal_metadata_xml, x_resolution / y_resolution /
resolution_unit, _xrspatial_geotiff_contract), round-trips it through
to_geotiff -> open_geotiff, and asserts presence + value per key.

Sibling locking tests already cover the other tiers: aliases (#2002),
pass-through (#2004), and per-backend stamping of the contract version
(#2003). The canonical assertion list is what #1985 (parity matrix) and
#1986 (round-trip invariants) will import.

No production-code changes.

* geotiff: address review on attrs canonical-tier locking test

Addresses self-review on PR #2009:

* Add per-backend canonical-key presence parametrisation
  (eager-numpy, dask-numpy, gpu, dask-gpu). The 7-PR plan in #1984
  called for "one fixture per backend" coverage; previously only the
  eager read path was exercised.
* Comment ``_CANONICAL_KEYS`` to explain the deliberate omission of
  ``raster_type`` (the implicit 'area' default is encoded as absence,
  so the constant cannot express it; the two dedicated raster_type
  tests handle both branches).
* Drop ``AREA_OR_POINT: 'Area'`` from the shared ``_GDAL_META``
  fixture so the point-branch test does not inherit an inconsistent
  GDAL_METADATA entry.
* Relax the crs_wkt substring check to a regex covering 'WGS 84',
  'WGS_1984', and 'WGS-84' so the assertion survives PROJ-version
  variation across CI platforms.
* Note in a comment that ``gdal_metadata['TIFFTAG_SOFTWARE']`` and the
  raw Software TIFF tag (305) in ``extra_tags`` are independent
  channels the writer does not synchronise.
brendancol added a commit that referenced this pull request May 18, 2026
Two nits from the post-merge reviews on #2001 and #2002 that did not
make it into the merged commits.

#2001 user-guide page (``docs/source/user_guide/attrs_contract.rst``):
- ``transform``: corrected layout from
  ``(origin_x, pixel_width, 0, origin_y, 0, pixel_height)`` (GDAL
  ordering) to the rasterio Affine ordering
  ``(pixel_width, 0.0, origin_x, 0.0, pixel_height, origin_y)`` that
  ``_transform_tuple_from_pixel_geometry`` actually emits.
- ``crs_wkt``: relaxed "Always present" and "WKT2 string" to reflect
  that the dialect depends on the source path (pyproj synthesis is
  WKT2; VRT SRS-tag passthrough carries whatever dialect was on disk).
- ``nodata``: documented the read-side precedence chain (``nodata``,
  then ``nodatavals``, then ``_FillValue``) as codified in
  ``_resolve_nodata_attr``.
- ``colormap_rgba`` / ``cmap``: noted the ``Photometric == 3`` gate
  that the reader applies before emitting either attr.

#2002 alias locking test
(``xrspatial/geotiff/tests/test_attrs_contract_aliases_1984.py``):
- ``_SENTINEL``: added a one-line comment that the value just needs to
  be float-castable and distinct from data; the resolver and
  round-trip assertions are what the tests pin.

Coordinates with the canonical-tier docstring already corrected on
``_attrs.py`` in main.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant