Repository navigation
π· ci: adopt shared check, CodeQL and release workflows #2901
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "π§ͺ check" | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: ["main"] | |
| tags-ignore: ["**"] | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| schedule: | |
| - cron: "0 8 * * *" | |
| - cron: "0 6 * * 1" | |
| concurrency: | |
| group: check-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| test: | |
| name: "π§ͺ Test ${{ matrix.py }} - ${{ matrix.os }}" | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| py: | |
| - "3.15t" | |
| - "3.15" | |
| - "3.14t" | |
| - "3.14" | |
| - "3.13" | |
| - "3.12" | |
| - "3.11" | |
| - "pypy3.11" | |
| os: | |
| - ubuntu-24.04 | |
| - windows-2025 | |
| - macos-15 | |
| exclude: | |
| - {os: windows-2025, py: pypy3.11} | |
| steps: | |
| - name: "π₯ Check out code" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: "π Install uv" | |
| uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 | |
| with: | |
| version: latest-known | |
| python-version: "3.15" | |
| enable-cache: false | |
| - name: "π¦ Install tox" | |
| run: uv tool install --python-preference only-managed --python 3.15 tox --with tox-uv | |
| - name: "π Install Python ${{ matrix.py }}" | |
| run: uv python install --python-preference only-managed ${{ matrix.py }} | |
| - name: "π¦ Set up test suite" | |
| run: tox run -vv --notest --skip-missing-interpreters false -e ${{ matrix.py }} | |
| env: | |
| UV_PYTHON_PREFERENCE: only-managed | |
| # PyPy runs unmeasured: coverage has no JIT path there, so the suite takes 12m rather than 2m and roughly 60 of | |
| # its lock, heartbeat and poll deadlines lapse under the added latency, though they pass measured in isolation. | |
| # PyPy is not a coverage source either: the 100% gate stays CPython's. | |
| - name: "π§ͺ Run test suite" | |
| run: | | |
| if [[ "${{ matrix.py }}" == pypy* ]]; then | |
| tox run --skip-pkg-install -e ${{ matrix.py }} -- | |
| else | |
| tox run --skip-pkg-install -e ${{ matrix.py }} | |
| fi | |
| shell: bash | |
| env: | |
| PYTEST_ADDOPTS: "-vv --durations=20" | |
| UV_PYTHON_PREFERENCE: only-managed | |
| DIFF_AGAINST: HEAD | |
| - name: "π Rename coverage report file" | |
| if: ${{ !startsWith(matrix.py, 'pypy') }} | |
| run: | | |
| import os; import sys | |
| os.rename(f".tox/.coverage.${{ matrix.py }}", f".tox/.coverage.${{ matrix.py }}-{sys.platform}") | |
| shell: python | |
| - name: "π€ Upload coverage data" | |
| if: ${{ !startsWith(matrix.py, 'pypy') }} | |
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| include-hidden-files: true | |
| name: .coverage.${{ matrix.os }}.${{ matrix.py }} | |
| path: ".tox/.coverage.*" | |
| retention-days: 3 | |
| graalpy: | |
| name: "π§ͺ Test graalpy3.13 - ubuntu-24.04 (weekly)" | |
| # GraalPy 25.4.4 can freeze every thread at once under the threaded lock tests, so it stays off pull requests and | |
| # pushes. A weekly run keeps the capability probes the suite gates on honest on a second runtime. It runs on one | |
| # Linux job because those probes are a per-OS answer, and unmeasured, so the 100% gate stays CPython's. | |
| if: github.event_name == 'workflow_dispatch' || github.event.schedule == '0 6 * * 1' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 60 | |
| steps: | |
| - name: "π₯ Check out code" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: "π Install uv" | |
| uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 | |
| with: | |
| version: latest-known | |
| python-version: "3.15" | |
| enable-cache: false | |
| - name: "π¦ Install tox" | |
| run: uv tool install --python-preference only-managed --python 3.15 tox --with tox-uv | |
| - name: "π Install Python" | |
| run: uv python install --python-preference only-managed graalpy3.13 | |
| - name: "π¦ Set up test suite" | |
| run: tox run -vv --notest --skip-missing-interpreters false -e graalpy3.13 | |
| env: | |
| UV_PYTHON_PREFERENCE: only-managed | |
| - name: "π§ͺ Run test suite" | |
| run: tox run --skip-pkg-install -e graalpy3.13 -- | |
| env: | |
| PYTEST_ADDOPTS: "-vv --durations=20" | |
| UV_PYTHON_PREFERENCE: only-managed | |
| coverage: | |
| name: "π Combine coverage" | |
| runs-on: ubuntu-24.04 | |
| needs: test | |
| steps: | |
| - name: "π₯ Check out code" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: "π© Download coverage data" | |
| id: coverage-data | |
| background: true | |
| uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 | |
| with: | |
| path: .tox | |
| pattern: .coverage.* | |
| merge-multiple: true | |
| - name: "π Install uv" | |
| uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 | |
| with: | |
| version: latest-known | |
| python-version: "3.15" | |
| enable-cache: false | |
| - name: "π¦ Install tox" | |
| run: uv tool install --python-preference only-managed --python 3.15 tox --with tox-uv | |
| - name: "ποΈ Build package to generate version" | |
| run: uv build --python 3.15 --python-preference only-managed --wheel . --out-dir dist | |
| - name: "π¦ Set up coverage tool" | |
| run: tox -e coverage --notest | |
| env: | |
| UV_PYTHON_PREFERENCE: only-managed | |
| - name: "β³ Wait for coverage data" | |
| wait: coverage-data | |
| - name: "π Combine and report coverage" | |
| run: tox -e coverage --skip-pkg-install | |
| env: | |
| UV_PYTHON_PREFERENCE: only-managed | |
| - name: "π€ Upload HTML report" | |
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| name: html-report | |
| path: .tox/htmlcov | |
| check: | |
| name: "π ${{ matrix.tox_env }} - ${{ matrix.os }}" | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: | |
| - ubuntu-24.04 | |
| - windows-2025 | |
| tox_env: | |
| - dev | |
| - type | |
| - docs | |
| - pkg_meta | |
| exclude: | |
| - {os: windows-2025, tox_env: pkg_meta} | |
| steps: | |
| - name: "π₯ Check out code" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: "π Install uv" | |
| uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 | |
| with: | |
| version: latest-known | |
| python-version: "3.15" | |
| enable-cache: false | |
| - name: "π¦ Install tox" | |
| run: uv tool install --python-preference only-managed --python 3.15 tox --with tox-uv | |
| - name: "π¦ Set up check suite" | |
| run: tox run -vv --notest --skip-missing-interpreters false -e ${{ matrix.tox_env }} | |
| env: | |
| UV_PYTHON_PREFERENCE: only-managed | |
| - name: "π§ͺ Run check for ${{ matrix.tox_env }}" | |
| run: tox run --skip-pkg-install -e ${{ matrix.tox_env }} | |
| env: | |
| UV_PYTHON_PREFERENCE: only-managed | |
| nfs: | |
| name: "π§ͺ NFS mutual exclusion" | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: "π₯ Check out code" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: "π Install uv" | |
| uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 | |
| with: | |
| version: latest-known | |
| python-version: "3.15" | |
| enable-cache: false | |
| - name: "βοΈ Export and mount loopback NFS shares" | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y nfs-kernel-server nfs-common | |
| sudo mkdir -p /export/filelock | |
| sudo chmod 1777 /export/filelock | |
| echo "/export/filelock 127.0.0.1(rw,sync,no_subtree_check,no_root_squash,insecure)" | sudo tee /etc/exports | |
| sudo exportfs -ra | |
| sudo systemctl restart nfs-server | |
| # NFSv3 sends flock to the server through the NLM lock manager. rpcbind must be enabled; rpc-statd is a | |
| # static unit that enable cannot touch, so start it explicitly or a v3 flock blocks in the kernel forever. | |
| sudo systemctl enable --now rpcbind | |
| sudo systemctl start rpc-statd | |
| # A client can otherwise reopen a removed claim from its cache and block until the acquire timeout. | |
| # Disable attribute and lookup caching, and use nosharecache to test independent client caches. | |
| for m in nfs4a nfs4b nfs3a nfs3b; do sudo mkdir -p "/mnt/$m"; done | |
| sudo mount -t nfs -o vers=4.2,noac,lookupcache=none 127.0.0.1:/export/filelock /mnt/nfs4a | |
| sudo mount -t nfs -o vers=4.2,nosharecache,noac,lookupcache=none 127.0.0.1:/export/filelock /mnt/nfs4b | |
| sudo mount -t nfs -o vers=3,noac,lookupcache=none 127.0.0.1:/export/filelock /mnt/nfs3a | |
| sudo mount -t nfs -o vers=3,nosharecache,noac,lookupcache=none 127.0.0.1:/export/filelock /mnt/nfs3b | |
| for m in nfs4a nfs4b nfs3a nfs3b; do sudo chmod 1777 "/mnt/$m"; done | |
| mount | grep /mnt/nfs | |
| - name: "π§ͺ Verify NFSv4 mutual exclusion across two client caches" | |
| env: | |
| # StrictSoftFileLock is the lock the gate enforces on NFS: its claim queue hands off fairly, so it finishes | |
| # every hold with no overlap on both versions (the strict claim retries the transient ESTALE that NFSv3's | |
| # churn raises). The other two print their behavior ungated and neither is safe to gate on here: native flock | |
| # does not exclude across NFS client caches (overlaps appear), and the poll-based soft lock starves out under | |
| # NFS latency before finishing its holds. Prefer StrictSoftFileLock on NFS. | |
| FILELOCK_VERIFY_LOCKS: StrictSoftFileLock | |
| run: | | |
| uv venv | |
| uv pip install . | |
| if ! timeout 420 .venv/bin/python tasks/verify_filesystem.py /mnt/nfs4a /mnt/nfs4b; then | |
| for root in /export/filelock /mnt/nfs4a /mnt/nfs4b; do | |
| echo "$root claims:" | |
| find "$root/StrictSoftFileLock.lock.filelock/claims" -maxdepth 1 -type f -printf '%f\n' | |
| done | |
| exit 1 | |
| fi | |
| - name: "π§ͺ Verify NFSv3 mutual exclusion across two client caches" | |
| env: | |
| FILELOCK_VERIFY_LOCKS: StrictSoftFileLock | |
| run: timeout 420 .venv/bin/python tasks/verify_filesystem.py /mnt/nfs3a /mnt/nfs3b | |
| smb: | |
| name: "π§ͺ SMB mutual exclusion" | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: "π₯ Check out code" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: "π Install uv" | |
| uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 | |
| with: | |
| version: latest-known | |
| python-version: "3.15" | |
| enable-cache: false | |
| - name: "βοΈ Export and mount a loopback SMB share" | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y samba cifs-utils | |
| sudo mkdir -p /srv/filelock | |
| sudo chmod 1777 /srv/filelock | |
| sudo tee -a /etc/samba/smb.conf >/dev/null <<'CONF' | |
| [filelock] | |
| path = /srv/filelock | |
| writable = yes | |
| guest ok = yes | |
| create mask = 0666 | |
| directory mask = 0777 | |
| CONF | |
| sudo sed -i '/\[global\]/a \ map to guest = Bad User\n server min protocol = SMB2' /etc/samba/smb.conf | |
| sudo systemctl restart smbd | |
| # Two mounts are two independent CIFS client caches over one server, for a multi-client check. | |
| sudo mkdir -p /mnt/smba /mnt/smbb | |
| for m in smba smbb; do | |
| sudo mount -t cifs //127.0.0.1/filelock "/mnt/$m" \ | |
| -o "guest,uid=$(id -u),gid=$(id -g),file_mode=0777,dir_mode=0777,vers=3.0" | |
| done | |
| mount | grep /mnt/smb | |
| - name: "π§ͺ Verify SMB mutual exclusion across two client caches" | |
| # CIFS byte-range locks are mandatory and server-enforced, so FileLock excludes and finishes every hold: it is | |
| # the lock the gate enforces here. SoftFileLock prints ungated (it excludes but its poll-acquire can starve | |
| # under contention), and the strict claim needs an atomic no-replace hard link SMB does not reliably provide. | |
| env: | |
| FILELOCK_VERIFY_LOCKS: FileLock | |
| run: | | |
| uv venv | |
| uv pip install . | |
| timeout 240 .venv/bin/python tasks/verify_filesystem.py /mnt/smba /mnt/smbb | |
| capability: | |
| name: "π§ͺ without ${{ matrix.block }}" | |
| runs-on: ubuntu-24.04 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| block: | |
| - fcntl | |
| - sqlite3 | |
| steps: | |
| - name: "π₯ Check out code" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: "π Install uv" | |
| uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 | |
| with: | |
| version: latest-known | |
| python-version: "3.15" | |
| enable-cache: false | |
| - name: "π§ͺ Verify the fallback without ${{ matrix.block }}" | |
| env: | |
| FILELOCK_BLOCK_MODULE: ${{ matrix.block }} | |
| run: | | |
| uv venv | |
| uv pip install . | |
| export PYTHONPATH="$PWD/tasks/capability" | |
| .venv/bin/python tasks/capability/assert_fallback.py | |
| .venv/bin/python tasks/verify_filesystem.py | |
| symlink: | |
| name: "π§ͺ Windows symlinks ${{ matrix.developer_mode == '1' && 'granted' || 'denied' }}" | |
| runs-on: windows-2025 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| developer_mode: ["0", "1"] | |
| steps: | |
| - name: "π₯ Check out code" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: "π Install uv" | |
| uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 | |
| with: | |
| version: latest-known | |
| python-version: "3.15" | |
| enable-cache: false | |
| - name: "π¦ Install tox" | |
| run: uv tool install --python-preference only-managed --python 3.15 tox --with tox-uv | |
| # Windows grants symlink creation two ways, so both have to be closed to deny it: Developer Mode lets an | |
| # unprivileged process create them, and SeCreateSymbolicLinkPrivilege lets any holder create them regardless. | |
| # Clearing only the registry value left the runner's privilege intact and both variants probed identical. | |
| - name: "βοΈ Set Developer Mode to ${{ matrix.developer_mode }}" | |
| run: > | |
| reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v AllowDevelopmentWithoutDevLicense /d ${{ matrix.developer_mode }} | |
| - name: "π¦ Set up test suite" | |
| run: tox run -vv --notest --skip-missing-interpreters false -e 3.15 | |
| - name: "π Report the probed capability" | |
| if: matrix.developer_mode == '1' | |
| run: | | |
| $env:PYTHONPATH = "$PWD\tasks" | |
| uv run --no-project --python 3.15 --with coverage python -c "import coverage_pragmas; print(coverage_pragmas.CAPABILITIES)" | |
| - name: "π§ͺ Run test suite with symlinks granted" | |
| if: matrix.developer_mode == '1' | |
| run: tox run --skip-pkg-install -e 3.15 -- | |
| # deny_symlink reports the token privilege, the Developer Mode value, and whether a symlink still succeeds in | |
| # this process and in a child, so the log says which mechanism actually governs rather than assuming either. | |
| - name: "π Report the probed capability" | |
| if: matrix.developer_mode == '0' | |
| run: | | |
| $env:PYTHONPATH = "$PWD\tasks" | |
| uv run --no-project --python 3.15 python tasks\deny_symlink.py uv run --no-project --python 3.15 --with coverage python -c "import coverage_pragmas; print(coverage_pragmas.CAPABILITIES)" | |
| - name: "π§ͺ Run test suite with symlinks denied" | |
| if: matrix.developer_mode == '0' | |
| run: uv run --no-project --python 3.15 python tasks\deny_symlink.py tox run --skip-pkg-install -e 3.15 -- | |
| termux: | |
| name: "π§ͺ Termux (Android)" | |
| runs-on: ubuntu-24.04-arm | |
| steps: | |
| - name: "π₯ Check out code" | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: "π§ͺ Run the suite in a real Termux userland via tox" | |
| run: docker compose run --rm termux | |
| gate: | |
| name: "π¦ ${{ github.event_name == 'pull_request' && 'Required checks pass' || 'Non-PR checks pass' }}" | |
| if: ${{ !cancelled() }} # codespell:ignore cancelled | |
| needs: [test, graalpy, coverage, check, nfs, smb, capability, symlink, termux] | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: "π¦ Check all jobs passed" | |
| env: | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| run: | | |
| jq -e ' | |
| all( | |
| to_entries[]; | |
| .value.result == "success" or | |
| (.key == "graalpy" and .value.result == "skipped") | |
| ) | |
| ' <<<"$NEEDS_JSON" |