From 1d971623fab70d8a8176a45b1d08a070bfbef98e Mon Sep 17 00:00:00 2001 From: Remis Lape Date: Tue, 1 Sep 2026 13:42:24 +0100 Subject: [PATCH 1/2] Add CI dcio has never had CI. That was tolerable while it was one person's file reader; it is not now that it is becoming the record layer under SCALCS, EKDIST and HJCFIT, where a regression reaches three other projects. The suite runs in under two seconds, so the matrix covers every supported Python (3.11-3.13) and all three platforms rather than one representative build. Reading and writing SCN means struct packing and endianness, which is exactly the kind of thing that is fine on the machine it was written on. Three gates beyond running pytest: Example records present. Several tests take a legacy SCN from examples/ and skip when it is absent. A skip is a silent pass: if those files ever stopped being checked out, the golden burst counts and the round-trip checks against real records would quietly stop running while CI stayed green. Oldest dependencies. numpy>=2.0 is declared but nothing has ever run against 2.0 itself, so the floor is an assertion rather than a tested fact. That job pins Python 3.11, since numpy 2.0 has no wheel for 3.13. Verified locally that the suite passes on numpy 2.1, the oldest available for this machine's 3.13. Wheel and console scripts. An editable install resolves imports and entry points from the source tree, so it cannot catch an undeclared package or an entry point that does not resolve. The three dcio-* commands have never been run from an installed wheel; all three answer --help locally. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 156 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..97f7c68 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,156 @@ +# dcio has never had CI. That was tolerable while it was one person's file +# reader; it is not now that it is becoming the record layer under SCALCS, +# EKDIST and HJCFIT, where a regression reaches three other projects. +# +# The suite is fast -- under two seconds -- so the matrix covers every +# supported Python and all three platforms rather than one representative +# build. Reading and writing SCN means struct packing and endianness, which is +# exactly the kind of thing that is fine on the machine it was written on. + +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + test: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + + strategy: + fail-fast: false + matrix: + include: + # requires-python is >=3.11; every version in that range is tested. + - { name: linux-py311, os: ubuntu-24.04, python: '3.11' } + - { name: linux-py312, os: ubuntu-24.04, python: '3.12' } + - { name: linux-py313, os: ubuntu-24.04, python: '3.13', coverage: true } + - { name: macos, os: macos-14, python: '3.13' } + - { name: windows, os: windows-2022, python: '3.12' } + + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python }} + + - name: Install + shell: bash + run: | + python -m pip install --upgrade pip + pip install -e '.[dev]' + pip list + + # Several tests take a legacy SCN from examples/ and skip when it is + # absent. A skip is a silent pass: if those files ever stop being + # checked out, the golden burst counts and the round-trip checks against + # real records would quietly stop running while CI stayed green. Assert + # they are here rather than trusting the skip to be noticed. + - name: Example records present + shell: bash + run: | + python - <<'PY' + from pathlib import Path + import sys + required = ["glyr_simulated.scn", "glyr_experimental.scn"] + d = Path("examples/scn") + missing = [n for n in required if not (d / n).is_file()] + if missing: + sys.exit("missing example records (tests would skip): " + ", ".join(missing)) + print("example records present:", ", ".join(required)) + PY + + - name: Tests + shell: bash + run: pytest + + - name: Tests with coverage + if: matrix.coverage + shell: bash + run: pytest --cov --cov-report=term-missing + + # numpy>=2.0 is declared but nothing has ever run against 2.0 itself, so the + # floor is an assertion rather than a tested fact. NEP 50 changed how scalars + # promote, and impose_resolution does float arithmetic on array elements -- + # if the real minimum has drifted upward, this is where it shows. + oldest: + name: oldest-deps + runs-on: ubuntu-24.04 + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-python@v6 + with: + python-version: '3.11' + + - name: Install with the declared minimum numpy + run: | + python -m pip install --upgrade pip + pip install -e '.[dev]' + pip install 'numpy==2.0.*' + pip list + + - name: Tests + run: pytest + + package: + name: package + runs-on: ubuntu-24.04 + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: Build sdist and wheel + run: | + python -m pip install --upgrade pip build + python -m build + + # An editable install resolves imports and console scripts from the + # source tree, so it cannot catch a package that was never declared or an + # entry point that does not resolve. Installing the built wheel into a + # clean environment is the only check that can. + - name: Install the wheel and check it + run: | + python -m venv /tmp/check + /tmp/check/bin/pip install --upgrade pip + /tmp/check/bin/pip install dist/*.whl + cd /tmp + /tmp/check/bin/python - <<'PY' + from dcio.formats import scn, ssd, scan_ini + from dcio.analysis import ( + from_scn, impose_resolution, set_periods, + extract_bursts, extract_burst_intervals, bursts_from_record, + ) + print("installed wheel imports formats and analysis") + PY + + # The three console scripts are declared in pyproject and have never been + # run from an installed wheel. A broken entry point is invisible until a + # user types the command. + - name: Console scripts resolve + run: | + for cmd in dcio-scn2txt dcio-txt2scn dcio-csv2ssd; do + echo "--- $cmd" + /tmp/check/bin/$cmd --help > /dev/null || exit 1 + done + echo "all three console scripts run" From 6ce104aa21778a3839a0c30b4f4001172930b405 Mon Sep 17 00:00:00 2001 From: Remis Lape Date: Tue, 1 Sep 2026 13:44:14 +0100 Subject: [PATCH 2/2] Check only the API master has in the wheel job The import list named the burst functions from #1, which is not on this branch. Those go in when M1 merges. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97f7c68..86a4034 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,8 +138,8 @@ jobs: /tmp/check/bin/python - <<'PY' from dcio.formats import scn, ssd, scan_ini from dcio.analysis import ( + Periods, SingleChannelRecord, from_scn, impose_resolution, set_periods, - extract_bursts, extract_burst_intervals, bursts_from_record, ) print("installed wheel imports formats and analysis") PY