Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,23 @@ jobs:
- name: Build
run: mvn compile

# Runs both test tiers. The 'interop' tag is excluded from a bare `mvn test` so a
# contributor without a working uv/zarr-python setup can still run the suite locally;
# CI has Python, so it clears the exclusion and runs everything.
#
# Interop belongs on the pull-request path, not on a schedule: it catches bugs caused by
# the change under review -- a dtype or codec where zarr-java's reader and writer agree
# with each other but not with anyone else -- and that is feedback you want on the PR.
# It costs about 20s now that zarr-python runs as one worker process instead of one
# interpreter launch per test case.
- name: Test
env:
MAVEN_OPTS: "-Xmx6g"
run: |
if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then
mvn --no-transfer-progress test -DargLine="-Xmx6g" -DrunS3Tests=true
mvn --no-transfer-progress test -DargLine="-Xmx6g" -DrunS3Tests=true -DexcludedTestGroups=
else
mvn --no-transfer-progress test -DargLine="-Xmx6g"
mvn --no-transfer-progress test -DargLine="-Xmx6g" -DexcludedTestGroups=
fi
- name: Assemble JAR
run: mvn package -DskipTests
Expand Down
105 changes: 105 additions & 0 deletions .github/workflows/data-type-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Keeps src/test/resources/spec-data-types-v3.json honest.
#
# That file is the external conformance list DataTypeConformanceTest checks zarr-java against.
# It is generated from zarr-python's data type registry and committed, so the Java test stays
# offline and fast. Committing it buys reproducibility -- a given commit always tests against a
# known list -- at the cost of the list going stale. These two jobs pay that cost back.
#
# The split follows who caused the breakage:
#
# stale-check runs per pull request. Fails when the committed list disagrees with the
# zarr-python version this repository pins. That is caused by the change under
# review (a dependency bump without a regeneration, or a hand-edited file), so
# it belongs on the pull-request path.
#
# upstream-drift runs nightly. Fails when the *latest* zarr-python knows a data type our
# pinned version did not. No pull request caused that, so gating pull requests
# on it would fail innocent changes for reasons their authors cannot act on.
# As a scheduled job it becomes a task someone picks up: bump the pin,
# regenerate, and either implement the new data type or add it to
# KNOWN_UNSUPPORTED with a note.

name: Data Type Conformance List

on:
workflow_dispatch:
pull_request:
branches: [ "main" ]
push:
branches: [ "main" ]
schedule:
# 04:23 UTC daily. Off the hour so it does not queue behind everyone else's midnight cron.
- cron: '23 4 * * *'

jobs:
stale-check:
name: Committed list matches the pinned zarr-python
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'

- name: Install uv
uses: astral-sh/setup-uv@v6

# Pinned explicitly: uv.lock resolves zarr differently per Python version, so letting uv
# pick an interpreter would make this check depend on what happens to be installed. 3.11 is
# what the committed list was generated with and what ci.yml runs the interop tier on.
- name: Regenerate the conformance list
run: uv run --python 3.11 src/test/python-scripts/generate_spec_data_types.py

- name: Fail if it differs from the committed copy
run: |
if ! git diff --exit-code src/test/resources/spec-data-types-v3.json; then
echo
echo "::error::spec-data-types-v3.json is out of date with the pinned zarr-python."
echo "Regenerate and commit it:"
echo " uv run src/test/python-scripts/generate_spec_data_types.py"
echo "If the diff adds a data type, either implement it in dev.zarr.zarrjava.v3.DataType"
echo "or add it to DataTypeConformanceTest.KNOWN_UNSUPPORTED with a note on what it needs."
exit 1
fi

upstream-drift:
name: Latest zarr-python has no data types we have not seen
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'

- name: Install uv
uses: astral-sh/setup-uv@v6

# `uv run` alone would reuse uv.lock and regenerate a byte-identical file forever, which
# would make this job pass vacuously. Re-resolving zarr is the entire point: it is how a
# new upstream data type reaches us.
- name: Resolve the latest zarr-python
run: |
uv lock --upgrade-package zarr
uv run --python 3.11 python -c "import zarr; print('resolved zarr-python', zarr.__version__)"

- name: Regenerate the conformance list against it
run: uv run --python 3.11 src/test/python-scripts/generate_spec_data_types.py

- name: Report drift
run: |
if ! git diff --exit-code src/test/resources/spec-data-types-v3.json; then
echo
echo "::warning::A newer zarr-python describes data types differently than our pinned version."
echo "This is not a broken build -- it is a to-do. To act on it:"
echo " 1. bump the zarr pin in pyproject.toml and commit the updated uv.lock"
echo " 2. uv run src/test/python-scripts/generate_spec_data_types.py"
echo " 3. implement any new data type, or list it in KNOWN_UNSUPPORTED with a note"
exit 1
fi
echo "No drift: the latest zarr-python matches the committed conformance list."
58 changes: 57 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,36 @@ data

### Run Tests Locally

To be able to run the tests locally, make sure to have `python3.11` and `uv` installed.
The test suite is split into two tiers.

**Fast tier (default).** Offline, no Python required, runs in seconds:

```
mvn test
```

It covers the metadata-only data type conformance checks, per-data-type round-trips, and the
committed golden fixtures.

**Interop tier.** Cross-checks zarr-java against zarr-python, and needs `python3.11` and `uv`
installed. It is tagged `interop` and excluded from a bare `mvn test`, so that a contributor
without a working Python setup still gets a useful run. Clear the exclusion to include it:

```
mvn test -DexcludedTestGroups= # everything, both tiers
mvn test -DexcludedTestGroups= -Dtest=ZarrPythonTests # just the interop tier
```

**CI runs both tiers on every pull request** — the tag exists for local convenience, not to keep
these tests out of the build. They belong on the pull-request path because they catch bugs caused
by the change under review: a test that writes with zarr-java and reads back with zarr-java passes
even when the reader and writer share the same misunderstanding of the spec. The resulting store is
then wrong for every other tool, and only a second implementation can catch that.

The interop tier is cheap enough for that. zarr-python runs as one long-lived worker process for
the whole suite (see `ZarrPythonWorker` and `src/test/python-scripts/zarr_python_worker.py`) rather
than one `uv run` per test case; interpreter startup used to dominate, and removing it took the
full interop run from ~129s to ~19s.

Furthermore, you will need the `l4_sample` test data:

Expand All @@ -69,6 +98,33 @@ Furthermore, you will need the `l4_sample` test data:
&& unzip l4_sample.zip
`

### Data Type Coverage

`DataTypeConformanceTest` checks zarr-java's data types against an external list
(`src/test/resources/spec-data-types-v3.json`, generated from zarr-python's registry). Data types we
do not implement yet are enumerated in that test's `KNOWN_UNSUPPORTED` set.

The list is deliberately *not* derived from our own `DataType` enum. A provider built from the enum
can only assert that what we implemented is implemented, so a data type we never added stays
invisible and no test can fail for it — which is how `float16` and `string` went unnoticed.

Implementing a data type therefore means deleting its entry from `KNOWN_UNSUPPORTED`; the assertion
is bidirectional, so leaving a stale entry there fails the build too.

To refresh the list after a zarr-python upgrade:

```
uv run src/test/python-scripts/generate_spec_data_types.py
```

Two CI jobs keep it from silently rotting (`.github/workflows/data-type-drift.yml`), split by who
caused the problem. On every pull request, the list is regenerated against the pinned zarr-python
and the build fails if it differs — that catches a dependency bump without a regeneration. Nightly,
it is regenerated against the *latest* zarr-python instead, which is how a newly specified data
type reaches us. The nightly check is deliberately not a pull-request gate: no pull request causes
upstream to release a version, and failing unrelated changes for it only teaches people to ignore
a red build.

### Code Style & Formatting

This project uses IntelliJ IDEA default Java formatting
Expand Down
21 changes: 21 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
<properties>
<maven.compiler.release>8</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--
JUnit tags excluded from the default test run. Override to run everything:
mvn test -DexcludedTestGroups=
-->
<excludedTestGroups>interop</excludedTestGroups>
<jackson.version>2.20.0</jackson.version>
<aws.version>2.34.6</aws.version>
<netcdfJavaVersion>5.9.1</netcdfJavaVersion>
Expand Down Expand Up @@ -156,6 +161,22 @@
<version>3.2.5</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<!--
Tests are split into two tiers.

The default run is fast and fully offline: metadata conformance checks,
per-data-type round-trips and the committed golden fixtures. It needs no
Python, so a contributor without a working uv/zarr-python setup can still
run it and get a useful result.

The 'interop' tier drives zarr-python for cross-implementation checks. It is
excluded here purely as a local convenience. CI has Python and clears the
exclusion with -DexcludedTestGroups=, so both tiers run on every pull
request. Interop tests catch bugs no self-round-trip test can, and now that
zarr-python runs as a single worker process they cost about 20s, so there is
no reason to keep them off the pull-request path.
-->
<excludedGroups>${excludedTestGroups}</excludedGroups>
<environmentVariables>
<!-- Force Testcontainers to use a newer Docker API version supported by your local Daemon -->
<DOCKER_API_VERSION>1.44</DOCKER_API_VERSION>
Expand Down
Loading
Loading