Repository navigation
chore(deps): update dependency chrome-for-testing to v157 (#1799) #7201
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: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| # `labeled`/`unlabeled` (beyond the opened/synchronize/reopened defaults) so | |
| # toggling the `full-ci` label re-triggers CI and immediately flips the | |
| # matrix between the subset and the full grid — otherwise the label only | |
| # takes effect on the next push. | |
| # | |
| # `ready_for_review` (also non-default) is the recovery path for issue #509: | |
| # if a `synchronize` ever fails to dispatch this required check (e.g. a merge | |
| # commit pushed under a non-trigger-capable token), marking the PR draft and | |
| # back to ready re-evaluates the head SHA and (re)dispatches — a no-side-effect | |
| # alternative to close+reopen. | |
| types: [opened, synchronize, reopened, labeled, unlabeled, ready_for_review] | |
| # Nightly full sweep on main: catches a portability/CVE/dep regression that a | |
| # trimmed internal-PR matrix would skip, without waiting for the next release. | |
| schedule: | |
| - cron: "27 6 * * *" | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.head_ref || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # -------------------------------------------------------------------- | |
| # setup — emit the test matrix + classify the diff. ONE cheap job that | |
| # decides everything downstream: | |
| # • matrix: internal PR → a representative SUBSET (all 4 Pythons + all | |
| # 3 OSes in 4 legs, Windows-weighted since it's the buggiest | |
| # platform, and ubuntu+3.13 always present = the coverage-gate | |
| # and Codecov cell). push:main / nightly cron / fork PRs (after | |
| # approval) / a `full-ci`-labelled PR → the FULL 3×4 = 12 grid. | |
| # Trimming applies ONLY to internal PRs; main + nightly always | |
| # run the full grid (release safety-net — a release is only ever | |
| # cut from a green main). | |
| # • code: true unless the PR touches ONLY docs/meta files. The heavy | |
| # jobs (tests, build, docker) gate on this so a docs-only PR | |
| # skips them entirely. Fail-safe: anything unrecognised, an | |
| # empty diff, or any non-PR event → code=true (never skip on | |
| # main/cron). The `ci required checks passed` aggregator counts a | |
| # skipped job as passing, so the required check still reports. | |
| # DRAFT PRs (#623): EVERY job in this workflow carries | |
| # `github.event.pull_request.draft != true`, so a draft PR runs nothing at | |
| # all — including `setup` and the aggregator. A batch of drafts can be opened | |
| # cheaply and each flipped to "ready for review" one at a time to fire its CI. | |
| # `!= true` (not `== false`) so push:main / cron — where the `draft` field is | |
| # null — fail-safe to running. | |
| # | |
| # The guard used to stop short of `setup` and the aggregator, on the theory | |
| # that the required context must always report. It doesn't need to: GitHub | |
| # reports a job skipped by its `if:` as a check run whose conclusion is | |
| # SUCCESS, so `ci required checks passed` resolves on a draft instead of | |
| # hanging at "Expected — waiting for status" (that failure mode, #196, comes | |
| # from a WORKFLOW-level `paths:`/branch filter, which suppresses the check run | |
| # entirely — not from a job-level `if:`). Guarding the aggregator too is the | |
| # honest reading: a draft now shows the context greyed-out/skipped rather than | |
| # a solid green earned by a run in which zero test cells executed. | |
| # | |
| # `ready_for_review` is already in the pull_request types, so marking a draft | |
| # ready re-dispatches the whole workflow on the current head SHA. | |
| # TRADE-OFF, stated plainly: a draft PR gets NO CI feedback. Push freely while | |
| # drafting; the first real signal arrives when you flip it ready. | |
| # -------------------------------------------------------------------- | |
| setup: | |
| name: setup (matrix + change filter) | |
| if: github.event.pull_request.draft != true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| matrix: ${{ steps.matrix.outputs.matrix }} | |
| code: ${{ steps.changes.outputs.code }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| # Need both PR endpoints in history to diff base..head. | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| # Shared classifier (also used by security.yml) — keep the docs/meta | |
| # allowlist in one place. | |
| - name: Classify changed files (code vs docs-only) | |
| id: changes | |
| uses: $/.github/actions/changed-code | |
| - name: Decide matrix (subset vs full) | |
| id: matrix | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| IS_FORK: ${{ github.event.pull_request.head.repo.fork }} | |
| LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # FULL 3×4 grid. macOS/Windows were brought green via a .cmd test-stub | |
| # entrypoint + cross-platform guards; the full sweep keeps a | |
| # version-specific portability bug from hiding on an untested OS×py cell. | |
| full='{"include":[ | |
| {"os":"ubuntu-latest","python":"3.11"}, | |
| {"os":"ubuntu-latest","python":"3.12"}, | |
| {"os":"ubuntu-latest","python":"3.13"}, | |
| {"os":"ubuntu-latest","python":"3.14"}, | |
| {"os":"macos-latest","python":"3.11"}, | |
| {"os":"macos-latest","python":"3.12"}, | |
| {"os":"macos-latest","python":"3.13"}, | |
| {"os":"macos-latest","python":"3.14"}, | |
| {"os":"windows-latest","python":"3.11"}, | |
| {"os":"windows-latest","python":"3.12"}, | |
| {"os":"windows-latest","python":"3.13"}, | |
| {"os":"windows-latest","python":"3.14"} | |
| ]}' | |
| # SUBSET for internal PRs: 4 legs covering all 4 Pythons and all 3 OSes. | |
| # ubuntu+3.13 is mandatory (coverage gate + Codecov upload live there); | |
| # Windows gets two legs (our buggiest platform); one macOS leg. | |
| subset='{"include":[ | |
| {"os":"ubuntu-latest","python":"3.13"}, | |
| {"os":"windows-latest","python":"3.11"}, | |
| {"os":"windows-latest","python":"3.14"}, | |
| {"os":"macos-latest","python":"3.12"} | |
| ]}' | |
| use_full=false | |
| if [ "$EVENT_NAME" != "pull_request" ]; then | |
| use_full=true # push:main, schedule | |
| elif [ "$IS_FORK" = "true" ]; then | |
| use_full=true # fork PR → full gamut | |
| elif printf '%s' "$LABELS" | grep -q '"full-ci"'; then | |
| use_full=true # escape hatch label | |
| fi | |
| if [ "$use_full" = "true" ]; then | |
| echo "Using FULL matrix (event=$EVENT_NAME fork=$IS_FORK)." | |
| echo "matrix=$(printf '%s' "$full" | jq -c .)" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Using SUBSET matrix (internal PR)." | |
| echo "matrix=$(printf '%s' "$subset" | jq -c .)" >> "$GITHUB_OUTPUT" | |
| fi | |
| # -------------------------------------------------------------------- | |
| # Unit tests across the matrix `setup` emitted. A consistent 96% coverage | |
| # gate applies on ALL THREE OSes (#929): the floor lives in pyproject | |
| # addopts (--cov-fail-under=96). Linux + macOS (both POSIX) hold it | |
| # directly; Windows measures through `.coveragerc-win`, which omits the | |
| # POSIX/ConPTY surface it physically can't run so the Windows-reachable | |
| # code holds the same 96% (see the pytest step below). 96 (not higher) so | |
| # the known coverage.py TRACER NOISE on Python 3.11/3.14 (~1% of executed | |
| # lines dropped vs 3.12/3.13 — a coverage.py artifact, not real gaps) stays | |
| # comfortably within margin on every cell. Skipped wholesale on a docs-only PR. | |
| # -------------------------------------------------------------------- | |
| tests: | |
| name: tests (${{ matrix.os }}, ${{ matrix.python }}) | |
| needs: setup | |
| if: needs.setup.outputs.code == 'true' && github.event.pull_request.draft != true | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| # Concurrency cap as a simple boolean toggle via the CI_CAP_PARALLEL repo | |
| # variable: set it to `false` to LIFT the cap (256 = GitHub's matrix-job | |
| # ceiling, i.e. effectively uncapped on the 12-cell grid); anything else — | |
| # `true`, unset, or garbage — keeps the safe default cap of 4 so a clauster | |
| # run doesn't monopolise the account-wide runner pool. `4`/`256` are UNQUOTED | |
| # numeric literals, so the expression evaluates to a NUMBER (max-parallel | |
| # rejects a string — a quoted value is what broke the first attempt); the | |
| # variable is only ever string-COMPARED (case-insensitive), never parsed as a | |
| # number, so no value can error or break matrix expansion. The 4-cell subset | |
| # PRs are unaffected by a cap of 4 — only the full grid (push:main / nightly) | |
| # is throttled. | |
| max-parallel: ${{ vars.CI_CAP_PARALLEL != 'false' && 4 || 256 }} | |
| matrix: ${{ fromJSON(needs.setup.outputs.matrix) }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Set up uv + Python | |
| uses: $/.github/actions/setup | |
| with: | |
| python-version: ${{ matrix.python }} | |
| # `shell: bash` keeps one script across all three runners (Git Bash ships | |
| # on Windows). Matrix values flow through env (not inlined) for zizmor. | |
| - name: pytest | |
| shell: bash | |
| env: | |
| PYTHON_VERSION: ${{ matrix.python }} | |
| RUNNER_OS: ${{ runner.os }} | |
| MATRIX_OS: ${{ matrix.os }} | |
| run: | | |
| # Time the suite and record it — the runners (esp. Windows) vary a lot, so a | |
| # per-cell wall time on the run Summary + the slowest tests (`--durations`) make | |
| # a REAL slowdown obvious instead of mined from job timestamps. `|| rc=$?` so the | |
| # timing is still written when pytest fails (the default shell is `bash -eo pipefail`). | |
| start=$(date +%s) | |
| rc=0 | |
| # Consistent 96% coverage floor on ALL THREE OSes (#929) — the gate lives in | |
| # pyproject addopts (--cov-fail-under=96). macOS is POSIX, so it runs the same | |
| # code as Linux and holds the floor directly. Windows physically can't run | |
| # clauster's POSIX code (fcntl/os.openpty/termios/dir-fsync) and its ConPTY | |
| # keeper is exercised via a fake process seam on Linux, so Windows measures | |
| # through `.coveragerc-win`, which excludes that un-runnable surface (omit | |
| # pty_keeper + `# pragma: skip-on-win`); the Windows-REACHABLE code then holds | |
| # the same 96%. Every OS still emits coverage.xml + junit.xml for its Codecov flag. | |
| if [ "$RUNNER_OS" = "Windows" ]; then | |
| uv run --python "$PYTHON_VERSION" pytest -q --durations=15 \ | |
| --cov-config=.coveragerc-win \ | |
| --cov-report=xml --junitxml=junit.xml -o junit_family=legacy || rc=$? | |
| else | |
| uv run --python "$PYTHON_VERSION" pytest -q --durations=15 \ | |
| --cov-report=xml --junitxml=junit.xml -o junit_family=legacy || rc=$? | |
| fi | |
| dur=$(( $(date +%s) - start )) | |
| { | |
| echo "### ⏱ pytest timing" | |
| echo "" | |
| echo "| OS | Python | Wall time | Result |" | |
| echo "| --- | --- | --- | --- |" | |
| echo "| $MATRIX_OS | $PYTHON_VERSION | ${dur}s | $([ "$rc" -eq 0 ] && echo pass || echo FAIL) |" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| exit "$rc" | |
| # Upload coverage from EVERY cell, tagged with its OS as a Codecov flag | |
| # (Linux/macOS/Windows), so coverage is broken out per platform — the axis | |
| # where clauster actually varies (74 sys.platform branches; zero version | |
| # branches). The internal-PR subset spans all three OSes, so every code PR | |
| # uploads all three flags on the current commit and no carryforward is needed. | |
| # Token-based upload (no id-token perm). Non-blocking: Codecov isn't a | |
| # required check and upload flakiness must never fail CI. `!cancelled()` so | |
| # a FAILED pytest still uploads (pytest writes the reports before the gate), | |
| # surfacing a coverage drop precisely when it matters. Skips the release | |
| # bot's PRs (knope opens them as the clauster-ci App; code never moves, so the | |
| # status/comment is pure noise) — filtered on the PR author; the login is empty | |
| # on push, so main-branch uploads are unaffected. | |
| - name: Upload coverage to Codecov | |
| if: ${{ !cancelled() && github.event.pull_request.user.login != 'clauster-ci[bot]' }} | |
| uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: coverage.xml | |
| flags: ${{ runner.os }} | |
| fail_ci_if_error: false | |
| # Upload test results once per OS — the always-present gate cell for each | |
| # (Linux 3.13 / macOS 3.12 / Windows 3.14, in both the subset and the full | |
| # matrix) — tagged with its OS as a Codecov flag, mirroring the coverage | |
| # upload, so Test Analytics (flaky/slow tests) breaks out per platform. One | |
| # cell per OS (not every matrix cell) keeps the duration metrics from | |
| # double-counting Python versions. Every cell now writes junit.xml (pytest step). | |
| - name: Upload test results to Codecov | |
| if: >- | |
| ${{ !cancelled() && github.event.pull_request.user.login != 'clauster-ci[bot]' | |
| && ((runner.os == 'Linux' && matrix.python == '3.13') | |
| || (runner.os == 'macOS' && matrix.python == '3.12') | |
| || (runner.os == 'Windows' && matrix.python == '3.14')) }} | |
| uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: junit.xml | |
| flags: ${{ runner.os }} | |
| report_type: test_results | |
| fail_ci_if_error: false | |
| # -------------------------------------------------------------------- | |
| # Build sdist + wheel. Cheap; confirms the package is shippable. | |
| # -------------------------------------------------------------------- | |
| build: | |
| name: build sdist + wheel | |
| needs: setup | |
| if: needs.setup.outputs.code == 'true' && github.event.pull_request.draft != true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Set up uv + Python | |
| uses: $/.github/actions/setup | |
| with: | |
| python-version: "3.13" | |
| extras: "" # build only needs the build backend, not test/lint tooling | |
| - name: Build | |
| run: uv build | |
| - name: Upload dist artefacts | |
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| name: dist | |
| path: dist/ | |
| retention-days: 7 | |
| if-no-files-found: error | |
| # -------------------------------------------------------------------- | |
| # Docker smoke-test. The image is built + Trivy-scanned in security.yml | |
| # but never RUN there — this is the runtime regression net. Build once, | |
| # then prove the server actually boots and serves /healthz two ways: | |
| # 1. `docker run` — the image itself. | |
| # 2. `docker compose` — the published compose.yaml (#97). | |
| # The image binds 0.0.0.0, so it requires enforced auth to start; we mint | |
| # a throwaway argon2id hash from the image's own auth module (no real | |
| # secret) — /healthz is public, so liveness needs no login. Docs-only PRs | |
| # skip it. | |
| # -------------------------------------------------------------------- | |
| docker: | |
| name: docker smoke-test | |
| needs: setup | |
| if: needs.setup.outputs.code == 'true' && github.event.pull_request.draft != true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Buildx | |
| uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 | |
| # Build amd64 once, load locally, and tag it as the compose image name too | |
| # so `docker compose` uses this build (pull policy "missing" → local hit), | |
| # never the published :latest. The gha cache keeps cold builds cheap. | |
| - name: Build image | |
| uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 | |
| with: | |
| context: . | |
| load: true | |
| platforms: linux/amd64 | |
| tags: | | |
| clauster:smoke | |
| ghcr.io/schubydoo/clauster:latest | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # Throwaway hash from the image's OWN auth code — non-interactive (the CLI's | |
| # hash-password uses getpass), no hard-coded credential to scan-flag. | |
| - name: Mint a throwaway auth hash | |
| id: hash | |
| run: | | |
| set -euo pipefail | |
| HASH=$(docker run --rm --entrypoint python clauster:smoke -c \ | |
| "from clauster.auth import hash_password, make_hasher; print(hash_password(make_hasher(), 'ci-smoke'))") | |
| echo "::add-mask::$HASH" | |
| echo "hash=$HASH" >> "$GITHUB_OUTPUT" | |
| # Boot prerequisites the image deliberately omits: | |
| # • clauster.yml — clauster needs one to exist (env vars only override | |
| # scalars); projects_root=/projects exists in the image. | |
| # • a `claude` binary — clauster resolves it on PATH at startup and exits | |
| # if missing. The image ships WITHOUT claude by design (users mount it — | |
| # see compose.yaml), so we mount a tiny stub, exactly as documented. The | |
| # compose override mirrors compose.yaml's commented-out claude mount. | |
| - name: Write smoke config + claude stub | |
| run: | | |
| set -euo pipefail | |
| mkdir -p config projects | |
| cat > config/clauster.yml <<'YML' | |
| schema_version: 1 | |
| projects_root: /projects | |
| state_dir: /config/state | |
| log_format: json | |
| YML | |
| cat > claude-stub <<'SH' | |
| #!/bin/sh | |
| case "$1" in | |
| --version) echo "2.1.200 (Claude Code)" ;; | |
| agents) echo "[]" ;; | |
| *) echo "[]" ;; | |
| esac | |
| SH | |
| chmod +x claude-stub | |
| cat > compose.ci.yaml <<'YML' | |
| # CI-only override: mount the stub claude onto PATH (compose.yaml ships | |
| # this commented out). Not committed — written by the smoke-test job. | |
| services: | |
| clauster: | |
| image: clauster:smoke | |
| volumes: | |
| - ./claude-stub:/usr/local/bin/claude:ro | |
| YML | |
| - name: docker run → /healthz | |
| env: | |
| HASH: ${{ steps.hash.outputs.hash }} | |
| run: | | |
| set -euo pipefail | |
| docker run -d --name clauster-smoke -p 7621:7621 \ | |
| -e CLAUSTER_AUTH_ENABLED=true \ | |
| -e CLAUSTER_AUTH_PASSWORD_REQUIRED=true \ | |
| -e CLAUSTER_AUTH_PASSWORD_HASH="$HASH" \ | |
| -v "$PWD/config:/config" \ | |
| -v "$PWD/projects:/projects" \ | |
| -v "$PWD/claude-stub:/usr/local/bin/claude:ro" \ | |
| clauster:smoke | |
| ok=false | |
| for _ in $(seq 1 30); do | |
| code=$(curl -fsS -o /dev/null -w '%{http_code}' http://127.0.0.1:7621/healthz || true) | |
| if [ "$code" = "200" ]; then ok=true; break; fi | |
| sleep 2 | |
| done | |
| if [ "$ok" != "true" ]; then | |
| echo "::error::docker run: /healthz never returned 200" | |
| docker logs clauster-smoke || true | |
| exit 1 | |
| fi | |
| echo "docker run: /healthz 200 ✓" | |
| docker rm -f clauster-smoke | |
| - name: docker compose → /healthz | |
| env: | |
| # compose.yaml requires this (its `:?` guard); a real env var holds the | |
| # literal hash fine — only .env files need the `$`→`$$` doubling. | |
| CLAUSTER_AUTH_PASSWORD_HASH: ${{ steps.hash.outputs.hash }} | |
| run: | | |
| set -euo pipefail | |
| docker compose -f compose.yaml -f compose.ci.yaml up -d | |
| ok=false | |
| for _ in $(seq 1 30); do | |
| code=$(curl -fsS -o /dev/null -w '%{http_code}' http://127.0.0.1:7621/healthz || true) | |
| if [ "$code" = "200" ]; then ok=true; break; fi | |
| sleep 2 | |
| done | |
| if [ "$ok" != "true" ]; then | |
| echo "::error::docker compose: /healthz never returned 200" | |
| docker compose -f compose.yaml -f compose.ci.yaml logs || true | |
| exit 1 | |
| fi | |
| echo "docker compose: /healthz 200 ✓" | |
| - name: Tear down | |
| if: always() | |
| run: | | |
| docker compose -f compose.yaml -f compose.ci.yaml down -v || true | |
| # Also reap the docker-run container — a failed healthz poll exits | |
| # before its inline `docker rm`, leaving it bound to port 7621. | |
| docker rm -f clauster-smoke || true | |
| # -------------------------------------------------------------------- | |
| # musl portability canary. The `tests` matrix is all glibc/native; the | |
| # published Docker image runs on Alpine/musl, so run the unit suite once on | |
| # musl + CPython 3.14 (the image's exact libc + interpreter) to catch a | |
| # musl-only regression before it ships. Driven via `docker run` from the ubuntu | |
| # host rather than a `container:` job — GitHub injects a glibc node that can't | |
| # execute on musl, so actions/checkout inside an Alpine container fails. No | |
| # coverage gate (this is a portability canary, not the coverage cell); atheris | |
| # has no musl wheel, so the dev extra is skipped and the fuzz smoke test | |
| # importorskips (1 skip), exactly as it does elsewhere. | |
| # -------------------------------------------------------------------- | |
| musl-tests: | |
| name: musl smoke-test (alpine / py3.14) | |
| needs: setup | |
| if: needs.setup.outputs.code == 'true' && github.event.pull_request.draft != true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: pytest on alpine musl | |
| env: | |
| # Kept byte-identical to the Dockerfile's `FROM alpine:` pin — the two | |
| # must never diverge, or this canary validates a different Alpine than | |
| # we ship. Hoisted out of the `run:` script into a step `env:` so the | |
| # annotation below sits on a mapping key a Renovate custom manager can | |
| # match cleanly (the manager itself lives in the shared renovate-config | |
| # preset, keyed to the `ALPINE_MUSL_IMAGE` name + this annotation). | |
| # renovate: datasource=docker depName=alpine versioning=docker | |
| ALPINE_MUSL_IMAGE: "alpine:3.24.3@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a" | |
| run: | | |
| set -euo pipefail | |
| docker run --rm -v "$PWD":/src:ro \ | |
| "$ALPINE_MUSL_IMAGE" \ | |
| sh -euc ' | |
| apk add --no-cache uv python3 git >/dev/null | |
| cp -a /src /work && cd /work | |
| uv sync --frozen --no-dev --python python3 | |
| uv pip install --python .venv/bin/python \ | |
| pytest pytest-asyncio pytest-xdist pytest-timeout hypothesis pyte | |
| .venv/bin/python -m pytest --ignore=tests/e2e -o addopts="" \ | |
| -n auto --timeout=120 --timeout-method=thread -q -p no:cacheprovider | |
| ' | |
| # -------------------------------------------------------------------- | |
| # Required-checks aggregator. ONE check for the `main` ruleset to gate | |
| # on (context: "ci required checks passed"), regardless of how many | |
| # underlying jobs exist. `if: always()` so it runs even when a need | |
| # failed or was skipped; jq fails it iff any listed job is neither | |
| # success nor skipped. Add a ci.yml job by appending to `needs:`. | |
| # -------------------------------------------------------------------- | |
| ci-required-checks-passed: | |
| name: ci required checks passed | |
| # `always()` so a failed/skipped need still lets this job evaluate them; the | |
| # draft term skips the whole aggregator on a draft PR, matching its upstreams | |
| # (see the DRAFT PRs note above the `setup` job). | |
| if: always() && github.event.pull_request.draft != true | |
| needs: | |
| - setup | |
| - tests | |
| - build | |
| - docker | |
| - musl-tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| steps: | |
| - name: Check required jobs passed | |
| env: | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| run: | | |
| failing=$(echo "$NEEDS_JSON" | jq -r ' | |
| to_entries[] | |
| | select(.value.result != "success" and .value.result != "skipped") | |
| | "\(.key): \(.value.result)" | |
| ') | |
| if [ -n "$failing" ]; then | |
| echo "Failed or cancelled jobs:" | |
| echo "$failing" | |
| exit 1 | |
| fi | |
| echo "All required jobs passed (or were intentionally skipped)." |