From 3de5ece50e55a29211ff1b5383eb463d883c4685 Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Fri, 23 Oct 2026 13:51:19 +0200 Subject: [PATCH 1/3] Add CI for package tests and release builds Run tests across platforms and Python versions, check minimum dependencies, and validate built wheels and installed version metadata. Add changed-file formatting checks and weekly prerelease checks with failure tracking issues. --- .github/workflows/build_release.yml | 57 +++++++++++++++ .github/workflows/format.yml | 69 ++++++++++++++++++ .github/workflows/test_package.yml | 63 ++++++++++++++++ .github/workflows/weekly_checks.yml | 107 ++++++++++++++++++++++++++++ tests/test_package.py | 16 +++++ 5 files changed, 312 insertions(+) create mode 100644 .github/workflows/build_release.yml create mode 100644 .github/workflows/format.yml create mode 100644 .github/workflows/test_package.yml create mode 100644 .github/workflows/weekly_checks.yml create mode 100644 tests/test_package.py diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml new file mode 100644 index 0000000..ffbbadb --- /dev/null +++ b/.github/workflows/build_release.yml @@ -0,0 +1,57 @@ +name: build and test release + +on: + workflow_call: + workflow_dispatch: + inputs: + ref: + description: "Commit, tag or branch to build" + required: false + default: "" + type: string + +permissions: + contents: read + +jobs: + build-and-test-wheel: + name: wheel install py3.12 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ inputs.ref }} + # Needed for setuptools-scm + fetch-depth: 0 + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install build tooling + run: python -m pip install --upgrade pip build twine setuptools-scm + + - name: Show SCM-derived version + run: python -m setuptools_scm + + - name: Build and check metadata + run: | + python -m build + python -m twine check --strict dist/* + ls dist + + - name: Install the wheel + run: python -m pip install "$(ls dist/*.whl)" "pytest>=8" + + - name: Test the wheel outside the source tree + run: | + cp -r tests "$RUNNER_TEMP/wheeltest" + cd "$RUNNER_TEMP" + pytest wheeltest + + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + if-no-files-found: error diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 0000000..b5d741a --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,69 @@ +name: pre-commit (PR only on changed files) + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + +jobs: + detect_changes: + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.changed_files.outputs.changed }} + + steps: + - name: Checkout full history + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Detect changed files + id: changed_files + env: + BASE_REF: ${{ github.base_ref }} + run: | + git fetch origin "$BASE_REF" + CHANGED_FILES=$(git diff --name-only --diff-filter=d "origin/$BASE_REF...HEAD") + + delimiter="changed_$(openssl rand -hex 16)" + { + echo "changed<<$delimiter" + echo "$CHANGED_FILES" + echo "$delimiter" + } >> "$GITHUB_OUTPUT" + + - name: Show changed files + env: + CHANGED_FILES: ${{ steps.changed_files.outputs.changed }} + run: | + echo "Changed files:" + echo "$CHANGED_FILES" + + precommit: + needs: detect_changes + runs-on: ubuntu-latest + if: ${{ needs.detect_changes.outputs.changed != '' }} + + steps: + - name: Checkout PR branch + uses: actions/checkout@v6 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install pre-commit + run: pip install pre-commit + + - name: Run pre-commit (CI check-only stage) on changed files + env: + CHANGED_FILES: ${{ needs.detect_changes.outputs.changed }} + run: | + mapfile -t files <<< "$CHANGED_FILES" + pre-commit run --hook-stage manual --files "${files[@]}" --show-diff-on-failure diff --git a/.github/workflows/test_package.yml b/.github/workflows/test_package.yml new file mode 100644 index 0000000..3795484 --- /dev/null +++ b/.github/workflows/test_package.yml @@ -0,0 +1,63 @@ +name: Test suite + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + workflow_call: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + +jobs: + test: + name: ${{ matrix.os }} py${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + # Keep in sync w/ DeepLabCut + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v6 + + - uses: astral-sh/setup-uv@v7 + with: + python-version: ${{ matrix.python-version }} + activate-environment: true + + - name: Install + run: uv pip install . "pytest>=8" + + - name: Test the installed package + run: pytest tests + + test-dependency-floor: + name: dependency floor py3.10 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: astral-sh/setup-uv@v7 + with: + python-version: "3.10" + activate-environment: true + + - name: Install the lowest version each dependency declares + run: uv pip install --resolution lowest-direct . "pytest>=8" + + - name: Test + run: pytest tests + + build: + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: ./.github/workflows/build_release.yml diff --git a/.github/workflows/weekly_checks.yml b/.github/workflows/weekly_checks.yml new file mode 100644 index 0000000..4b755f7 --- /dev/null +++ b/.github/workflows/weekly_checks.yml @@ -0,0 +1,107 @@ +name: weekly checks + +on: + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: + inputs: + simulate_failure: + description: "Skip the checks, fail on purpose, and open a test tracking issue" + required: false + default: false + type: boolean + +permissions: + contents: read + +jobs: + dependency-prereleases: + if: ${{ !inputs.simulate_failure }} + name: dependency pre-releases py3.12 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: astral-sh/setup-uv@v7 + with: + python-version: "3.12" + activate-environment: true + + - name: Install with pre-releases allowed + run: uv pip install --prerelease allow . "pytest>=8" + + # `-o filterwarnings`, not `-W`: `-W` matches the module name exactly and would miss every submodule + - name: Test + run: | + pytest tests -o $'filterwarnings=error::DeprecationWarning:dlc_core\nerror::FutureWarning:dlc_core' + + build-release: + if: ${{ !inputs.simulate_failure }} + uses: ./.github/workflows/build_release.yml + + simulate-failure: + if: ${{ inputs.simulate_failure }} + runs-on: ubuntu-latest + steps: + - run: exit 1 + + notify: + # A job of its own: a step-level `if: failure()` never runs when its job dies before reaching it + needs: [dependency-prereleases, build-release, simulate-failure] + if: failure() && (github.event_name == 'schedule' || inputs.simulate_failure) + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + + steps: + - name: Open or update the tracking issue + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + PRERELEASE_RESULT: ${{ needs['dependency-prereleases'].result }} + BUILD_RESULT: ${{ needs['build-release'].result }} + SIMULATED: ${{ inputs.simulate_failure }} + run: | + set -euo pipefail + if [ "$SIMULATED" = "true" ]; then + title="Weekly checks failing (notification test)" + label="weekly checks test" + banner="> **Test.** Opened by a manual dispatch with *simulate_failure*; nothing is broken. Close it." + else + title="Weekly checks failing" + label="weekly checks" + banner="" + fi + gh label create "$label" --repo "$REPO" --color B60205 \ + --description "Tracking issue for the weekly CI checks" --force + + summary=$(printf '%s\n' \ + "| Check | Result |" \ + "| --- | --- |" \ + "| dependency pre-releases | $PRERELEASE_RESULT |" \ + "| build and test release | $BUILD_RESULT |") + + existing=$(gh issue list --repo "$REPO" --state open --label "$label" --json number --jq '.[0].number // empty') + if [ -n "$existing" ]; then + gh issue comment "$existing" --repo "$REPO" --body "$(printf '%s\n' "Still failing: [run $GITHUB_RUN_ID]($RUN_URL)" "" "$summary")" + else + body=$(printf '%s\n' \ + "Weekly checks failed: [run $GITHUB_RUN_ID]($RUN_URL)" \ + "" \ + "$summary" \ + "" \ + "These run against dependency pre-releases and the built wheel: a failure is usually an upstream change or a packaging problem, not a regression from a PR." \ + "" \ + "## Resolving" \ + "" \ + "1. Fix the failing job on a branch." \ + "2. Re-run the check on that branch: Actions -> build and test release -> Run workflow, or Actions -> weekly checks -> Run workflow." \ + "3. Merge the fix, dispatch Actions -> weekly checks on main, and close this issue. While it is open, later failures are added as comments.") + if [ -n "$banner" ]; then + body=$(printf '%s\n' "$banner" "" "$body") + fi + gh issue create --repo "$REPO" --title "$title" --body "$body" --label "$label" + fi diff --git a/tests/test_package.py b/tests/test_package.py new file mode 100644 index 0000000..0116d52 --- /dev/null +++ b/tests/test_package.py @@ -0,0 +1,16 @@ +# +# DeepLabCut Toolbox (deeplabcut.org) +# © A. & M.W. Mathis Labs +# https://github.com/DeepLabCut/dlc-core +# +# Contributors: +# https://github.com/DeepLabCut/dlc-core/graphs/contributors +# +# Licensed under GNU Lesser General Public License v3.0 +# + +import dlc_core + + +def test_version_comes_from_the_installed_distribution() -> None: + assert dlc_core.__version__ != "0+unknown" From a7c7fec4028d73ba1161e6be88f9d885f5b18828 Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Fri, 23 Oct 2026 15:21:39 +0200 Subject: [PATCH 2/3] Fix stale dlc_core references Use dlccore in weekly warning filters and the package version test. Update test header links to the renamed repository. --- .github/workflows/weekly_checks.yml | 2 +- tests/test_package.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/weekly_checks.yml b/.github/workflows/weekly_checks.yml index 4b755f7..989d147 100644 --- a/.github/workflows/weekly_checks.yml +++ b/.github/workflows/weekly_checks.yml @@ -34,7 +34,7 @@ jobs: # `-o filterwarnings`, not `-W`: `-W` matches the module name exactly and would miss every submodule - name: Test run: | - pytest tests -o $'filterwarnings=error::DeprecationWarning:dlc_core\nerror::FutureWarning:dlc_core' + pytest tests -o $'filterwarnings=error::DeprecationWarning:dlccore\nerror::FutureWarning:dlccore' build-release: if: ${{ !inputs.simulate_failure }} diff --git a/tests/test_package.py b/tests/test_package.py index 0116d52..8581f72 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -1,16 +1,16 @@ # # DeepLabCut Toolbox (deeplabcut.org) # © A. & M.W. Mathis Labs -# https://github.com/DeepLabCut/dlc-core +# https://github.com/DeepLabCut/dlccore # # Contributors: -# https://github.com/DeepLabCut/dlc-core/graphs/contributors +# https://github.com/DeepLabCut/dlccore/graphs/contributors # # Licensed under GNU Lesser General Public License v3.0 # -import dlc_core +import dlccore def test_version_comes_from_the_installed_distribution() -> None: - assert dlc_core.__version__ != "0+unknown" + assert dlccore.__version__ != "0+unknown" From df69cc13ca5bb286f95a4f4cf2a11a5d6bd0fb6f Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Mon, 2 Nov 2026 14:56:38 +0100 Subject: [PATCH 3/3] Trigger CI