Skip to content

Merge pull request #221 from BioVisionCenter/fix/flaky-win-tests3 #1114

Merge pull request #221 from BioVisionCenter/fix/flaky-win-tests3

Merge pull request #221 from BioVisionCenter/fix/flaky-win-tests3 #1114

Workflow file for this run

# CI — primary test suite
#
# Scope: Runs on every PR and push to main/tags. Uses pixi (locked environments)
# for fast, reproducible tests across all supported Python versions and platforms.
# Jobs: lint (pre-commit), check-manifest, test matrix (pixi), deploy (pip, tag-only)
# Matrix: PRs → ubuntu × py3.11/3.13 | main/tags → ubuntu+macos+windows × py3.11–3.14
name: CI
on:
push:
branches:
- main
tags:
- "v*"
pull_request:
workflow_dispatch:
schedule:
# run every week to catch upstream breakage early
- cron: "0 0 * * 0"
# cancel in-progress runs that use the same workflow and branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
check-manifest:
# check-manifest is a tool that checks that all files in version control are
# included in the sdist (unless explicitly excluded)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: pipx run --spec check-manifest==0.51 check-manifest
typecheck:
# `src/` must stay free of type errors. `tests/` is deliberately not gated
# yet — see the 1.0 checklist.
name: type check (src)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: 🔧 Set up pixi
uses: prefix-dev/setup-pixi@a09b6247153796b190642a2b53fac4241043cf6f # v0.10.0
with:
environments: dev
- name: 🔍 ty
run: pixi run -e dev ty check src/
docs:
# Build the docs and run every snippet on PRs. Kept here rather than in
# docs.yml so the deploy workflow's `contents: write` is not extended to
# pull requests.
name: docs build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: 🔧 Set up pixi
uses: prefix-dev/setup-pixi@a09b6247153796b190642a2b53fac4241043cf6f # v0.10.0
with:
environments: docs
- name: 📄 Run docs snippets
run: pixi run -e docs test_snippets
- name: 📚 Build docs
run: pixi run -e docs build_docs
test-min-deps:
# Installs the exact lower bounds declared in pyproject so the floors are
# tested rather than asserted. Lowering a floor without updating this job
# will not be caught anywhere else.
name: minimum dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- name: 📦 Install at the declared floors
# The `test` extra deliberately is NOT installed here, and cannot be:
# test-only dependencies demand versions that contradict ngio's runtime
# contract — `aiomoto[pandas]` needs pyarrow>=22 against our pyarrow>=16,
# and s3fs pins fsspec to its own date release against our fsspec>=2025.3.
# Only the tooling the reduced suite below needs is installed.
run: |
python -m pip install --upgrade pip
python -m pip install -c .github/min-constraints.txt . \
pytest pytest-xdist scikit-image
- name: 🔒 Verify the floors actually took effect
run: python .github/check_min_deps.py
- name: Restore shared data cache (read-only)
# Read-only, so this job adds no cache-poisoning surface; on a miss the
# fixtures are downloaded instead.
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: data/
key: "${{ runner.os }}-data-${{ hashFiles('src/ngio/utils/_datasets.py') }}"
restore-keys: |
${{ runner.os }}-data-
- name: 🧪 Run Tests
# Scoped to `tests/unit` (overriding `testpaths`) because
# `tests/stores/conftest.py` imports aiomoto/boto3 at module level, and
# `test_s3fs_config.py` imports s3fs — none of which can be installed
# alongside the floors. That excludes 21 of 441 test functions, all of
# them exercising s3fs/aiomoto rather than a declared runtime floor.
run: |
pytest tests/unit -n 4 --durations=10 \
--ignore=tests/unit/utils/test_s3fs_config.py
setup:
# Outputs a reduced matrix for PRs, full matrix for main/tags/schedule
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo 'matrix=[{"environment":"test11","platform":"ubuntu-latest"},{"environment":"test13","platform":"ubuntu-latest"}]' >> $GITHUB_OUTPUT
else
echo 'matrix=[{"environment":"test11","platform":"ubuntu-latest"},{"environment":"test12","platform":"ubuntu-latest"},{"environment":"test13","platform":"ubuntu-latest"},{"environment":"test14","platform":"ubuntu-latest"},{"environment":"test11","platform":"macos-latest"},{"environment":"test12","platform":"macos-latest"},{"environment":"test13","platform":"macos-latest"},{"environment":"test14","platform":"macos-latest"},{"environment":"test11","platform":"windows-latest"},{"environment":"test12","platform":"windows-latest"},{"environment":"test13","platform":"windows-latest"},{"environment":"test14","platform":"windows-latest"}]' >> $GITHUB_OUTPUT
fi
test:
name: ${{ matrix.platform }} (${{ matrix.environment }})
needs: setup
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.setup.outputs.matrix) }}
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: 🔧 Set up pixi
uses: prefix-dev/setup-pixi@a09b6247153796b190642a2b53fac4241043cf6f # v0.10.0
with:
environments: ${{ matrix.environment }}
- name: Restore shared data cache (PR — read-only)
if: github.event_name == 'pull_request'
id: cache-data-restore
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: data/
key: "${{ runner.os }}-data-${{ hashFiles('src/ngio/utils/_datasets.py') }}"
restore-keys: |
${{ runner.os }}-data-
- name: Restore/save shared data cache (trusted)
if: github.event_name != 'pull_request'
id: cache-data
# zizmor: ignore[cache-poisoning] -- restricted to trusted (non pull_request)
# triggers above; PR runs use the read-only restore step instead, so this
# cache can't be poisoned by untrusted code.
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: data/
key: "${{ runner.os }}-data-${{ hashFiles('src/ngio/utils/_datasets.py') }}"
restore-keys: |
${{ runner.os }}-data-
- name: 🧪 Run Tests
run: pixi run -e ${{ matrix.environment }} pytest -n 4 --durations=10 ${{ matrix.platform == 'ubuntu-latest' && matrix.environment == 'test11' && '--cov=ngio --cov-report=xml' || '' }}
# If something goes wrong with scheduled tests, open an issue in the repo
- name: 📝 Report Failures
if: failure() && github.event_name == 'schedule'
uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PLATFORM: ${{ matrix.platform }}
PYTHON: ${{ matrix.environment }}
RUN_ID: ${{ github.run_id }}
TITLE: "[test-bot] Scheduled tests are failing"
with:
filename: .github/TEST_FAIL_TEMPLATE.md
update_existing: true
- name: Coverage
if: success() && matrix.platform == 'ubuntu-latest' && matrix.environment == 'test11'
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: /home/runner/work/ngio/ngio/coverage.xml
deploy:
name: deploy
# Every gate must pass before anything reaches PyPI, not just the test matrix.
needs: [test, lint, check-manifest, typecheck, docs, test-min-deps]
if: success() && startsWith(github.ref, 'refs/tags/') && github.event_name != 'schedule'
runs-on: ubuntu-latest
environment: pypi
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing on PyPi
# see https://docs.pypi.org/trusted-publishers/
id-token: write
# This permission allows writing releases
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: 🐍 Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.x"
- name: 👷 Build
run: |
python -m pip install build twine
python -m build
- name: 🔍 Check the built distributions
run: twine check dist/*
- name: 🏷️ Assert the tag matches the built version
# hatch-vcs derives the version from the tag, so a mismatch means the
# checkout is not what the tag points at.
env:
TAG: ${{ github.ref_name }}
run: |
python - <<'EOF'
import os, pathlib, sys
tag = os.environ["TAG"].removeprefix("v")
wheels = list(pathlib.Path("dist").glob("*.whl"))
if len(wheels) != 1:
sys.exit(f"expected exactly one wheel, found {wheels}")
built = wheels[0].name.split("-")[1]
if built != tag:
sys.exit(f"tag {tag!r} does not match built version {built!r}")
print(f"tag and built version agree: {built}")
EOF
- name: 🚢 Publish to PyPI
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
- name: 🚢 Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release create "${GITHUB_REF_NAME}" ./dist/* --generate-notes