Repository navigation
ci: reuse the merge-queue verdict on main and trim coverage/sbt work #3623
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: CI | |
| on: | |
| push: | |
| # Only main. A landing on the v5 integration branch runs through the | |
| # pull_request event of its PR into main instead: the full-tier jobs | |
| # below also run for `head_ref == release/v5-prerelease`. A push trigger | |
| # on a release-named branch makes zizmor audit this as a release | |
| # workflow (cache-poisoning). | |
| branches: [main] | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| # The merge queue tests each queued PR merged onto the tip of main (plus | |
| # the PRs ahead of it) before it lands. `ci-ok` below is the required | |
| # check, so this event has to run the same pull_request-tier job set. | |
| merge_group: | |
| types: [checks_requested] | |
| schedule: | |
| # Nightly on main: the `full` tier (e2e-full, cargo-vex-matrix-full, | |
| # yarn-berry-full) and e2e-docker, which pull_request runs skip. | |
| - cron: '41 5 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| hosted_e2e: | |
| # Underscored on purpose: `inputs.hosted-e2e` is not valid expression | |
| # syntax (a hyphenated name needs `inputs['hosted-e2e']`). | |
| description: 'hosted-e2e: auto (obey vars.HOSTED_E2E_DISABLED) | force | skip' | |
| type: choice | |
| default: auto | |
| options: [auto, force, skip] | |
| permissions: | |
| contents: read | |
| env: | |
| # Keep backtrace file/line information without linking full debug symbols | |
| # into every integration binary. Shared dev caches use the same setting. | |
| CARGO_PROFILE_DEV_DEBUG: line-tables-only | |
| # A newer push to the same PR supersedes its older run; nothing else is | |
| # cancelled. Push runs are grouped per commit: a concurrency group holds only | |
| # ONE pending run, so a shared `refs/heads/main` group silently cancelled every | |
| # queued push but the newest during a merge burst, and most main commits never | |
| # got a verdict. Merge-group refs (gh-readonly-queue/...) are unique per queue | |
| # entry already. The nightly keeps its own group so it never queues behind (or | |
| # cancels) a push. | |
| concurrency: | |
| group: ci-${{ github.event.pull_request.number || (github.event_name == 'push' && github.sha) || github.ref }}${{ github.event_name == 'schedule' && '-nightly' || '' }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| # Required independently of ci-ok so the merge queue sees a compile/lint | |
| # failure immediately. Expensive jobs also depend on this preflight. | |
| clippy: | |
| if: github.event.pull_request.draft != true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: read | |
| actions: read | |
| outputs: | |
| reuse: ${{ steps.merge-queue.outputs.reuse }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| # Main remains the cache writer. Skip duplicate test execution only | |
| # when this workflow passed in the merge queue on the identical SHA. | |
| - name: Check merge-queue validation | |
| id: merge-queue | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: python3 scripts/ci-reuse-merge-group.py | |
| - name: Install Rust | |
| # rustup is pre-installed on GitHub-hosted runners. `toolchain | |
| # install` with no name reads rust-toolchain.toml in the repo root, | |
| # then installs the pinned channel + listed components if missing | |
| # (retried past download blips; see the script). No third-party | |
| # action dependency needed for toolchain setup. | |
| run: scripts/rustup-retry.sh toolchain install | |
| - name: Cache cargo | |
| # Swatinem/rust-cache instead of a raw actions/cache of the whole | |
| # target/ dir: it prunes the cache to dependency artifacts (~5-10x | |
| # smaller), which keeps this repo's total cache footprint inside | |
| # GitHub's 10 GiB budget (a raw target/ cache let every PR save evict | |
| # main's caches). save-if restricts writes to main so PR branches | |
| # restore without churning the budget. Compatible build jobs use | |
| # shared-key by profile + runner image (not job or package-manager | |
| # version). rust-cache also hashes the toolchain, Cargo manifests | |
| # and Rust environment. Check/coverage/release keep separate caches. | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run clippy | |
| run: cargo clippy --locked --workspace --all-features -- -D warnings | |
| - name: Check every test target before starting expensive jobs | |
| # Clippy above checks the shipped targets. This catches errors in | |
| # cfg(test), integration tests and feature-gated targets without | |
| # linking hundreds of executables or waiting for the E2E fan-out. | |
| run: cargo check --locked --workspace --all-targets --all-features | |
| # Cargo's tests do not link the addon's test=false cdylib. Build it once | |
| # per OS alongside the test shards; serializing it ahead of shard 1 left | |
| # that shard waiting on a separate production-profile compilation. | |
| # Linux also exercises the JS loader and engine/provider boundary. | |
| node-addon: | |
| if: github.event.pull_request.draft != true | |
| needs: clippy | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| exclude: | |
| - os: ${{ github.event_name == 'pull_request' && 'macos-latest' || '' }} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| shell: bash | |
| run: scripts/rustup-retry.sh toolchain install | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| # The addon uses production features; the test shards enable dev | |
| # features. Keep both cache writers instead of racing to save a | |
| # partial dependency set under one key. | |
| shared-key: addon-${{ matrix.os }} | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '20.20.2' | |
| - name: Build addon | |
| env: | |
| SOCKET_PATCH_NODE_CARGO_PROFILE: dev | |
| run: node crates/socket-patch-node/npm/scripts/build-addon.mjs | |
| - name: Smoke-test addon | |
| if: matrix.os == 'ubuntu-latest' && needs.clippy.outputs.reuse != 'true' | |
| run: node --test crates/socket-patch-node/npm/test/smoke.mjs | |
| # Check the standalone installer, release scripts, and native installer | |
| # test harnesses. | |
| lint-ecosystems: | |
| if: github.event.pull_request.draft != true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Python — test native installer harnesses | |
| run: python3 -B -m unittest discover -s scripts/tests -v | |
| - name: Shell — shellcheck the curl|sh installer | |
| # shellcheck is pre-installed on ubuntu-latest. | |
| run: shellcheck --shell=sh scripts/install.sh | |
| - name: Shell — run the installer end to end | |
| # shellcheck proves the script parses; this proves it installs. The | |
| # script is what install.socket.dev/patch serves and what the README | |
| # tells people to pipe into a shell, so "it downloads the latest | |
| # release, verifies SHA256SUMS, and produces a binary that runs" is | |
| # worth asserting on every PR rather than discovering from a user. | |
| # Installs the LATEST RELEASE, not this checkout — on a version-bump PR | |
| # that is deliberately the previous version. | |
| run: | | |
| sh scripts/install.sh | |
| command -v socket-patch | |
| socket-patch --version | |
| - name: Shell — run the installer against an alternate origin | |
| # Exercises SOCKET_PATCH_BASE_URL (and SOCKET_PATCH_INSTALL_DIR) with a | |
| # base that is not the default. Uses GitHub's own releases base, which | |
| # is the same URL shape install.socket.dev serves, so the template the | |
| # script builds is covered regardless of whether the Socket relay is | |
| # deployed yet. The dedicated Socket-origin check is the next step. | |
| run: | | |
| SOCKET_PATCH_BASE_URL=https://github.com/SocketDev/socket-patch/releases \ | |
| SOCKET_PATCH_INSTALL_DIR="$RUNNER_TEMP/alt-origin" \ | |
| sh scripts/install.sh | |
| "$RUNNER_TEMP/alt-origin/socket-patch" --version | |
| - name: Shell — install through install.socket.dev, once it exists | |
| # The whole point of the relay is that a client never has to reach | |
| # github.com. That is only assertable against the deployed host, so this | |
| # step skips itself until the host resolves rather than being red from | |
| # the day it merges (same posture as the installer-drift workflow). | |
| run: | | |
| if ! curl -sfI -m 20 https://install.socket.dev/patch/latest >/dev/null 2>&1; then | |
| echo "::notice::install.socket.dev/patch/latest does not answer yet — skipping the Socket-origin install." | |
| exit 0 | |
| fi | |
| latest=$(curl -fsSL -m 20 https://install.socket.dev/patch/latest) | |
| echo "install.socket.dev reports latest=$latest" | |
| SOCKET_PATCH_BASE_URL=https://install.socket.dev/patch/SocketDev/socket-patch/releases \ | |
| SOCKET_PATCH_INSTALL_DIR="$RUNNER_TEMP/socket-origin" \ | |
| sh scripts/install.sh | |
| installed=$("$RUNNER_TEMP/socket-origin/socket-patch" --version | awk '{print $NF}') | |
| if [ "$installed" != "$latest" ]; then | |
| echo "::error::install.socket.dev says latest is $latest but installed $installed" >&2 | |
| exit 1 | |
| fi | |
| - name: Shell — the installer URL is consistent across the docs | |
| # The README, the script's own usage comment, and the hosting runbook | |
| # all name the canonical URL. Keeping them in lockstep is the whole | |
| # promise of install.socket.dev/patch being "a copy of this file". | |
| run: | | |
| for f in README.md scripts/install.sh docs/installer-hosting.md; do | |
| if ! grep -qF 'https://install.socket.dev/patch' "$f"; then | |
| echo "Error: $f no longer references https://install.socket.dev/patch" >&2 | |
| exit 1 | |
| fi | |
| done | |
| - name: Shell — shellcheck the release scripts | |
| run: shellcheck scripts/version-sync.sh scripts/release-lint.sh scripts/dispatch-publish.sh | |
| # Release-readiness gate (scripts/release-lint.sh — the same checks the | |
| # Release workflow's `version` job runs before publishing anything): | |
| # - every PR/push: version coherence — version-sync.sh must be a no-op, | |
| # so a hand-edited version in any single packaging site fails CI here | |
| # instead of surfacing mid-release; | |
| # - the release train's rolling `release-sync` PR (docs/release-train/ | |
| # DESIGN.md §3.7), which moves main to the newest cut tag (rc or | |
| # stable): CHANGELOG has a non-empty section for that version and the | |
| # tag already exists. Only a same-repo `release-sync` branch targeting | |
| # main gets this path; the same branch name from a fork (or aimed at | |
| # another base) gets the full gate below; | |
| # - any other PR that bumps the workspace version: the full gate — | |
| # CHANGELOG has a dated, non-empty section for the new version and the | |
| # tag doesn't already exist. | |
| release-readiness: | |
| if: github.event.pull_request.draft != true | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Lint release readiness | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_REF: ${{ github.head_ref }} | |
| BASE_REF: ${{ github.base_ref }} | |
| HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| if [ "$EVENT_NAME" = "pull_request" ] && [ "$HEAD_REF" = "release-sync" ] \ | |
| && [ "$HEAD_REPO" = "$REPO" ] && [ "$BASE_REF" = "main" ]; then | |
| bash scripts/release-lint.sh --tag-exists | |
| exit 0 | |
| fi | |
| if [ "$EVENT_NAME" = "pull_request" ]; then | |
| # Compare the workspace version against the PR base to detect a | |
| # version bump. The shallow checkout doesn't have the base | |
| # commit; fetch just that object. | |
| git fetch --quiet --depth 1 origin "$BASE_SHA" | |
| BASE_VERSION="$(git show "$BASE_SHA:Cargo.toml" | grep '^version = ' | head -1 | sed 's/version = "\(.*\)"/\1/')" | |
| HEAD_VERSION="$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')" | |
| if [ "$BASE_VERSION" != "$HEAD_VERSION" ]; then | |
| echo "Version bump PR detected ($BASE_VERSION -> $HEAD_VERSION); running the full release gate." | |
| bash scripts/release-lint.sh --tag-check | |
| exit 0 | |
| fi | |
| fi | |
| bash scripts/release-lint.sh --sync-only | |
| test: | |
| if: github.event.pull_request.draft != true | |
| needs: clippy | |
| # No ubuntu-latest leg: `coverage` runs this same `cargo test | |
| # --workspace` (debug, default features, plus Go and vexctl) on ubuntu, | |
| # instrumented, and fails on any test failure. A plain ubuntu leg ran | |
| # every Linux test a second time for ~12 job-minutes per run. | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [macos-latest, windows-latest] | |
| # Two legs per OS (scripts/ci-test-shard.py): one leg linked ~240 | |
| # test binaries and ran them serially for ~26 min. Windows balances | |
| # measured runtime plus linking work; macOS keeps the count split. | |
| # Shard 1 also owns unit tests and doctests. | |
| shard: [1, 2] | |
| exclude: | |
| # macOS legs run on main, the merge queue and nightly, not per PR push. | |
| - os: ${{ github.event_name == 'pull_request' && 'macos-latest' || '' }} | |
| # One compile-only writer per OS is enough for an already tested SHA. | |
| - shard: ${{ needs.clippy.outputs.reuse == 'true' && 2 || 0 }} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 50 | |
| env: | |
| VEXCTL_VERSION: v0.3.0 | |
| CI_TEST_TIMINGS: ${{ matrix.os == 'windows-latest' && 'scripts/ci-test-durations.json' || '' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| # rustup is pre-installed on GitHub-hosted runners. `toolchain | |
| # install` with no name reads rust-toolchain.toml in the repo root, | |
| # then installs the pinned channel + listed components if missing | |
| # (retried past download blips; see the script). No third-party | |
| # action dependency needed for toolchain setup. | |
| shell: bash | |
| run: scripts/rustup-retry.sh toolchain install | |
| - name: Cache cargo | |
| # Swatinem/rust-cache, main-only saves: see the first `Cache cargo` | |
| # step in this file. | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| shared-key: dev-${{ matrix.os }} | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Warm the test cache after merge-queue validation | |
| if: needs.clippy.outputs.reuse == 'true' | |
| run: cargo test --locked --workspace --no-run | |
| - name: Install Go (for vexctl) | |
| if: needs.clippy.outputs.reuse != 'true' | |
| id: go | |
| # The `vex` subcommand emits OpenVEX documents; tests/e2e_vex.rs | |
| # validates the output with vexctl when it's on PATH. vexctl is | |
| # a Go binary distributed via `go install`. Setting up Go here | |
| # is the cheapest way to give every test job a usable vexctl. | |
| # Go must be >= 1.24: its linker only began emitting an LC_UUID load | |
| # command then, and the macOS-latest runner's dyld (Sequoia+) refuses | |
| # to load a Mach-O binary without one ("missing LC_UUID load command"), | |
| # so a 1.22-built vexctl crashes on launch and every e2e_vex assertion | |
| # fails. ubuntu/windows are unaffected, but the matrix shares this pin. | |
| # SHA pin resolved from `gh api repos/actions/setup-go/git/refs/tags/v6.4.0`. | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: '1.24' | |
| cache: false | |
| - name: Restore vexctl (macOS) | |
| # The macOS compile below took a median 110s (max 168s) of every | |
| # macOS `test` job and is the step's only network flake source | |
| # (sum.golang.org resets). Its output depends only on the vexctl | |
| # version, the Go toolchain and the runner, so it is cached under | |
| # exactly those. Saved from main only, like the cargo cache. | |
| id: vexctl-cache | |
| if: needs.clippy.outputs.reuse != 'true' && runner.os == 'macOS' | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: ${{ runner.temp }}/vexctl-bin | |
| key: vexctl-${{ env.VEXCTL_VERSION }}-go${{ steps.go.outputs.go-version }}-${{ runner.os }}-${{ runner.arch }} | |
| - name: Install vexctl | |
| if: needs.clippy.outputs.reuse != 'true' | |
| # Linux / Windows: the v0.3.0 release binary, checked against the | |
| # sha256 pinned here (from the release's vexctl_checksums.txt). | |
| # Compiling it took ~80s on ubuntu and ~180s on windows, the | |
| # slowest job in this workflow. | |
| # | |
| # macOS still compiles: the release's darwin binaries are built | |
| # with go1.22.7 and have no LC_UUID, so the runner's dyld refuses | |
| # them (see the Go step above). The compile is retried: it builds | |
| # sigstore/cosign, whose module verification reads dozens of | |
| # sum.golang.org checksum tiles, and transient INTERNAL_ERROR | |
| # stream resets there have failed this step on otherwise-green | |
| # runs. The backoff rides out short resets; a persistent outage | |
| # still fails loudly on the last attempt. A binary restored by the | |
| # step above skips the compile. | |
| # | |
| # --ssl-revoke-best-effort: Windows curl (schannel) otherwise fails | |
| # the TLS handshake with CRYPT_E_REVOCATION_OFFLINE whenever the | |
| # CA's revocation server is unreachable. A revoked certificate still | |
| # fails, and the sha256 check follows. A no-op on other OSes. | |
| # | |
| # Either way the binary's directory goes on PATH so | |
| # `Command::new("vexctl")` in the tests resolves. | |
| shell: bash | |
| env: | |
| VEXCTL_SHA256_LINUX_AMD64: cd7f8b57d20642166ed4eb3dd1fd849bbb30bbd7c5b9f5b0316b6003b57ceaba | |
| VEXCTL_SHA256_WINDOWS_AMD64: 346fb3104b656fe1a407cbae88ea29a636b36f4569ec58a5a8dadca7343993ed | |
| CACHE_HIT: ${{ steps.vexctl-cache.outputs.cache-hit }} | |
| run: | | |
| set -euo pipefail | |
| dir="$RUNNER_TEMP/vexctl-bin" | |
| mkdir -p "$dir" | |
| case "$RUNNER_OS" in | |
| Linux) asset=vexctl-linux-amd64 bin=vexctl sha="$VEXCTL_SHA256_LINUX_AMD64" ;; | |
| Windows) asset=vexctl-windows-amd64.exe bin=vexctl.exe sha="$VEXCTL_SHA256_WINDOWS_AMD64" ;; | |
| *) asset='' ;; | |
| esac | |
| if [ -n "$asset" ]; then | |
| curl -fsSL --retry 5 --retry-all-errors --ssl-revoke-best-effort -o "$dir/$bin" \ | |
| "https://github.com/openvex/vexctl/releases/download/$VEXCTL_VERSION/$asset" | |
| echo "$sha $dir/$bin" | sha256sum -c - | |
| chmod +x "$dir/$bin" | |
| "$dir/$bin" version | |
| echo "$dir" >> "$GITHUB_PATH" | |
| exit 0 | |
| fi | |
| if [ "$CACHE_HIT" != true ]; then | |
| for attempt in 1 2 3 4 5; do | |
| if GOBIN="$dir" go install "github.com/openvex/vexctl@$VEXCTL_VERSION"; then | |
| break | |
| fi | |
| if [ "$attempt" = 5 ]; then | |
| echo "::error::go install vexctl failed on all 5 attempts" | |
| exit 1 | |
| fi | |
| echo "::warning::go install vexctl attempt $attempt failed; retrying" | |
| sleep $((attempt * 20)) | |
| done | |
| fi | |
| "$dir/vexctl" version | |
| echo "$dir" >> "$GITHUB_PATH" | |
| - name: Save vexctl (macOS) | |
| if: >- | |
| needs.clippy.outputs.reuse != 'true' | |
| && runner.os == 'macOS' | |
| && github.ref == 'refs/heads/main' | |
| && steps.vexctl-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 | |
| with: | |
| path: ${{ runner.temp }}/vexctl-bin | |
| key: ${{ steps.vexctl-cache.outputs.cache-primary-key }} | |
| - name: Run tests | |
| if: needs.clippy.outputs.reuse != 'true' | |
| # Default features only: `--all-features` would also RUN the | |
| # docker-e2e suites, which soft-skip as "ok" here (no images, and | |
| # macOS/Windows have no Docker) — fake greens hiding a broken | |
| # skip-guard. Their compile rot is caught on every OS by e2e-build, | |
| # which compiles every CLI test target with --all-features; one | |
| # feature set here means one compile. | |
| # | |
| # `--no-fail-fast`: without it cargo stops at the first failing test | |
| # BINARY, so one bad file hides every later binary's result on that | |
| # OS. Run them all and fail at the end instead. | |
| shell: bash | |
| env: | |
| # The real-go hosted/vendored suites (`#![cfg(unix)]`) ride the Go | |
| # installed above for vexctl: fail instead of skip without `go` / | |
| # `zip`, and assert the pinned release. | |
| SOCKET_PATCH_GO_E2E_REQUIRED: '1' | |
| SOCKET_PATCH_GO_E2E_VERSION: '1.24' | |
| TEST_SHARD: ${{ matrix.shard }} | |
| # This leg's share of `cargo test --workspace --no-fail-fast`; the | |
| # shards together run exactly that selection (test_ci_test_shard.py). | |
| run: | | |
| python3 scripts/ci-test-shard.py "$TEST_SHARD" 2 | |
| test-release: | |
| # Each PR and main push runs the complete release-mode suite. The merge | |
| # queue already skips this job because each constituent PR ran it. | |
| if: github.event.pull_request.draft != true && github.event_name != 'merge_group' | |
| needs: clippy | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3] | |
| # Warm-cache runs still spend 20-23 minutes compiling ~240 optimized | |
| # test binaries, followed by 5-6 minutes executing them. Split both | |
| # compilation and execution with the same partitioner as `test`: | |
| # shard 1 owns the unit tests/doctests and fewer integration targets. | |
| # `ci-ok` waits for every shard and fails if any shard fails. | |
| timeout-minutes: 40 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| # rustup is pre-installed on GitHub-hosted runners. `toolchain | |
| # install` with no name reads rust-toolchain.toml in the repo root, | |
| # then installs the pinned channel + listed components if missing | |
| # (retried past download blips; see the script). No third-party | |
| # action dependency needed for toolchain setup. | |
| run: scripts/rustup-retry.sh toolchain install | |
| - name: Cache cargo | |
| # Swatinem/rust-cache, main-only saves: see the first `Cache cargo` | |
| # step in this file. | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run tests (release) | |
| # `ci-release` = [profile.release] minus the full-LTO link (see the | |
| # profile's comment in Cargo.toml). Same opt-level/debug-assertion | |
| # semantics this job exists to validate; ~23m of LTO relinking gone. | |
| # Default features for the same reason as the `test` job; the | |
| # feature-gated suites' compile rot is e2e-build's. | |
| env: | |
| TEST_SHARD: ${{ matrix.shard }} | |
| TEST_SHARD_COUNT: ${{ strategy.job-total }} | |
| run: python3 scripts/ci-test-shard.py "$TEST_SHARD" "$TEST_SHARD_COUNT" --locked --profile ci-release | |
| coverage: | |
| if: github.event.pull_request.draft != true | |
| needs: clippy | |
| # Code coverage via cargo-llvm-cov (LLVM source-based instrumentation). | |
| # Reports as a markdown table in the job summary and uploads the raw | |
| # lcov.info file as a workflow artifact. No threshold gating — the | |
| # numbers are report-only so contributors get visibility without flaky | |
| # CI when coverage shifts naturally with test edits. A failing TEST | |
| # fails the job: this is the Linux leg of `test`. | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 35 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| # `toolchain install` installs the rust-toolchain.toml channel + | |
| # listed components; `component add` adds the llvm-tools-preview | |
| # bits cargo-llvm-cov needs to merge .profraw files into lcov. | |
| run: | | |
| scripts/rustup-retry.sh toolchain install | |
| scripts/rustup-retry.sh component add llvm-tools-preview | |
| - name: Install cargo-llvm-cov | |
| # taiki-e/install-action ships precompiled binaries — much faster | |
| # than `cargo install` and avoids a per-CI-run compile. | |
| uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2.79.3 | |
| with: | |
| tool: cargo-llvm-cov@0.8.7 | |
| - name: Cache cargo | |
| # Swatinem/rust-cache, main-only saves: see the first `Cache cargo` | |
| # step in this file. | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Warm the coverage cache after merge-queue validation | |
| if: needs.clippy.outputs.reuse == 'true' | |
| run: | | |
| cargo llvm-cov show-env --sh > "$RUNNER_TEMP/coverage-env.sh" | |
| source "$RUNNER_TEMP/coverage-env.sh" | |
| cargo test --locked --workspace --no-run | |
| - name: Install Go (for vexctl and the real-go suites) | |
| if: needs.clippy.outputs.reuse != 'true' | |
| # This job is the Linux leg of `test` (see there): same Go pin. | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: '1.24' | |
| cache: false | |
| - name: Install vexctl | |
| if: needs.clippy.outputs.reuse != 'true' | |
| # The v0.3.0 Linux release binary, as in `test`'s vexctl step. | |
| env: | |
| VEXCTL_VERSION: v0.3.0 | |
| VEXCTL_SHA256_LINUX_AMD64: cd7f8b57d20642166ed4eb3dd1fd849bbb30bbd7c5b9f5b0316b6003b57ceaba | |
| run: | | |
| set -euo pipefail | |
| dir="$RUNNER_TEMP/vexctl-bin" | |
| mkdir -p "$dir" | |
| curl -fsSL --retry 5 --retry-all-errors -o "$dir/vexctl" \ | |
| "https://github.com/openvex/vexctl/releases/download/$VEXCTL_VERSION/vexctl-linux-amd64" | |
| echo "$VEXCTL_SHA256_LINUX_AMD64 $dir/vexctl" | sha256sum -c - | |
| chmod +x "$dir/vexctl" | |
| "$dir/vexctl" version | |
| echo "$dir" >> "$GITHUB_PATH" | |
| - name: Run tests with coverage | |
| if: needs.clippy.outputs.reuse != 'true' | |
| # Two-step pattern: `--no-report` runs instrumented tests and | |
| # collects raw profiles, then one `report` exports LCOV. Derive | |
| # line/function/branch totals from that file instead of merging | |
| # profiles and scanning objects a second time. The filename matches the `*.lcov` | |
| # gitignore pattern so a stray local run can't accidentally | |
| # commit a 600 KB report. | |
| # | |
| # Default features (instead of --all-features) exclude the | |
| # docker-e2e feature — those tests need Docker images this job | |
| # doesn't build. The coverage-docker matrix covers them | |
| # separately, and coverage-merge stitches everything together. | |
| # | |
| # This is also the gating Linux test run (`test` has no ubuntu | |
| # leg), hence `test`'s Go env and `--no-fail-fast`. | |
| env: | |
| SOCKET_PATCH_GO_E2E_REQUIRED: '1' | |
| SOCKET_PATCH_GO_E2E_VERSION: '1.24' | |
| run: | | |
| cargo llvm-cov --workspace --no-fail-fast \ | |
| --no-report | |
| cargo llvm-cov report --lcov --output-path coverage-host.lcov | |
| python3 scripts/ci-lcov-summary.py coverage-host.lcov > coverage-summary.txt | |
| - name: Publish coverage summary to job summary | |
| if: needs.clippy.outputs.reuse != 'true' | |
| # Render the per-file LCOV totals as a fenced | |
| # block in the GitHub Actions job summary so reviewers don't | |
| # need to crack open the artifact for a quick look. | |
| run: | | |
| { | |
| echo "## Host coverage summary" | |
| echo "" | |
| echo "(In-process tests only. See coverage-merge for the" | |
| echo "full picture including docker-e2e binary coverage.)" | |
| echo "" | |
| echo '```' | |
| cat coverage-summary.txt | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload host LCOV artifact | |
| if: needs.clippy.outputs.reuse != 'true' | |
| uses: ./.github/actions/upload-artifact | |
| with: | |
| name: coverage-host | |
| path: coverage-host.lcov | |
| if-no-files-found: error | |
| retention-days: 30 | |
| # Dockerfile.base compiles the full-LTO release binary inside Docker, with | |
| # no cache. Build it once per run and hand the image to every docker leg. | |
| docker-base: | |
| if: github.event.pull_request.draft != true && needs.clippy.outputs.reuse != 'true' | |
| needs: clippy | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Docker Buildx | |
| # `driver: docker`: see coverage-docker's matching step. | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 | |
| with: | |
| driver: docker | |
| - name: Build base image | |
| uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 | |
| with: | |
| context: . | |
| file: tests/docker/Dockerfile.base | |
| tags: socket-patch-test-base:latest | |
| load: true | |
| - name: Save base image | |
| run: | | |
| mkdir -p docker-base | |
| docker save --output docker-base/socket-patch-test-base.tar socket-patch-test-base:latest | |
| - uses: ./.github/actions/upload-artifact | |
| with: | |
| name: docker-base-image | |
| path: docker-base/socket-patch-test-base.tar | |
| if-no-files-found: error | |
| retention-days: 3 | |
| coverage-docker: | |
| # Per-ecosystem coverage for the Docker-driven e2e suite. Mirrors | |
| # the e2e-docker matrix but builds an instrumented socket-patch | |
| # binary and mounts it into the container along with a host- | |
| # visible profraw directory, so the in-container code paths | |
| # contribute to the lcov merge. | |
| # | |
| # Hooks: docker_e2e_<eco>.rs reads SOCKET_PATCH_COV_BIN + | |
| # SOCKET_PATCH_COV_PROFRAW_DIR. Both unset is the no-op default | |
| # (used by the e2e-docker matrix below). Every per-push docker_e2e | |
| # suite runs here; e2e-docker is the nightly run of the same suites | |
| # against the base image's full-LTO release binary. | |
| # | |
| # Pin to ubuntu-22.04 (glibc 2.35) instead of ubuntu-latest | |
| # (currently 24.04, glibc 2.39). The instrumented binary built | |
| # here gets mounted into the debian:12-slim test container | |
| # (glibc 2.36); a binary linked against a newer glibc than the | |
| # container ships fails to load. ubuntu-22.04's older glibc is | |
| # the highest base that's forward-compatible with debian:12. | |
| needs: docker-base | |
| runs-on: ubuntu-22.04 | |
| # sbt's image bakes three JDKs and warm sbt / Mill / scala-cli caches. | |
| timeout-minutes: ${{ matrix.ecosystem == 'sbt' && 45 || 30 }} | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ecosystem: [npm, pypi, gem, cargo, golang, maven, composer, nuget, deno, sbt] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Docker Buildx | |
| # `driver: docker` makes buildx use the host docker daemon directly | |
| # rather than running BuildKit in its own container. This is what | |
| # lets the per-ecosystem image build see the locally-tagged | |
| # `socket-patch-test-base:latest` from the previous step (with the | |
| # default container driver, BuildKit runs in a sandbox that cannot | |
| # see the host daemon's image store and tries to pull base from | |
| # docker.io, which fails). The trade-off is that `type=gha` cache | |
| # exports aren't supported under the docker driver — we accept | |
| # rebuilding the images per job for correctness. | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 | |
| with: | |
| driver: docker | |
| - name: Install Rust | |
| # `toolchain install` consumes rust-toolchain.toml; the explicit | |
| # `component add` covers llvm-tools-preview for cargo-llvm-cov. | |
| run: | | |
| scripts/rustup-retry.sh toolchain install | |
| scripts/rustup-retry.sh component add llvm-tools-preview | |
| - name: Install cargo-llvm-cov | |
| uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2.79.3 | |
| with: | |
| tool: cargo-llvm-cov@0.8.7 | |
| # No `actions/cache` here intentionally. This job builds Docker | |
| # images and would be flagged by zizmor's cache-poisoning audit | |
| # (a PR-poisoned cargo cache could compromise the instrumented | |
| # binary we mount into the container). | |
| - name: Download base image | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| pattern: docker-base-image* | |
| merge-multiple: true | |
| path: docker-base | |
| - name: Load base image | |
| run: docker load --input docker-base/socket-patch-test-base.tar | |
| - name: Build ${{ matrix.ecosystem }} image | |
| uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 | |
| with: | |
| context: . | |
| file: tests/docker/Dockerfile.${{ matrix.ecosystem }} | |
| tags: socket-patch-test-${{ matrix.ecosystem }}:latest | |
| load: true | |
| # Match the blocking agent_sbt_ slice below. The nightly and | |
| # compatibility workflows retain Dockerfile.sbt's full defaults. | |
| build-args: | | |
| ${{ matrix.ecosystem == 'sbt' && 'SBT_WARM_VERSIONS=1.2.8 1.13.0' || '' }} | |
| ${{ matrix.ecosystem == 'sbt' && 'SBT_WARM_TOOLS=0' || '' }} | |
| - name: Configure docker-e2e coverage hooks | |
| # Mount the instrumented socket-patch that the test step's own | |
| # `cargo llvm-cov` builds for the integration tests (cargo builds | |
| # a package's bins before running its integration tests), and | |
| # write the in-container profraws next to the test processes' | |
| # ones. `cargo llvm-cov report` reads only | |
| # target/llvm-cov-target/*.profraw and objects built there, so a | |
| # separately built binary or profraws elsewhere in target/ never | |
| # reach the lcov. | |
| run: | | |
| echo "SOCKET_PATCH_COV_BIN=$PWD/target/llvm-cov-target/debug/socket-patch" >> "$GITHUB_ENV" | |
| echo "SOCKET_PATCH_COV_PROFRAW_DIR=$PWD/target/llvm-cov-target" >> "$GITHUB_ENV" | |
| - name: Run ${{ matrix.ecosystem }} Docker e2e test with coverage | |
| run: | | |
| # Vendor build-proof capstones ride the same image as their | |
| # ecosystem's main suite (extend the case as new vendor suites land). | |
| EXTRA="" | |
| # libtest arguments after `--`. | |
| FILTER="" | |
| case "${{ matrix.ecosystem }}" in | |
| composer) EXTRA="--test docker_e2e_vendor_composer" ;; | |
| gem) EXTRA="--test docker_e2e_vendor_gem" ;; | |
| maven) EXTRA="--test docker_e2e_vendor_maven" ;; | |
| nuget) EXTRA="--test docker_e2e_vendor_nuget" ;; | |
| pypi) EXTRA="--test docker_e2e_vendor_pypi_pm" ;; | |
| # The blocking sbt slice: the agent cells on 1.2.8 (Ivy) and | |
| # 1.13.0 (Coursier, plus the `useCoursier := false` Ivy cell). | |
| # Mill, scala-cli and the other sbt lines run nightly in | |
| # e2e-docker and in sbt-compatibility.yml. | |
| sbt) | |
| export SOCKET_PATCH_SBT_DOCKER_VERSIONS="1.2.8 1.13.0" | |
| export SOCKET_PATCH_DOCKER_E2E_REQUIRED=1 | |
| FILTER="agent_sbt_ --test-threads=1" ;; | |
| esac | |
| # shellcheck disable=SC2086 # EXTRA and FILTER are intentionally word-split | |
| cargo llvm-cov \ | |
| --features docker-e2e \ | |
| --no-report \ | |
| --test docker_e2e_${{ matrix.ecosystem }} $EXTRA \ | |
| -- $FILTER | |
| - name: Generate per-ecosystem lcov | |
| run: | | |
| cargo llvm-cov report \ | |
| --lcov \ | |
| --output-path coverage-docker-${{ matrix.ecosystem }}.lcov | |
| - name: Upload per-ecosystem LCOV artifact | |
| uses: ./.github/actions/upload-artifact | |
| with: | |
| name: coverage-docker-${{ matrix.ecosystem }} | |
| path: coverage-docker-${{ matrix.ecosystem }}.lcov | |
| if-no-files-found: error | |
| retention-days: 30 | |
| coverage-merge: | |
| # Merge the host coverage and per-ecosystem docker coverage into a | |
| # single lcov.info. lcov(1) handles the union — same files are | |
| # summed line-by-line so a line covered by ANY test counts. | |
| needs: [coverage, coverage-docker] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install lcov | |
| run: sudo apt-get update && sudo apt-get install -y lcov | |
| - name: Download all coverage artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| path: coverage-artifacts | |
| pattern: coverage-{host*,docker-*} | |
| # Retries have distinct artifact names. Their LCOV filenames stay | |
| # stable, so a lost finalization response cannot double the counts. | |
| merge-multiple: true | |
| - name: Merge LCOV files | |
| # `--add-tracefile` is repeated per input. lcov sums hit counts | |
| # for identical source/line keys, so files covered by both host | |
| # and docker tests report the higher (union) count. | |
| # `find` (not bash globstar) for portability across runners. | |
| run: | | |
| set -e | |
| ARGS=() | |
| while IFS= read -r f; do | |
| ARGS+=(--add-tracefile "$f") | |
| done < <(find coverage-artifacts -name '*.lcov' -type f) | |
| if [ ${#ARGS[@]} -eq 0 ]; then | |
| echo "No lcov files found to merge" >&2 | |
| exit 1 | |
| fi | |
| lcov "${ARGS[@]}" --output-file coverage.lcov | |
| - name: Render summary | |
| # `lcov --summary` prints a per-file rollup we tee into the job | |
| # summary, same shape as cargo-llvm-cov's own. | |
| run: | | |
| { | |
| echo "## Coverage (host + docker-e2e merged)" | |
| echo "" | |
| echo '```' | |
| lcov --summary coverage.lcov 2>&1 | tail -20 | |
| echo '```' | |
| echo "" | |
| echo "Full merged LCOV uploaded as the \`coverage-lcov\` artifact." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload merged LCOV artifact | |
| uses: ./.github/actions/upload-artifact | |
| with: | |
| name: coverage-lcov | |
| path: coverage.lcov | |
| if-no-files-found: error | |
| retention-days: 30 | |
| dispatch-tests: | |
| if: github.event.pull_request.draft != true | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '20.20.2' | |
| - name: Run npm dispatch tests | |
| run: node --test npm/socket-patch/bin/socket-patch.test.mjs | |
| - name: Run npm schema tests | |
| # The `./schema` export's zod tests. `npm install --no-save`, not | |
| # `npm ci`, for the reason publish-npm.yml gives (a version-synced | |
| # lock can name platform packages not yet on the registry); the | |
| # platform binaries are optional and not needed here. The pack | |
| # test runs again once `npm test` has built dist/, so it also | |
| # checks that the compiled schema ships. | |
| working-directory: npm/socket-patch | |
| run: | | |
| npm install --no-save --ignore-scripts --no-audit --no-fund --omit=optional | |
| npm test | |
| node --test --test-name-pattern="npm package contents" bin/socket-patch.test.mjs | |
| # Independent OS producers compile the CLI and every CLI test target | |
| # once per OS (--all-features, so this also checks feature-gated suites) | |
| # and upload the binaries the e2e, e2e-full and cargo-vex legs run. | |
| # The legs run the | |
| # test binaries directly from the same checkout path, so `CARGO_BIN_EXE_*` | |
| # and `CARGO_MANIFEST_DIR` resolve as they did under `cargo test`. | |
| e2e-build: | |
| outputs: | |
| reuse: ${{ needs.clippy.outputs.reuse }} | |
| if: github.event.pull_request.draft != true | |
| needs: clippy | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 45 | |
| env: &e2e-build-env | |
| CARGO_PROFILE_DEV_DEBUG: '0' | |
| CARGO_INCREMENTAL: '0' | |
| steps: &e2e-build-steps | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| shell: bash | |
| run: scripts/rustup-retry.sh toolchain install | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| shared-key: e2e-${{ matrix.os }} | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Compile the CLI and every CLI test target | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cargo test --locked -p socket-patch-cli --all-features --tests --no-run --message-format=json-render-diagnostics > target-build.json | |
| - name: Bundle the binaries the legs run | |
| if: needs.clippy.outputs.reuse != 'true' || matrix.os == 'ubuntu-latest' | |
| shell: bash | |
| env: | |
| BUNDLE_OS: ${{ matrix.os }} | |
| run: python3 scripts/ci-e2e-bundle.py --os "$BUNDLE_OS" --cargo-json target-build.json --dest target/e2e-bin | |
| - name: Compress the e2e binaries | |
| if: needs.clippy.outputs.reuse != 'true' || matrix.os == 'ubuntu-latest' | |
| # Compress the bundle as one stream so identical Rust code across | |
| # test binaries shares a dictionary. Zstd is on every runner image. | |
| # The 128 MiB window cuts the Linux artifact from ~200 MB to ~94 MB; | |
| # ZIP compresses each binary separately and repeats the common code. | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| tar -C target/e2e-bin -cf - . | zstd -q -f -10 --long=27 -o target/e2e-bin.tar.zst | |
| - uses: ./.github/actions/upload-artifact | |
| if: needs.clippy.outputs.reuse != 'true' || matrix.os == 'ubuntu-latest' | |
| with: | |
| name: e2e-bin-${{ matrix.os }} | |
| path: target/e2e-bin.tar.zst | |
| compression-level: 0 | |
| if-no-files-found: error | |
| retention-days: 3 | |
| e2e-build-windows: | |
| outputs: | |
| reuse: ${{ needs.clippy.outputs.reuse }} | |
| if: github.event.pull_request.draft != true | |
| needs: clippy | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [windows-latest] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 60 | |
| env: *e2e-build-env | |
| steps: *e2e-build-steps | |
| e2e-build-macos: | |
| outputs: | |
| reuse: ${{ needs.clippy.outputs.reuse }} | |
| if: github.event_name != 'pull_request' | |
| needs: clippy | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [macos-latest] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 45 | |
| env: *e2e-build-env | |
| steps: *e2e-build-steps | |
| # Short cases share setup and artifact downloads. `cases` preserves each | |
| # original suite/filter/toolchain row; ci-e2e-run.py isolates its environment | |
| # and checks every result. The Gradle agent leg overlaps its three suites | |
| # with isolated Gradle homes. Repack with scripts/ci-e2e-groups.py --write. | |
| e2e: | |
| name: e2e (${{ matrix.os }}, ${{ matrix.ci_group }}) | |
| if: needs.e2e-build.outputs.reuse != 'true' | |
| # These jobs consume e2e-build's binaries and can run alongside unit tests. | |
| needs: [e2e-build] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {os: 'ubuntu-latest', suite: 'e2e_vendor_composer_build e2e_redirect_composer_build', composer: '2', ci_group: 'e2e_vendor_composer_build e2e_redirect_composer_build-2-1'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vendor_composer_build e2e_redirect_composer_build', composer: '2.2', ci_group: 'e2e_vendor_composer_build e2e_redirect_composer_build-2.2-2'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vendor_composer_build e2e_redirect_composer_build', composer: '1', ci_group: 'e2e_vendor_composer_build e2e_redirect_composer_build-1-3'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gem_build e2e_vendor_gem_build', ruby: '3.1', bundler: '1.17.3', ci_group: 'e2e_redirect_gem_build e2e_vendor_gem_build-3.1-1.17.3-4'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gem_build e2e_vendor_gem_build', ruby: '3.1', bundler: '2.1.4', ci_group: 'e2e_redirect_gem_build e2e_vendor_gem_build-3.1-2.1.4-5'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gem_build e2e_vendor_gem_build', ruby: '3.1', bundler: '2.2.33', ci_group: 'e2e_redirect_gem_build e2e_vendor_gem_build-3.1-2.2.33-6'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gem_build e2e_vendor_gem_build', ruby: '3.3', bundler: '2.5.23', ci_group: 'e2e_redirect_gem_build e2e_vendor_gem_build-3.3-2.5.23-7'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gem_build e2e_vendor_gem_build', ruby: '3.3', bundler: '2.6.9', ci_group: 'e2e_redirect_gem_build e2e_vendor_gem_build-3.3-2.6.9-8'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gem_build e2e_vendor_gem_build', ruby: '3.4', bundler: '4.0.15', ci_group: 'e2e_redirect_gem_build e2e_vendor_gem_build-3.4-4.0.15-9'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gem_build e2e_vendor_gem_build', ruby: '3.4', bundler: '4.0.21', ci_group: 'e2e_redirect_gem_build e2e_vendor_gem_build-3.4-4.0.21-10'} | |
| - {os: 'ubuntu-latest', bun: '1.4.2', suite: 'e2e_safety_pnpm e2e_redirect_npm_build e2e_redirect_rush_sim e2e_redirect_bun_build e2e_vendor_bun_build mode_migration_bun', cases: '[{"os":"ubuntu-latest","suite":"e2e_safety_pnpm"},{"os":"ubuntu-latest","suite":"e2e_redirect_npm_build","npm_required":"1"},{"os":"ubuntu-latest","suite":"e2e_redirect_rush_sim"},{"os":"ubuntu-latest","suite":"e2e_redirect_bun_build e2e_vendor_bun_build mode_migration_bun","bun":"1.4.2","test_filter":"--include-ignored"}]', ci_group: 'node20-1.4.2-11'} | |
| - {os: 'ubuntu-latest', bun: '1.1.45', suite: 'e2e_redirect_bun_build e2e_vendor_bun_build mode_migration_bun e2e_bun_lockb', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_bun_build e2e_vendor_bun_build mode_migration_bun","bun":"1.1.45","test_filter":"--include-ignored"},{"os":"ubuntu-latest","suite":"e2e_bun_lockb","bun":"1.1.45","test_filter":"--include-ignored"}]', ci_group: 'node20-1.1.45-12'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_bun_build e2e_vendor_bun_build mode_migration_bun', bun: '1.2.23', test_filter: '--include-ignored', ci_group: 'node20-1.2.23-13'} | |
| - {os: 'ubuntu-latest', bun: '1.3.14', suite: 'e2e_redirect_bun_build e2e_vendor_bun_build mode_migration_bun e2e_bun_lockb', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_bun_build e2e_vendor_bun_build mode_migration_bun","bun":"1.3.14","test_filter":"--include-ignored"},{"os":"ubuntu-latest","suite":"e2e_bun_lockb","bun":"1.3.14","test_filter":"--include-ignored workspace_late_dependent"}]', ci_group: 'node20-1.3.14-14'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_bun_lockb', bun: '1.0.36', test_filter: '--include-ignored', ci_group: 'node20-1.0.36-15'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_bun_lockb', bun: '1.4.2', test_filter: '--include-ignored text_migration workspace_late_dependent binary_shared_bundled_record_hosted_pin_is_managed', ci_group: 'node20-1.4.2-16'} | |
| - {os: 'ubuntu-latest', vlt: '1.2.0', suite: 'e2e_redirect_vlt_build e2e_vendor_vlt_build mode_migration_vlt e2e_safety_vlt e2e_vlt e2e_redirect_pnpm_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_vlt_build","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"e2e_vendor_vlt_build","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"mode_migration_vlt","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"e2e_safety_vlt","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"e2e_safety_vlt","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix","vlt_store_linker":"hardlink"},{"os":"ubuntu-latest","suite":"e2e_vlt","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"e2e_redirect_pnpm_build","test_filter":"--ignored --skip pnpm_pinned_matrix"}]', ci_group: 'node24-1.2.0-17'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_vlt_build', vlt: '0.0.0-16', test_filter: '--include-ignored vlt_pinned_matrix', ci_group: 'node24-0.0.0-16-18'} | |
| - {os: 'ubuntu-latest', vlt: '0.0.0-32', suite: 'e2e_redirect_vlt_build e2e_vendor_vlt_build mode_migration_vlt e2e_vlt', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_vlt_build","vlt":"0.0.0-32","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"e2e_vendor_vlt_build","vlt":"0.0.0-32","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"mode_migration_vlt","vlt":"0.0.0-32","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"e2e_vlt","vlt":"0.0.0-32","test_filter":"--include-ignored vlt_pinned_matrix"}]', ci_group: 'node24-0.0.0-32-19'} | |
| - {os: 'ubuntu-latest', vlt: '1.0.0-rc.14', vlt_upgrade: '1.2.0', suite: 'e2e_redirect_vlt_build e2e_vendor_vlt_build mode_migration_vlt', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_vlt_build","vlt":"1.0.0-rc.14","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"e2e_vendor_vlt_build","vlt":"1.0.0-rc.14","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"mode_migration_vlt","vlt":"1.0.0-rc.14","test_filter":"--include-ignored vlt_pinned_matrix","vlt_upgrade":"1.2.0"}]', ci_group: 'node24-1.0.0-rc.14-1.2.0-20'} | |
| - {os: 'ubuntu-latest', vlt: '1.0.0-rc.32', suite: 'e2e_redirect_vlt_build e2e_vendor_vlt_build e2e_vlt', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_vlt_build","vlt":"1.0.0-rc.32","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"e2e_vendor_vlt_build","vlt":"1.0.0-rc.32","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"e2e_vlt","vlt":"1.0.0-rc.32","test_filter":"--include-ignored vlt_pinned_matrix"}]', ci_group: 'node24-1.0.0-rc.32-21'} | |
| - {os: 'ubuntu-latest', vlt: '1.0.4', suite: 'e2e_redirect_vlt_build e2e_vendor_vlt_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_vlt_build","vlt":"1.0.4","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"ubuntu-latest","suite":"e2e_vendor_vlt_build","vlt":"1.0.4","test_filter":"--include-ignored vlt_pinned_matrix"}]', ci_group: 'node24-1.0.4-22'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_vlt_build', vlt: '1.1.1', test_filter: '--include-ignored vlt_pinned_matrix', ci_group: 'node24-1.1.1-23'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vlt', vlt: '1.0.0-rc.12', test_filter: '--include-ignored vlt_pinned_matrix', ci_group: 'node24-1.0.0-rc.12-24'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vlt', vlt: '1.0.7', test_filter: '--include-ignored vlt_pinned_matrix', ci_group: 'node24-1.0.7-25'} | |
| - {os: 'ubuntu-latest', uv: '0.1.45', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.1.45"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.1.45","test_filter":"--include-ignored"}]', ci_group: 'uv-0.1.45-26'} | |
| - {os: 'ubuntu-latest', uv: '0.5.4', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.5.4"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.5.4","test_filter":"--include-ignored"}]', ci_group: 'uv-0.5.4-27'} | |
| - {os: 'ubuntu-latest', uv: '0.5.5', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.5.5"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.5.5","test_filter":"--include-ignored"}]', ci_group: 'uv-0.5.5-28'} | |
| - {os: 'ubuntu-latest', uv: '0.12.17', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.12.17"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.12.17","test_filter":"--include-ignored"}]', ci_group: 'uv-0.12.17-29'} | |
| - {os: 'ubuntu-latest', poetry: '1.0.10', pdm: '1.4.5', hatch: '1.0.0', suite: 'e2e_vex_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"poetry:: --ignored","poetry":"1.0.10"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"pdm:: --ignored","pdm":"1.4.5"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"hatch:: --ignored","hatch":"1.0.0"}]', ci_group: 'python-1.0.10-1.4.5-1.0.0-30'} | |
| - {os: 'ubuntu-latest', pipenv: '2022.12.19', pip: '22 23 24 25 26', suite: 'e2e_vex_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"pipenv:: --ignored","pipenv":"2022.12.19"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"pip:: --ignored","pip":"22 23 24 25 26"}]', ci_group: 'python-installers-2022.12.19-22 23 24 25 26-31'} | |
| - {os: 'ubuntu-latest', poetry: '1.1.15', pdm: '1.15.5', hatch: '1.2.1', suite: 'e2e_vex_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"poetry:: --ignored","poetry":"1.1.15"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"pdm:: --ignored","pdm":"1.15.5"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"hatch:: --ignored","hatch":"1.2.1"}]', ci_group: 'python-1.1.15-1.15.5-1.2.1-32'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vex_build', test_filter: 'pipenv:: --ignored', pipenv: '2026.8.0', ci_group: 'python-installers-2026.8.0-33'} | |
| - {os: 'ubuntu-latest', poetry: '1.8.5', pdm: '2.7.4', hatch: '1.9.7', suite: 'e2e_vex_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"poetry:: --ignored","poetry":"1.8.5"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"pdm:: --ignored","pdm":"2.7.4"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"hatch:: --ignored","hatch":"1.9.7"}]', ci_group: 'python-1.8.5-2.7.4-1.9.7-34'} | |
| - {os: 'ubuntu-latest', poetry: '2.0.1', pdm: '2.8.2', hatch: '1.14.2', suite: 'e2e_vex_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"poetry:: --ignored","poetry":"2.0.1"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"pdm:: --ignored","pdm":"2.8.2"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"hatch:: --ignored","hatch":"1.14.2"}]', ci_group: 'python-2.0.1-2.8.2-1.14.2-35'} | |
| - {os: 'ubuntu-latest', poetry: '2.4.3', pdm: '2.25.9', hatch: '1.18.1', suite: 'e2e_vex_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"poetry:: --ignored","poetry":"2.4.3"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"pdm:: --ignored","pdm":"2.25.9"},{"os":"ubuntu-latest","suite":"e2e_vex_build","test_filter":"hatch:: --ignored","hatch":"1.18.1"}]', ci_group: 'python-2.4.3-2.25.9-1.18.1-36'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vex_build', test_filter: 'pdm:: --ignored', pdm: '2.29.2', ci_group: 'python-2.29.2-37'} | |
| - {os: 'ubuntu-latest', jvm_tool: 'maven', maven: '3.6.3', dotnet: '6', suite: 'e2e_redirect_maven_build e2e_vendor_maven_build e2e_vendor_jvm_build e2e_nuget_dotnet_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_maven_build e2e_vendor_maven_build","jvm_tool":"maven","maven":"3.6.3"},{"os":"ubuntu-latest","suite":"e2e_vendor_jvm_build","jvm_tool":"maven","maven":"3.6.3","test_filter":"--ignored maven_reactor"},{"os":"ubuntu-latest","suite":"e2e_nuget_dotnet_build","dotnet":"6"}]', ci_group: 'jvm-dotnet-3.6.3-6-38'} | |
| - {os: 'ubuntu-latest', jvm_tool: 'maven', maven: '3.8.9', dotnet: '10', suite: 'e2e_redirect_maven_build e2e_vendor_maven_build e2e_vendor_jvm_build e2e_nuget_dotnet_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_maven_build e2e_vendor_maven_build","jvm_tool":"maven","maven":"3.8.9"},{"os":"ubuntu-latest","suite":"e2e_vendor_jvm_build","jvm_tool":"maven","maven":"3.8.9","test_filter":"--ignored maven_reactor"},{"os":"ubuntu-latest","suite":"e2e_nuget_dotnet_build","dotnet":"10"}]', ci_group: 'jvm-dotnet-3.8.9-10-39'} | |
| - {os: 'ubuntu-latest', jvm_tool: 'maven', maven: '3.9.16', suite: 'e2e_redirect_maven_build e2e_vendor_maven_build e2e_vendor_jvm_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_maven_build e2e_vendor_maven_build","jvm_tool":"maven","maven":"3.9.16"},{"os":"ubuntu-latest","suite":"e2e_vendor_jvm_build","jvm_tool":"maven","maven":"3.9.16","test_filter":"--ignored maven_reactor"}]', ci_group: 'jvm-dotnet-3.9.16-40'} | |
| - {os: 'ubuntu-latest', jvm_tool: 'maven', maven: '4.0.0-rc-6', suite: 'e2e_redirect_maven_build e2e_vendor_maven_build e2e_vendor_jvm_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_maven_build e2e_vendor_maven_build","jvm_tool":"maven","maven":"4.0.0-rc-6"},{"os":"ubuntu-latest","suite":"e2e_vendor_jvm_build","jvm_tool":"maven","maven":"4.0.0-rc-6","test_filter":"--ignored maven_reactor"}]', ci_group: 'jvm-dotnet-4.0.0-rc-6-41'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_maven_build', jvm_tool: 'maven', maven: '3.9.3', ci_group: 'jvm-dotnet-3.9.3-42'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_maven_build', jvm_tool: 'maven', maven: '3.9.4', ci_group: 'jvm-dotnet-3.9.4-43'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vendor_jvm_build', jvm_tool: 'maven', maven: '3.9.2', test_filter: '--ignored maven_reactor', ci_group: 'jvm-dotnet-3.9.2-44'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_gradle_discovery_build e2e_gradle_agent_build e2e_redirect_gradle_build', parallel_suites: 'true', jvm_tool: 'gradle', gradle: '6.9.4', java: '11', test_filter: '--ignored gradle_agent_ gradle_hosted_3 gradle_hosted_4 gradle_hosted_5', ci_group: '6.9.4-6.9.4-45'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '6.9.4', java: '11', test_filter: '--ignored gradle_hosted_b gradle_hosted_c gradle_hosted_d gradle_hosted_e gradle_hosted_f gradle_hosted_l gradle_hosted_m gradle_hosted_n gradle_hosted_o gradle_hosted_p --skip gradle_hosted_config_cache_second_row --skip gradle_hosted_fallback_snippet_compiles_kotlin', ci_group: '6.9.4-6.9.4-46'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '6.9.4', java: '11', test_filter: '--ignored gradle_hosted_ --skip gradle_hosted_3 --skip gradle_hosted_4 --skip gradle_hosted_5 --skip gradle_hosted_b --skip gradle_hosted_c --skip gradle_hosted_d --skip gradle_hosted_e --skip gradle_hosted_f --skip gradle_hosted_l --skip gradle_hosted_m --skip gradle_hosted_n --skip gradle_hosted_o --skip gradle_hosted_p --skip gradle_hosted_config_cache_second_row --skip gradle_hosted_fallback_snippet_compiles_kotlin --skip gradle_hosted_vendored_takeover_and_eject', ci_group: '6.9.4-6.9.4-47'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vendor_gradle_build e2e_vendor_jvm_build e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '6.9.4', java: '11', test_filter: '--ignored gradle_vendor_ gradle_multi_project gradle_hosted_config_cache_second_row gradle_hosted_fallback_snippet_compiles_kotlin gradle_hosted_vendored_takeover_and_eject', ci_group: '6.9.4-6.9.4-48'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_gradle_discovery_build e2e_gradle_agent_build e2e_redirect_gradle_build', parallel_suites: 'true', jvm_tool: 'gradle', gradle: '9.8.0', java: '21', test_filter: '--ignored gradle_agent_ gradle_hosted_3 gradle_hosted_4 gradle_hosted_5', ci_group: '9.8.0-9.8.0-49'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '9.8.0', java: '21', test_filter: '--ignored gradle_hosted_b gradle_hosted_c gradle_hosted_d gradle_hosted_e gradle_hosted_f gradle_hosted_l gradle_hosted_m gradle_hosted_n gradle_hosted_o gradle_hosted_p --skip gradle_hosted_config_cache_second_row --skip gradle_hosted_fallback_snippet_compiles_kotlin', ci_group: '9.8.0-9.8.0-50'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '9.8.0', java: '21', test_filter: '--ignored gradle_hosted_ --skip gradle_hosted_3 --skip gradle_hosted_4 --skip gradle_hosted_5 --skip gradle_hosted_b --skip gradle_hosted_c --skip gradle_hosted_d --skip gradle_hosted_e --skip gradle_hosted_f --skip gradle_hosted_l --skip gradle_hosted_m --skip gradle_hosted_n --skip gradle_hosted_o --skip gradle_hosted_p --skip gradle_hosted_config_cache_second_row --skip gradle_hosted_fallback_snippet_compiles_kotlin --skip gradle_hosted_vendored_takeover_and_eject', ci_group: '9.8.0-9.8.0-51'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vendor_gradle_build e2e_vendor_jvm_build e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '9.8.0', java: '21', test_filter: '--ignored gradle_vendor_ gradle_multi_project gradle_hosted_config_cache_second_row gradle_hosted_fallback_snippet_compiles_kotlin gradle_hosted_vendored_takeover_and_eject', ci_group: '9.8.0-9.8.0-52'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_sbt_build', jvm_tool: 'sbt', sbt: '1.13.0', test_filter: '--ignored --test-threads=1', ci_group: 'e2e_sbt_build-1.13.0-53'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_sbt_vendor_build', jvm_tool: 'sbt', sbt: '1.13.0', test_filter: '--ignored --test-threads=1', ci_group: 'e2e_sbt_vendor_build-1.13.0-54'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vex_build', test_filter: 'deno:: --ignored', deno: '1.46.3', ci_group: 'e2e_vex_build-1.46.3-55'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vex_build', test_filter: 'deno:: --ignored', deno: '2.9.7', ci_group: 'e2e_vex_build-2.9.7-56'} | |
| runs-on: ${{ matrix.os }} | |
| # The real-toolchain capstones loop several releases per leg (pip, | |
| # pipenv) or bootstrap a tool from PyPI before the suite (poetry, pdm, | |
| # hatch), hence more than the 25 minutes the older legs needed. | |
| timeout-minutes: 40 | |
| # What .cargo/config.toml's [env] gives processes cargo launches; these | |
| # legs launch the test binaries themselves. | |
| env: &e2e-env | |
| SOCKET_NO_CONFIG: '1' | |
| SOCKET_NO_UPDATE_CHECK: '1' | |
| SOCKET_TELEMETRY_DISABLED: '1' | |
| # e2e-full runs these same steps over its rows. | |
| steps: &e2e-steps | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Download the e2e binaries | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| pattern: e2e-bin-${{ matrix.os }}* | |
| merge-multiple: true | |
| path: target/e2e-archive | |
| - name: Unpack the e2e binaries | |
| shell: bash | |
| # Decompress to a file, not into `| tar -xf -`: a tar that exits at | |
| # the end-of-archive marker without draining stdin (macOS's does) | |
| # leaves zstd writing into a closed pipe, and pipefail turns its | |
| # "Broken pipe" into a failed leg that evicts the queue entry. | |
| run: | | |
| set -euo pipefail | |
| mkdir -p target/e2e-bin | |
| zstd -q -d -f target/e2e-archive/e2e-bin.tar.zst -o target/e2e-bin.tar | |
| tar -xf target/e2e-bin.tar -C target/e2e-bin | |
| rm target/e2e-bin.tar | |
| - name: Stage the CLI where the test binaries expect it | |
| # `CARGO_BIN_EXE_socket-patch` was baked in at compile time as | |
| # <checkout>/target/debug/socket-patch; CARGO_TARGET_TMPDIR likewise. | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| exe='' | |
| if [ "$RUNNER_OS" = Windows ]; then exe=.exe; fi | |
| chmod +x target/e2e-bin/* || true | |
| mkdir -p target/debug target/tmp | |
| cp "target/e2e-bin/socket-patch$exe" "target/debug/socket-patch$exe" | |
| - name: Setup Node.js | |
| if: contains(format(' {0} ', matrix.suite), ' e2e_npm ') || contains(format(' {0} ', matrix.suite), ' e2e_scan ') || contains(format(' {0} ', matrix.suite), ' e2e_safety_pnpm ') | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '20.20.2' | |
| - name: Setup pnpm | |
| if: contains(format(' {0} ', matrix.suite), ' e2e_safety_pnpm ') | |
| # Pin the major version so the store layout the test | |
| # asserts on stays stable. `npm install -g` is the simplest | |
| # cross-platform install path (works on ubuntu, macos, | |
| # windows-runners — they all ship a usable npm via | |
| # actions/setup-node). | |
| run: npm install -g pnpm@10 | |
| - name: Setup Node.js 24 (named pnpm legs) | |
| if: contains(format(' {0} ', matrix.suite), ' e2e_redirect_pnpm_build ') && matrix.vlt == '' | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '24.x' | |
| - name: Setup Python | |
| if: contains(format(' {0} ', matrix.suite), ' e2e_pypi ') || matrix.uv != '' || matrix.poetry != '' || matrix.pdm != '' || matrix.hatch != '' | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: '3.12.x' | |
| - name: Setup uv (uv legs) | |
| if: matrix.uv != '' | |
| # The exact uv release the leg pins; the suites assert it through | |
| # SOCKET_PATCH_UV_E2E_VERSION. | |
| shell: bash | |
| env: | |
| UV_TEST_VERSION: ${{ matrix.uv }} | |
| run: | | |
| scripts/pip-install-retry.sh --upgrade pip "uv==$UV_TEST_VERSION" | |
| uv --version | |
| - name: Install uv (PDM / Hatch legs) | |
| if: matrix.pdm != '' || matrix.hatch != '' | |
| # The suites bootstrap the pinned PDM / Hatch into their own venv | |
| # with uv; this uv is tooling, not the release under test. | |
| shell: bash | |
| run: scripts/pip-install-retry.sh uv==0.11.19 | |
| - name: Install pinned Poetry | |
| if: matrix.poetry != '' | |
| # Poetry 1.0/1.1 need Python 3.8 (their vendored deps predate 3.12); | |
| # 1.2.2 needs the cleo pre-release it was published against. pip 24.0 | |
| # seeds the tool venv (Poetry 1.0 + pip 22.3-23.0 misread the lock-1.0 | |
| # `#sha256=` fragment, as the CLI's advisory says). | |
| shell: bash | |
| env: | |
| POETRY_TEST_VERSION: ${{ matrix.poetry }} | |
| run: | | |
| scripts/pip-install-retry.sh uv==0.11.19 | |
| case "$POETRY_TEST_VERSION" in 1.0.*|1.1.*) py=3.8.20 ;; *) py=3.12 ;; esac | |
| uv venv "$RUNNER_TEMP/poetry" --python "$py" | |
| extra=""; [ "$POETRY_TEST_VERSION" = "1.2.2" ] && extra="cleo==1.0.0a5" | |
| uv pip install --python "$RUNNER_TEMP/poetry/bin/python" "poetry==$POETRY_TEST_VERSION" pip==24.0 setuptools==69.5.1 $extra | |
| echo "SOCKET_PATCH_POETRY_BIN=$RUNNER_TEMP/poetry/bin/poetry" >> "$GITHUB_ENV" | |
| - name: Setup uv (Pipenv / pip legs) | |
| if: matrix.pipenv != '' || matrix.pip != '' | |
| uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 | |
| - name: Install the Pythons the Pipenv / pip legs run on | |
| if: matrix.pipenv != '' || matrix.pip != '' | |
| run: uv python install 3.8 3.11 3.12 | |
| - name: Setup Ruby | |
| if: contains(format(' {0} ', matrix.suite), ' e2e_gem ') || matrix.bundler != '' | |
| uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0 | |
| with: | |
| # setup-ruby does NOT support `3.2.x` wildcard pinning the | |
| # way setup-python does — it errors with "Unknown version | |
| # 3.2.x for ruby on ubuntu-24.04". Pin to an exact patch | |
| # that's currently in the catalog. If the action drops this | |
| # patch in the future, bump to whatever's available — see | |
| # https://github.com/ruby/setup-ruby for the supported list. | |
| # Bundler-era legs pin their own Ruby (`ruby:`): bundler <= 2.2 | |
| # needs Ruby <= 3.3 and 1.17-2.1 need Ruby <= 3.1 (`untaint`). | |
| ruby-version: ${{ matrix.ruby || '3.2.10' }} | |
| # The legs pin their bundler (`bundler:`) so a capstone never rides | |
| # whatever bundler the runner's Ruby ships; bundler 1.x is not | |
| # installable through setup-ruby, see the next steps. e2e_gem keeps | |
| # the 2.5 floor. | |
| bundler: ${{ startsWith(matrix.bundler, '1.') && 'none' || matrix.bundler || '2.5' }} | |
| bundler-cache: false | |
| - name: Install Bundler 1.x | |
| if: startsWith(matrix.bundler, '1.') | |
| shell: bash | |
| env: | |
| BUNDLER_TEST_VERSION: ${{ matrix.bundler }} | |
| run: gem install bundler -v "$BUNDLER_TEST_VERSION" --no-document | |
| # Installing a bundler does not make `bundle` run it: with no lockfile | |
| # to read, RubyGems' binstub activates the HIGHEST installed bundler, | |
| # so a leg pinned BELOW its Ruby's default gem (2.1.4 / 2.2.33 on Ruby | |
| # 3.1, whose default is 2.3.27) silently runs the default instead and | |
| # tests/common/bundler_e2e.rs rightly panics. BUNDLER_VERSION makes | |
| # the binstub select exactly the pinned release in every process (and | |
| # turns off bundler >= 2.3's lockfile-driven self-switch), the same | |
| # knob tests/docker/Dockerfile.gem-b1 sets. | |
| - name: Select the pinned Bundler | |
| if: matrix.bundler != '' | |
| shell: bash | |
| env: | |
| BUNDLER_TEST_VERSION: ${{ matrix.bundler }} | |
| run: | | |
| echo "BUNDLER_VERSION=$BUNDLER_TEST_VERSION" >> "$GITHUB_ENV" | |
| BUNDLER_VERSION="$BUNDLER_TEST_VERSION" bundle --version | |
| - name: Setup PHP | |
| if: matrix.composer != '' | |
| # The composer capstones shell out to a real composer; `composer:` | |
| # pins the release line (1, 2.2 LTS, 2) so the composer.lock grammar | |
| # the edits assert stays stable across runners. | |
| uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0 | |
| with: | |
| php-version: '8.2' | |
| tools: composer:${{ matrix.composer }} | |
| - name: Select the JVM toolchain (JVM legs) | |
| id: jvm | |
| if: matrix.jvm_tool != '' | |
| # `jvm_tool` (gradle | maven | sbt) marks a JVM leg. The JDK comes | |
| # from the runner image (JAVA_HOME_<N>_X64 / _arm64: 8, 11, 17 and 21 | |
| # on every hosted OS) when it has the pinned feature release, else | |
| # from setup-java below. Maven is needed by the Maven legs and by the | |
| # Gradle legs whose filter selects gradle_vendor_* (gradle_vendor_395 | |
| # builds the mixed root's pom with it). The multi-project capstone | |
| # reads the Gradle cache and the agent / hosted Gradle legs run | |
| # without Maven, so the windows multi-project row proves both. | |
| shell: bash | |
| env: | |
| JVM_TOOL: ${{ matrix.jvm_tool }} | |
| JAVA_FEATURE: ${{ matrix.java || '17' }} | |
| TEST_FILTER: ${{ matrix.test_filter }} | |
| run: | | |
| set -euo pipefail | |
| home='' | |
| for arch in X64 arm64 ARM64; do | |
| var="JAVA_HOME_${JAVA_FEATURE}_${arch}" | |
| if [ -n "${!var:-}" ]; then home="${!var}"; break; fi | |
| done | |
| if [ -n "$home" ]; then | |
| bin="$home/bin" | |
| if [ "$RUNNER_OS" = Windows ]; then bin="$home\\bin"; fi | |
| echo "JAVA_HOME=$home" >> "$GITHUB_ENV" | |
| echo "$bin" >> "$GITHUB_PATH" | |
| echo "runner-jdk=true" >> "$GITHUB_OUTPUT" | |
| echo "JDK $JAVA_FEATURE from the runner image: $home" | |
| else | |
| echo "runner-jdk=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| maven=false | |
| case "$JVM_TOOL" in | |
| maven) maven=true ;; | |
| gradle) case " $TEST_FILTER " in *gradle_vendor_*) maven=true ;; esac ;; | |
| esac | |
| echo "maven=$maven" >> "$GITHUB_OUTPUT" | |
| - name: Setup Java (JDK not on the runner image) | |
| if: matrix.jvm_tool != '' && steps.jvm.outputs.runner-jdk != 'true' | |
| uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 | |
| with: | |
| distribution: temurin | |
| java-version: ${{ matrix.java || '17' }} | |
| - name: Setup sbt (sbt legs) | |
| if: matrix.sbt != '' | |
| # The sbt launcher script; it boots whatever `sbt.version` each | |
| # fixture's project/build.properties pins. | |
| uses: sbt/setup-sbt@6158cb0903b8ceeae04f830055f3155e1b6a5ad7 # v1.5.11 | |
| with: | |
| sbt-runner-version: 1.13.0 | |
| disk-cache: false | |
| - name: Warm the sbt seed (sbt legs) | |
| if: matrix.sbt != '' | |
| # Boots the pinned sbt once into a seed the suites hard-link into | |
| # each test's private caches (SOCKET_PATCH_SBT_E2E_SEED), and names | |
| # the launcher (SOCKET_PATCH_SBT_E2E_SBT). | |
| shell: bash | |
| env: | |
| SBT_TEST_VERSION: ${{ matrix.sbt }} | |
| run: scripts/sbt-warm-seed.sh "$SBT_TEST_VERSION" "$RUNNER_TEMP/sbt-seed" >> "$GITHUB_ENV" | |
| - name: Install Maven ${{ matrix.maven || '3.9.16' }} | |
| if: steps.jvm.outputs.maven == 'true' | |
| # The exact release a leg names rather than the runner's Maven. The | |
| # tarball comes from Maven Central's CDN (well under a second); | |
| # archive.apache.org throttles bulk downloads to 1.5-5.5 minutes per | |
| # leg. If the CDN fails, use the slower archive tarball. Both | |
| # sources must match the committed SHA512 in scripts/maven-sha512.json; | |
| # an origin cannot replace both the archive and its expected digest. | |
| # --ssl-revoke-best-effort: see the `test` job's vexctl step. | |
| shell: bash | |
| env: | |
| MAVEN_VERSION: ${{ matrix.maven || '3.9.16' }} | |
| run: | | |
| major="${MAVEN_VERSION%%.*}" | |
| file="apache-maven-${MAVEN_VERSION}-bin.tar.gz" | |
| url="https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${MAVEN_VERSION}/${file}" | |
| archive_url="https://archive.apache.org/dist/maven/maven-${major}/${MAVEN_VERSION}/binaries/${file}" | |
| if ! curl -fsSL --retry 5 --retry-all-errors --ssl-revoke-best-effort "$url" -o "$RUNNER_TEMP/maven.tgz"; then | |
| echo "::warning::Maven Central download failed; falling back to the Apache archive." | |
| curl -fsSL --retry 5 --retry-all-errors --ssl-revoke-best-effort "$archive_url" -o "$RUNNER_TEMP/maven.tgz" | |
| fi | |
| python - "$MAVEN_VERSION" "$RUNNER_TEMP/maven.tgz" <<'PY' | |
| import hashlib, json, sys | |
| from pathlib import Path | |
| # Pins come from Apache's release checksums, cross-checked against | |
| # the Maven Central tarballs. Add a pin when adding a matrix version. | |
| expected = json.loads(Path("scripts/maven-sha512.json").read_text())[sys.argv[1]] | |
| if hashlib.sha512(Path(sys.argv[2]).read_bytes()).hexdigest() != expected: | |
| raise SystemExit("Maven archive SHA512 mismatch") | |
| PY | |
| # Python accepts native Windows paths for both archive and destination. | |
| python -m tarfile -e "$RUNNER_TEMP/maven.tgz" "$RUNNER_TEMP" | |
| launcher="$RUNNER_TEMP/apache-maven-${MAVEN_VERSION}/bin/mvn" | |
| if [ "$RUNNER_OS" = Windows ]; then launcher="${launcher}.cmd"; fi | |
| echo "SOCKET_PATCH_MAVEN_E2E_MVN=$launcher" >> "$GITHUB_ENV" | |
| - name: Install Gradle ${{ matrix.gradle }} | |
| if: matrix.gradle != '' | |
| shell: bash | |
| env: | |
| GRADLE_VERSION: ${{ matrix.gradle }} | |
| # Pinned digest, GitHub-release origin: see the script. | |
| run: scripts/install-gradle.sh "$GRADLE_VERSION" "$RUNNER_TEMP" >> "$GITHUB_ENV" | |
| - name: Setup .NET SDK | |
| if: matrix.dotnet != '' | |
| # Installs next to the runner's preinstalled SDKs; the suite writes | |
| # its own global.json to select the pinned major. | |
| uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 | |
| with: | |
| dotnet-version: ${{ matrix.dotnet }}.0.x | |
| - name: Setup Deno | |
| if: matrix.deno != '' | |
| uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5 | |
| with: | |
| deno-version: ${{ matrix.deno }} | |
| - name: Setup Bun | |
| if: matrix.bun != '' | |
| # Installs the exact bun release the leg pins (setup-bun resolves a | |
| # strict semver straight to the `bun-v<ver>` GitHub release, so the | |
| # lock-era legs get the historical writer, not `latest`). Works on | |
| # all three runner OSes (windows → bun-windows-x64.zip / bun.exe). | |
| # SHA resolved from `gh api repos/oven-sh/setup-bun/git/ref/tags/v2.2.0`. | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version: ${{ matrix.bun }} | |
| - name: Setup Node.js 24 (vlt legs) | |
| if: matrix.vlt != '' | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '24.21.0' | |
| - name: Setup vlt | |
| if: matrix.vlt != '' | |
| # The pinned release (and, for the upgrade legs, the second vlt): | |
| # `npm pack`, sha512 against the registry and the committed pin, then | |
| # a prefix install run as `node vlt.js`. The store-linker knob is a | |
| # harness variable because the harness scrubs ambient VLT_*. | |
| shell: bash | |
| env: | |
| VLT_TEST_VERSION: ${{ matrix.vlt }} | |
| VLT_TEST_UPGRADE: ${{ matrix.vlt_upgrade }} | |
| VLT_TEST_STORE_LINKER: ${{ matrix.vlt_store_linker }} | |
| run: | | |
| set -euo pipefail | |
| js=$(scripts/install-vlt.sh "$VLT_TEST_VERSION" "$RUNNER_TEMP/vlt-tool") | |
| { | |
| echo "SOCKET_PATCH_VLT_E2E_JS=$js" | |
| echo "SOCKET_PATCH_VLT_E2E_VERSION=$VLT_TEST_VERSION" | |
| echo "SOCKET_PATCH_VLT_E2E_REQUIRED=1" | |
| echo "LANG=C" | |
| echo "LC_ALL=C" | |
| } >> "$GITHUB_ENV" | |
| if [ -n "$VLT_TEST_STORE_LINKER" ]; then | |
| echo "SOCKET_PATCH_VLT_E2E_STORE_LINKER=$VLT_TEST_STORE_LINKER" >> "$GITHUB_ENV" | |
| fi | |
| if [ -n "$VLT_TEST_UPGRADE" ]; then | |
| up=$(scripts/install-vlt.sh "$VLT_TEST_UPGRADE" "$RUNNER_TEMP/vlt-upgrade") | |
| echo "SOCKET_PATCH_VLT_E2E_UPGRADE_JS=$up" >> "$GITHUB_ENV" | |
| echo "SOCKET_PATCH_VLT_E2E_UPGRADE_VERSION=$VLT_TEST_UPGRADE" >> "$GITHUB_ENV" | |
| fi | |
| node --version | |
| - name: Run e2e tests | |
| # Every grouped member retains its own filters and required-tool | |
| # guards; vlt members additionally pass the leg-manifest checker. | |
| # The runner reports each failure and continues through later suites. | |
| env: | |
| E2E_ROW_JSON: ${{ toJSON(matrix) }} | |
| run: python3 scripts/ci-e2e-run.py | |
| - name: Upload the Gradle probe reports | |
| if: always() && matrix.gradle != '' | |
| uses: ./.github/actions/upload-artifact | |
| with: | |
| name: gradle-probe-pr-${{ matrix.os }}-${{ matrix.gradle }}-${{ strategy.job-index }} | |
| path: target/gradle-probe/ | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| # The PM-version legs with no boundary of their own: the middle uv lines, | |
| # Pipenv releases and .NET SDK majors, and bundler 2.7. Skipped | |
| # on pull_request (the `e2e` rows keep every named boundary, the oldest | |
| # and newest release of each tool and every vlt era there); main pushes, | |
| # v5 landings, the nightly schedule and dispatch run them with the `e2e` | |
| # steps. | |
| # Each OS consumes only its own build. A queued macOS runner must not | |
| # delay Linux or Windows, and a Windows compile must not delay Gradle. | |
| # Middle Gradle lines are required in the merge queue; every PR still | |
| # runs every suite on the oldest/newest line. Main reuses the queue verdict. | |
| e2e-gradle-mid: | |
| name: e2e-gradle-mid (${{ matrix.os }}, ${{ matrix.ci_group }}) | |
| if: github.event_name != 'pull_request' && needs.e2e-build.outputs.reuse != 'true' | |
| needs: [e2e-build] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {os: 'ubuntu-latest', suite: 'e2e_gradle_discovery_build e2e_gradle_agent_build e2e_redirect_gradle_build', parallel_suites: 'true', jvm_tool: 'gradle', gradle: '7.6.6', java: '17', test_filter: '--ignored gradle_agent_ gradle_hosted_3 gradle_hosted_4 gradle_hosted_5', ci_group: '7.6.6-7.6.6-1'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '7.6.6', java: '17', test_filter: '--ignored gradle_hosted_b gradle_hosted_c gradle_hosted_d gradle_hosted_e gradle_hosted_f gradle_hosted_l gradle_hosted_m gradle_hosted_n gradle_hosted_o gradle_hosted_p --skip gradle_hosted_config_cache_second_row --skip gradle_hosted_fallback_snippet_compiles_kotlin', ci_group: '7.6.6-7.6.6-2'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '7.6.6', java: '17', test_filter: '--ignored gradle_hosted_ --skip gradle_hosted_3 --skip gradle_hosted_4 --skip gradle_hosted_5 --skip gradle_hosted_b --skip gradle_hosted_c --skip gradle_hosted_d --skip gradle_hosted_e --skip gradle_hosted_f --skip gradle_hosted_l --skip gradle_hosted_m --skip gradle_hosted_n --skip gradle_hosted_o --skip gradle_hosted_p --skip gradle_hosted_config_cache_second_row --skip gradle_hosted_fallback_snippet_compiles_kotlin --skip gradle_hosted_vendored_takeover_and_eject', ci_group: '7.6.6-7.6.6-3'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vendor_gradle_build e2e_vendor_jvm_build e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '7.6.6', java: '17', test_filter: '--ignored gradle_vendor_ gradle_multi_project gradle_hosted_config_cache_second_row gradle_hosted_fallback_snippet_compiles_kotlin gradle_hosted_vendored_takeover_and_eject', ci_group: '7.6.6-7.6.6-4'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_gradle_discovery_build e2e_gradle_agent_build e2e_redirect_gradle_build', parallel_suites: 'true', jvm_tool: 'gradle', gradle: '8.14.3', java: '21', test_filter: '--ignored gradle_agent_ gradle_hosted_3 gradle_hosted_4 gradle_hosted_5', ci_group: '8.14.3-8.14.3-5'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '8.14.3', java: '21', test_filter: '--ignored gradle_hosted_b gradle_hosted_c gradle_hosted_d gradle_hosted_e gradle_hosted_f gradle_hosted_l gradle_hosted_m gradle_hosted_n gradle_hosted_o gradle_hosted_p --skip gradle_hosted_config_cache_second_row --skip gradle_hosted_fallback_snippet_compiles_kotlin', ci_group: '8.14.3-8.14.3-6'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '8.14.3', java: '21', test_filter: '--ignored gradle_hosted_ --skip gradle_hosted_3 --skip gradle_hosted_4 --skip gradle_hosted_5 --skip gradle_hosted_b --skip gradle_hosted_c --skip gradle_hosted_d --skip gradle_hosted_e --skip gradle_hosted_f --skip gradle_hosted_l --skip gradle_hosted_m --skip gradle_hosted_n --skip gradle_hosted_o --skip gradle_hosted_p --skip gradle_hosted_config_cache_second_row --skip gradle_hosted_fallback_snippet_compiles_kotlin --skip gradle_hosted_vendored_takeover_and_eject', ci_group: '8.14.3-8.14.3-7'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vendor_gradle_build e2e_vendor_jvm_build e2e_redirect_gradle_build', jvm_tool: 'gradle', gradle: '8.14.3', java: '21', test_filter: '--ignored gradle_vendor_ gradle_multi_project gradle_hosted_config_cache_second_row gradle_hosted_fallback_snippet_compiles_kotlin gradle_hosted_vendored_takeover_and_eject', ci_group: '8.14.3-8.14.3-8'} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 40 | |
| env: *e2e-env | |
| steps: *e2e-steps | |
| e2e-windows: | |
| name: e2e-windows (${{ matrix.os }}, ${{ matrix.ci_group }}) | |
| if: needs.e2e-build-windows.outputs.reuse != 'true' | |
| needs: [e2e-build-windows] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {os: 'windows-latest', bun: '1.4.2', suite: 'e2e_safety_pnpm e2e_redirect_npm_build e2e_redirect_rush_sim e2e_redirect_bun_build e2e_vendor_bun_build mode_migration_bun', cases: '[{"os":"windows-latest","suite":"e2e_safety_pnpm"},{"os":"windows-latest","suite":"e2e_redirect_npm_build","npm_required":"1"},{"os":"windows-latest","suite":"e2e_redirect_rush_sim"},{"os":"windows-latest","suite":"e2e_redirect_bun_build e2e_vendor_bun_build mode_migration_bun","bun":"1.4.2","test_filter":"--include-ignored"}]', ci_group: 'node20-1.4.2-1'} | |
| - {os: 'windows-latest', vlt: '1.2.0', suite: 'e2e_redirect_vlt_build e2e_vendor_vlt_build mode_migration_vlt e2e_safety_vlt e2e_vlt e2e_redirect_pnpm_build', cases: '[{"os":"windows-latest","suite":"e2e_redirect_vlt_build","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"windows-latest","suite":"e2e_vendor_vlt_build","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"windows-latest","suite":"mode_migration_vlt","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"windows-latest","suite":"e2e_safety_vlt","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix","vlt_store_linker":"hardlink"},{"os":"windows-latest","suite":"e2e_vlt","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"windows-latest","suite":"e2e_redirect_pnpm_build","test_filter":"--ignored --skip pnpm_pinned_matrix"}]', ci_group: 'node24-1.2.0-2'} | |
| - {os: 'windows-latest', vlt: '1.0.0-rc.14', suite: 'e2e_vendor_vlt_build mode_migration_vlt e2e_vlt', cases: '[{"os":"windows-latest","suite":"e2e_vendor_vlt_build","vlt":"1.0.0-rc.14","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"windows-latest","suite":"mode_migration_vlt","vlt":"1.0.0-rc.14","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"windows-latest","suite":"e2e_vlt","vlt":"1.0.0-rc.14","test_filter":"--include-ignored vlt_pinned_matrix"}]', ci_group: 'node24-1.0.0-rc.14-3'} | |
| - {os: 'windows-latest', suite: 'e2e_vendor_jvm_build', jvm_tool: 'maven', maven: '3.9.16', test_filter: '--ignored maven_reactor', ci_group: 'jvm-dotnet-3.9.16-4'} | |
| - {os: 'windows-latest', suite: 'e2e_vendor_jvm_build', jvm_tool: 'gradle', gradle: '8.14.3', java: '17', test_filter: '--ignored gradle_multi_project', ci_group: '8.14.3-8.14.3-5'} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 40 | |
| env: *e2e-env | |
| steps: *e2e-steps | |
| # The macOS rows run on main, the merge queue and nightly, not on every | |
| # PR push, so PRs stop queueing on the small macOS runner pool. | |
| e2e-macos: | |
| name: e2e-macos (${{ matrix.os }}, ${{ matrix.ci_group }}) | |
| if: github.event_name != 'pull_request' && needs.e2e-build-macos.outputs.reuse != 'true' | |
| # These jobs consume e2e-build's binaries and can run alongside unit tests. | |
| needs: [e2e-build-macos] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {os: 'macos-latest', bun: '1.4.2', suite: 'e2e_safety_pnpm e2e_redirect_npm_build e2e_redirect_rush_sim e2e_redirect_bun_build e2e_vendor_bun_build mode_migration_bun', cases: '[{"os":"macos-latest","suite":"e2e_safety_pnpm"},{"os":"macos-latest","suite":"e2e_redirect_npm_build","npm_required":"1"},{"os":"macos-latest","suite":"e2e_redirect_rush_sim"},{"os":"macos-latest","suite":"e2e_redirect_bun_build","bun":"1.4.2","test_filter":"--include-ignored"},{"os":"macos-latest","suite":"e2e_vendor_bun_build","bun":"1.4.2","test_filter":"--include-ignored"},{"os":"macos-latest","suite":"mode_migration_bun","bun":"1.4.2","test_filter":"--include-ignored"}]', ci_group: 'node20-1.4.2-1'} | |
| - {os: 'macos-latest', vlt: '1.2.0', suite: 'e2e_redirect_vlt_build e2e_vendor_vlt_build mode_migration_vlt e2e_safety_vlt e2e_vlt e2e_redirect_pnpm_build', cases: '[{"os":"macos-latest","suite":"e2e_redirect_vlt_build","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"macos-latest","suite":"e2e_vendor_vlt_build","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"macos-latest","suite":"mode_migration_vlt","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"macos-latest","suite":"e2e_safety_vlt","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix","vlt_store_linker":"hardlink"},{"os":"macos-latest","suite":"e2e_vlt","vlt":"1.2.0","test_filter":"--include-ignored vlt_pinned_matrix"},{"os":"macos-latest","suite":"e2e_redirect_pnpm_build","test_filter":"--ignored --skip pnpm_pinned_matrix"}]', ci_group: 'node24-1.2.0-2'} | |
| - {os: 'macos-latest', uv: '0.12.17', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"macos-latest","suite":"e2e_redirect_uv_build","uv":"0.12.17"},{"os":"macos-latest","suite":"e2e_vendor_pypi_build","uv":"0.12.17","test_filter":"--include-ignored"}]', ci_group: 'uv-0.12.17-3'} | |
| - {os: 'macos-latest', poetry: '2.4.3', pdm: '2.29.2', hatch: '1.18.1', suite: 'e2e_vex_build', cases: '[{"os":"macos-latest","suite":"e2e_vex_build","test_filter":"poetry:: --ignored","poetry":"2.4.3"},{"os":"macos-latest","suite":"e2e_vex_build","test_filter":"pdm:: --ignored","pdm":"2.29.2"},{"os":"macos-latest","suite":"e2e_vex_build","test_filter":"hatch:: --ignored","hatch":"1.18.1"}]', ci_group: 'python-2.4.3-2.29.2-1.18.1-4'} | |
| - {os: 'macos-latest', pipenv: '2022.12.19 2026.8.0', pip: '22 26', suite: 'e2e_vex_build', cases: '[{"os":"macos-latest","suite":"e2e_vex_build","test_filter":"pipenv:: --ignored","pipenv":"2022.12.19 2026.8.0"},{"os":"macos-latest","suite":"e2e_vex_build","test_filter":"pip:: --ignored","pip":"22 26"}]', ci_group: 'python-installers-2022.12.19 2026.8.0-22 26-5'} | |
| - {os: 'macos-latest', jvm_tool: 'maven', maven: '3.9.16', dotnet: '8', suite: 'e2e_redirect_maven_build e2e_vendor_maven_build e2e_vendor_jvm_build e2e_nuget_dotnet_build', cases: '[{"os":"macos-latest","suite":"e2e_redirect_maven_build","jvm_tool":"maven","maven":"3.9.16"},{"os":"macos-latest","suite":"e2e_vendor_maven_build","jvm_tool":"maven","maven":"3.9.16"},{"os":"macos-latest","suite":"e2e_vendor_jvm_build","jvm_tool":"maven","maven":"3.9.16","test_filter":"--ignored maven_reactor"},{"os":"macos-latest","suite":"e2e_nuget_dotnet_build","dotnet":"8"}]', ci_group: 'jvm-dotnet-3.9.16-8-6'} | |
| runs-on: ${{ matrix.os }} | |
| # The real-toolchain capstones loop several releases per leg (pip, | |
| # pipenv) or bootstrap a tool from PyPI before the suite (poetry, pdm, | |
| # hatch), hence more than the 25 minutes the older legs needed. | |
| timeout-minutes: 40 | |
| # What .cargo/config.toml's [env] gives processes cargo launches; these | |
| # legs launch the test binaries themselves. | |
| env: | |
| SOCKET_NO_CONFIG: '1' | |
| SOCKET_NO_UPDATE_CHECK: '1' | |
| SOCKET_TELEMETRY_DISABLED: '1' | |
| # e2e-full runs these same steps over its rows. | |
| steps: *e2e-steps | |
| e2e-full: | |
| name: e2e-full (${{ matrix.os }}, ${{ matrix.ci_group }}) | |
| # merge_group runs the pull_request tier: the queue gates on ci-ok. | |
| if: (github.event_name != 'pull_request' && github.event_name != 'merge_group') || github.head_ref == 'release/v5-prerelease' | |
| needs: [e2e-build] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {os: 'ubuntu-latest', suite: 'e2e_redirect_gem_build e2e_vendor_gem_build', ruby: '3.3', bundler: '2.7.2', ci_group: 'e2e_redirect_gem_build e2e_vendor_gem_build-3.3-2.7.2-1'} | |
| - {os: 'ubuntu-latest', uv: '0.2.37', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.2.37"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.2.37","test_filter":"--include-ignored"}]', ci_group: 'uv-0.2.37-2'} | |
| - {os: 'ubuntu-latest', uv: '0.3.5', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.3.5"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.3.5","test_filter":"--include-ignored"}]', ci_group: 'uv-0.3.5-3'} | |
| - {os: 'ubuntu-latest', uv: '0.4.30', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.4.30"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.4.30","test_filter":"--include-ignored"}]', ci_group: 'uv-0.4.30-4'} | |
| - {os: 'ubuntu-latest', uv: '0.5.3', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.5.3"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.5.3","test_filter":"--include-ignored"}]', ci_group: 'uv-0.5.3-5'} | |
| - {os: 'ubuntu-latest', uv: '0.5.6', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.5.6"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.5.6","test_filter":"--include-ignored"}]', ci_group: 'uv-0.5.6-6'} | |
| - {os: 'ubuntu-latest', uv: '0.6.17', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.6.17"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.6.17","test_filter":"--include-ignored"}]', ci_group: 'uv-0.6.17-7'} | |
| - {os: 'ubuntu-latest', uv: '0.7.22', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.7.22"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.7.22","test_filter":"--include-ignored"}]', ci_group: 'uv-0.7.22-8'} | |
| - {os: 'ubuntu-latest', uv: '0.8.24', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.8.24"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.8.24","test_filter":"--include-ignored"}]', ci_group: 'uv-0.8.24-9'} | |
| - {os: 'ubuntu-latest', uv: '0.9.30', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.9.30"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.9.30","test_filter":"--include-ignored"}]', ci_group: 'uv-0.9.30-10'} | |
| - {os: 'ubuntu-latest', uv: '0.10.12', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.10.12"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.10.12","test_filter":"--include-ignored"}]', ci_group: 'uv-0.10.12-11'} | |
| - {os: 'ubuntu-latest', uv: '0.11.33', suite: 'e2e_redirect_uv_build e2e_vendor_pypi_build', cases: '[{"os":"ubuntu-latest","suite":"e2e_redirect_uv_build","uv":"0.11.33"},{"os":"ubuntu-latest","suite":"e2e_vendor_pypi_build","uv":"0.11.33","test_filter":"--include-ignored"}]', ci_group: 'uv-0.11.33-12'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vex_build', test_filter: 'pipenv:: --ignored', pipenv: '2023.12.1', ci_group: 'python-installers-2023.12.1-13'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vex_build', test_filter: 'pipenv:: --ignored', pipenv: '2024.4.1', ci_group: 'python-installers-2024.4.1-14'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_vex_build', test_filter: 'pipenv:: --ignored', pipenv: '2025.1.3', ci_group: 'python-installers-2025.1.3-15'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_nuget_dotnet_build', dotnet: '7', ci_group: 'jvm-dotnet-7-16'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_nuget_dotnet_build', dotnet: '8', ci_group: 'jvm-dotnet-8-17'} | |
| - {os: 'ubuntu-latest', suite: 'e2e_nuget_dotnet_build', dotnet: '9', ci_group: 'jvm-dotnet-9-18'} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 40 | |
| # What .cargo/config.toml's [env] gives processes cargo launches; these | |
| # legs launch the test binaries themselves. | |
| env: | |
| SOCKET_NO_CONFIG: '1' | |
| SOCKET_NO_UPDATE_CHECK: '1' | |
| SOCKET_TELEMETRY_DISABLED: '1' | |
| steps: *e2e-steps | |
| # ---------------------------------------------------------------------- | |
| # Docker-driven real-package e2e suite. | |
| # | |
| # For each ecosystem, builds the shared base image (multi-stage: | |
| # Rust → debian + compiled socket-patch) and the per-ecosystem layer, | |
| # then runs the matching `docker_e2e_<eco>` test binary inside the | |
| # repo's checkout. Tests install real packages via real package | |
| # managers and run socket-patch against a wiremock-served fixture — | |
| # no real Socket API contact. Hermetic, reproducible. | |
| # | |
| # Nightly, on dispatch and on v5 landings only: coverage-docker runs these | |
| # suites on each push (with an instrumented binary mounted in); this job | |
| # is the one run of them against the base image's full-LTO release | |
| # binary. The `e2e` job above runs the `#[ignore]`-gated real-toolchain | |
| # capstones; the live-API smoke suites are run by hand (see the note in | |
| # its matrix). | |
| # ---------------------------------------------------------------------- | |
| e2e-docker: | |
| # The v5 branch has no nightly of its own, so its landings (the | |
| # pull_request runs of its PR into main) run it too. | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.head_ref == 'release/v5-prerelease' | |
| needs: docker-base | |
| runs-on: ubuntu-latest | |
| timeout-minutes: ${{ matrix.ecosystem == 'sbt' && 45 || 35 }} | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ecosystem: [npm, pypi, gem, cargo, golang, maven, composer, nuget, deno, sbt] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Docker Buildx | |
| # `driver: docker` — see the coverage-docker matching step above | |
| # for the rationale (the per-ecosystem image's `FROM | |
| # socket-patch-test-base:latest` only resolves when buildx talks | |
| # directly to the host docker daemon). | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 | |
| with: | |
| driver: docker | |
| - name: Install Rust | |
| run: scripts/rustup-retry.sh toolchain install | |
| # No `actions/cache` here intentionally. This job builds Docker | |
| # images and would be flagged by zizmor's cache-poisoning audit. | |
| - name: Download base image | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| pattern: docker-base-image* | |
| merge-multiple: true | |
| path: docker-base | |
| - name: Load base image | |
| run: docker load --input docker-base/socket-patch-test-base.tar | |
| - name: Build ${{ matrix.ecosystem }} image | |
| uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 | |
| with: | |
| context: . | |
| file: tests/docker/Dockerfile.${{ matrix.ecosystem }} | |
| tags: socket-patch-test-${{ matrix.ecosystem }}:latest | |
| load: true | |
| - name: Run ${{ matrix.ecosystem }} Docker e2e test | |
| # Every ecosystem is unconditionally compiled in; only the | |
| # `docker-e2e` feature is needed to compile the suite itself. | |
| # The composer/nuget/pypi vendored build-proof capstones (each | |
| # ending in the manifest-less VEX stage) ride the same image as | |
| # their ecosystem's main suite; the gem and maven vendor capstones | |
| # run only in coverage-docker. | |
| run: | | |
| EXTRA="" | |
| FILTER="" | |
| case "${{ matrix.ecosystem }}" in | |
| composer) EXTRA="--test docker_e2e_vendor_composer" ;; | |
| nuget) EXTRA="--test docker_e2e_vendor_nuget" ;; | |
| pypi) EXTRA="--test docker_e2e_vendor_pypi_pm" ;; | |
| # Every sbt / Mill / scala-cli agent cell (the full sbt version | |
| # x mode matrix is sbt-compatibility.yml). | |
| sbt) | |
| export SOCKET_PATCH_SBT_DOCKER_VERSIONS="1.2.8 1.13.0" | |
| export SOCKET_PATCH_DOCKER_E2E_REQUIRED=1 | |
| FILTER="--test-threads=1" ;; | |
| esac | |
| # shellcheck disable=SC2086 # EXTRA and FILTER are intentionally word-split | |
| cargo test -p socket-patch-cli --features docker-e2e --test "docker_e2e_${{ matrix.ecosystem }}" $EXTRA -- $FILTER | |
| # ---------------------------------------------------------------------- | |
| # Per-release real-toolchain matrices for the manifest-less VEX work that | |
| # loop several suites per release through a script (yarn) or need a | |
| # toolchain + lock-format axis (cargo). Each leg hard-fails on a skip. | |
| # ---------------------------------------------------------------------- | |
| yarn-classic-matrix: | |
| name: yarn-classic ${{ matrix.release }} | |
| # No `needs: [test, coverage]`: nothing here consumes their outputs, | |
| # and waiting on the ~30 min windows `test` leg made this the merge | |
| # queue's critical path. | |
| # Dropping that `needs` also dropped the draft skip it inherited, so | |
| # gate on draft here directly (push/merge_group/schedule still run). | |
| # Only wait for the clippy preflight. | |
| if: github.event.pull_request.draft != true && needs.clippy.outputs.reuse != 'true' | |
| needs: clippy | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 40 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # 1.0.2 oldest 1.x; 1.6.0 last that installs nothing for a `file:` | |
| # tarball; 1.7.0 first vendored-capable; 1.9.4 last without the | |
| # `integrity` line; 1.10.1 first with it; 1.22.22 current. | |
| release: ['1.0.2', '1.6.0', '1.7.0', '1.9.4', '1.10.1', '1.22.22'] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| run: scripts/rustup-retry.sh toolchain install | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| shared-key: dev-ubuntu-latest | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '24.x' | |
| - name: Manifest-less VEX over every real yarn classic flow | |
| # The script exports SOCKET_PATCH_YARN_E2E_REQUIRED=1 and | |
| # SOCKET_PATCH_YARN_CLASSIC_E2E_VERSION itself. | |
| env: | |
| COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' | |
| YARN_CLASSIC_RELEASE: ${{ matrix.release }} | |
| run: scripts/yarn-classic-vex-matrix.sh "$YARN_CLASSIC_RELEASE" | |
| yarn-berry-e2e: | |
| name: yarn-berry ${{ matrix.yarn }} (${{ matrix.os }}) | |
| # No `needs: [test, coverage]`: nothing here consumes their outputs, | |
| # and waiting on the ~30 min windows `test` leg made this the merge | |
| # queue's critical path. | |
| # Dropping that `needs` also dropped the draft skip it inherited, so | |
| # gate on draft here directly (push/merge_group/schedule still run). | |
| # Only wait for the clippy preflight. | |
| if: github.event.pull_request.draft != true && needs.clippy.outputs.reuse != 'true' | |
| needs: clippy | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # 4.0.2 bare-hex checksum writer (4.0.0-4.0.2), 4.1.0 first | |
| # `10c0/` writer, current, and 4.12.0 on macOS / Windows. The rest | |
| # of the spread (4.6.0, 4.12.0 on ubuntu) is yarn-berry-full. | |
| include: | |
| - {os: ubuntu-latest, yarn: '4.0.2'} | |
| - {os: ubuntu-latest, yarn: '4.1.0'} | |
| - {os: ubuntu-latest, yarn: '4.18.0'} | |
| - {os: windows-latest, yarn: '4.12.0'} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| steps: &yarn-berry-steps | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| shell: bash | |
| run: scripts/rustup-retry.sh toolchain install | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| shared-key: dev-${{ matrix.os }} | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Enable corepack | |
| run: corepack enable | |
| - name: Yarn berry hosted/vendored + manifest-less VEX | |
| shell: bash | |
| env: | |
| COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' | |
| SOCKET_PATCH_YARN_E2E_REQUIRED: '1' # the script also exports it | |
| YARN_BERRY_RELEASE: ${{ matrix.yarn }} | |
| run: scripts/yarn-berry-vex-matrix.sh "$YARN_BERRY_RELEASE" | |
| # Skipped on pull_request (except v5 landings), like e2e-full. | |
| # The macOS rows of `yarn-berry-e2e`: they run on main, the merge queue and | |
| # nightly, not on every PR push, so PRs stop queueing on the small | |
| # macOS runner pool. | |
| yarn-berry-e2e-macos: | |
| if: github.event_name != 'pull_request' && needs.clippy.outputs.reuse != 'true' | |
| name: yarn-berry ${{ matrix.yarn }} (${{ matrix.os }}) | |
| needs: clippy | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # 4.0.2 bare-hex checksum writer (4.0.0-4.0.2), 4.1.0 first | |
| # `10c0/` writer, current, and 4.12.0 on macOS / Windows. The rest | |
| # of the spread (4.6.0, 4.12.0 on ubuntu) is yarn-berry-full. | |
| include: | |
| - {os: macos-latest, yarn: '4.12.0'} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| steps: *yarn-berry-steps | |
| yarn-berry-full: | |
| name: yarn-berry ${{ matrix.yarn }} (${{ matrix.os }}) | |
| # merge_group runs the pull_request tier: the queue gates on ci-ok. | |
| if: (github.event_name != 'pull_request' && github.event_name != 'merge_group') || github.head_ref == 'release/v5-prerelease' | |
| needs: clippy | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {os: ubuntu-latest, yarn: '4.6.0'} | |
| - {os: ubuntu-latest, yarn: '4.12.0'} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| steps: *yarn-berry-steps | |
| # Every toolchain and every lock re-encoding at least once on PRs (the | |
| # toolchain's own lock is re-encoded as v1-v4 before socket-patch touches | |
| # it, the committed-lockfile case; '' keeps the toolchain's own format), | |
| # plus macOS and Windows; 1.93.1 with its own lock (on every OS) is the | |
| # pinned rust-toolchain.toml cell `cargo test` used to give | |
| # e2e_safety_cargo_build. cargo-vex-matrix-full runs the rest of the | |
| # toolchain x lock cross off pull_request. Each leg also runs | |
| # e2e_safety_cargo_build (its headline test honours the knobs). | |
| cargo-vex-matrix: | |
| if: needs.e2e-build.outputs.reuse != 'true' | |
| name: cargo ${{ matrix.toolchain }} lock-v${{ matrix.lock || 'own' }} (${{ matrix.os }}) | |
| # e2e-build only (its binaries; only the matching OS build is needed); | |
| # see yarn-classic-matrix on test/coverage. | |
| needs: [e2e-build] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 40 | |
| # What .cargo/config.toml's [env] gives processes cargo launches; these | |
| # legs launch the test binaries themselves. | |
| env: | |
| SOCKET_NO_CONFIG: '1' | |
| SOCKET_NO_UPDATE_CHECK: '1' | |
| SOCKET_TELEMETRY_DISABLED: '1' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {os: ubuntu-latest, toolchain: '1.93.1', lock: ''} | |
| - {os: ubuntu-latest, toolchain: '1.93.1', lock: '1'} | |
| - {os: ubuntu-latest, toolchain: stable, lock: '2'} | |
| - {os: ubuntu-latest, toolchain: '1.82.0', lock: '3'} | |
| - {os: ubuntu-latest, toolchain: '1.93.1', lock: '4'} | |
| steps: &cargo-vex-steps | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Download the e2e binaries | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| pattern: e2e-bin-${{ matrix.os }}* | |
| merge-multiple: true | |
| path: target/e2e-archive | |
| - name: Unpack the e2e binaries | |
| shell: bash | |
| # Decompress to a file, not into `| tar -xf -`: a tar that exits at | |
| # the end-of-archive marker without draining stdin (macOS's does) | |
| # leaves zstd writing into a closed pipe, and pipefail turns its | |
| # "Broken pipe" into a failed leg that evicts the queue entry. | |
| run: | | |
| set -euo pipefail | |
| mkdir -p target/e2e-bin | |
| zstd -q -d -f target/e2e-archive/e2e-bin.tar.zst -o target/e2e-bin.tar | |
| tar -xf target/e2e-bin.tar -C target/e2e-bin | |
| rm target/e2e-bin.tar | |
| - name: Install Rust | |
| shell: bash | |
| run: scripts/rustup-retry.sh toolchain install | |
| - name: Install the cargo under test | |
| # bash, not the Windows default pwsh: in PowerShell | |
| # "$CARGO_TEST_TOOLCHAIN" is an (unset) PowerShell variable, so the | |
| # windows-latest leg ran `rustup toolchain install ""`. | |
| shell: bash | |
| env: | |
| CARGO_TEST_TOOLCHAIN: ${{ matrix.toolchain }} | |
| run: scripts/rustup-retry.sh toolchain install "$CARGO_TEST_TOOLCHAIN" --profile minimal | |
| - name: Real-cargo hosted/vendored + manifest-less VEX | |
| # The e2e-build binaries, run from the package root as `cargo test` | |
| # would (see the e2e job's staging step). | |
| shell: bash | |
| env: | |
| SOCKET_PATCH_CARGO_E2E_REQUIRED: '1' | |
| SOCKET_PATCH_CARGO_E2E_TOOLCHAIN: ${{ matrix.toolchain }} | |
| SOCKET_PATCH_CARGO_E2E_LOCK_VERSION: ${{ matrix.lock }} | |
| run: | | |
| set -euo pipefail | |
| exe='' | |
| if [ "$RUNNER_OS" = Windows ]; then exe=.exe; fi | |
| chmod +x target/e2e-bin/* || true | |
| mkdir -p target/debug target/tmp | |
| cp "target/e2e-bin/socket-patch$exe" "target/debug/socket-patch$exe" | |
| cd crates/socket-patch-cli | |
| export CARGO_MANIFEST_DIR="$PWD" | |
| status=0 | |
| for suite in e2e_redirect_cargo_build e2e_redirect_cargo_shapes e2e_vendor_cargo_build mode_migration_cargo; do | |
| echo "::group::$suite" | |
| "../../target/e2e-bin/$suite$exe" || status=1 | |
| echo "::endgroup::" | |
| done | |
| "../../target/e2e-bin/e2e_safety_cargo_build$exe" --ignored || status=1 | |
| exit "$status" | |
| cargo-vex-matrix-windows: | |
| if: needs.e2e-build-windows.outputs.reuse != 'true' | |
| name: cargo ${{ matrix.toolchain }} lock-v${{ matrix.lock || 'own' }} (${{ matrix.os }}) | |
| needs: [e2e-build-windows] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 40 | |
| env: *e2e-env | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {os: windows-latest, toolchain: stable, lock: '1'} | |
| - {os: windows-latest, toolchain: '1.93.1', lock: ''} | |
| steps: *cargo-vex-steps | |
| # The macOS rows run on main, the merge queue and nightly, not per PR push. | |
| cargo-vex-matrix-macos: | |
| if: github.event_name != 'pull_request' && needs.e2e-build-macos.outputs.reuse != 'true' | |
| name: cargo ${{ matrix.toolchain }} lock-v${{ matrix.lock || 'own' }} (${{ matrix.os }}) | |
| needs: [e2e-build-macos] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 40 | |
| # What .cargo/config.toml's [env] gives processes cargo launches; these | |
| # legs launch the test binaries themselves. | |
| env: | |
| SOCKET_NO_CONFIG: '1' | |
| SOCKET_NO_UPDATE_CHECK: '1' | |
| SOCKET_TELEMETRY_DISABLED: '1' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {os: macos-latest, toolchain: stable, lock: '1'} | |
| - {os: macos-latest, toolchain: '1.93.1', lock: ''} | |
| steps: *cargo-vex-steps | |
| cargo-vex-matrix-full: | |
| name: cargo ${{ matrix.toolchain }} lock-v${{ matrix.lock || 'own' }} (${{ matrix.os }}) | |
| # merge_group runs the pull_request tier: the queue gates on ci-ok. | |
| if: (github.event_name != 'pull_request' && github.event_name != 'merge_group') || github.head_ref == 'release/v5-prerelease' | |
| needs: [e2e-build] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 40 | |
| # What .cargo/config.toml's [env] gives processes cargo launches; these | |
| # legs launch the test binaries themselves. | |
| env: | |
| SOCKET_NO_CONFIG: '1' | |
| SOCKET_NO_UPDATE_CHECK: '1' | |
| SOCKET_TELEMETRY_DISABLED: '1' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {os: ubuntu-latest, toolchain: '1.82.0', lock: '1'} | |
| - {os: ubuntu-latest, toolchain: '1.82.0', lock: '2'} | |
| - {os: ubuntu-latest, toolchain: '1.82.0', lock: '4'} | |
| - {os: ubuntu-latest, toolchain: '1.82.0', lock: ''} | |
| - {os: ubuntu-latest, toolchain: '1.93.1', lock: '2'} | |
| - {os: ubuntu-latest, toolchain: '1.93.1', lock: '3'} | |
| - {os: ubuntu-latest, toolchain: stable, lock: ''} | |
| - {os: ubuntu-latest, toolchain: stable, lock: '1'} | |
| - {os: ubuntu-latest, toolchain: stable, lock: '3'} | |
| - {os: ubuntu-latest, toolchain: stable, lock: '4'} | |
| steps: *cargo-vex-steps | |
| # Manifest `[patch]` + the tagged detached lock (the v5 vendored cargo | |
| # wiring) on cargo 1.41 and 1.56 — below / at the 1.56 floor of | |
| # config-file `[patch]`. The old-toolchain tests prefer the local | |
| # `rust:1.41-slim` / `rust:1.56-slim` docker images (run `--network none`; | |
| # they build AND run the consumer) and skip when no old cargo is | |
| # available; this leg pulls both images and requires them. | |
| cargo-old-toolchains: | |
| name: cargo old toolchains (manifest [patch]) | |
| # No `needs: [test, coverage]`: nothing here consumes their outputs, | |
| # and waiting on the ~30 min windows `test` leg made this the merge | |
| # queue's critical path. | |
| # Dropping that `needs` also dropped the draft skip it inherited, so | |
| # gate on draft here directly (push/merge_group/schedule still run). | |
| # Only wait for the clippy preflight. | |
| if: github.event.pull_request.draft != true | |
| needs: clippy | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| run: scripts/rustup-retry.sh toolchain install | |
| - name: Pull the old cargos under test | |
| if: needs.clippy.outputs.reuse != 'true' | |
| run: | | |
| docker pull rust:1.41-slim | |
| docker pull rust:1.56-slim | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| shared-key: dev-ubuntu-latest | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Warm the shared Linux dev cache after merge-queue validation | |
| if: needs.clippy.outputs.reuse == 'true' | |
| run: cargo test --locked -p socket-patch-cli --test e2e_vendor_cargo_build --no-run | |
| - name: Vendored manifest [patch] on old cargo | |
| if: needs.clippy.outputs.reuse != 'true' | |
| shell: bash | |
| env: | |
| SOCKET_PATCH_CARGO_E2E_REQUIRED: '1' | |
| SOCKET_PATCH_CARGO_OLD_TOOLCHAINS_REQUIRED: '1' | |
| run: | | |
| set -euo pipefail | |
| cargo test -p socket-patch-cli --test e2e_vendor_cargo_build -- old_toolchain --nocapture | |
| # ---------------------------------------------------------------------- | |
| # Hosted-mode production e2e — merge-blocking through `ci-ok`, with a kill | |
| # switch. | |
| # | |
| # Drives `scan --mode hosted` against the REAL production endpoints | |
| # (patches-api.socket.dev + patch.socket.dev) and the REAL upstream | |
| # registries, using patches that are actually published on production. | |
| # Nothing is mocked. Every other hosted-mode capstone in this repo | |
| # (e2e_redirect_*) points at a wiremock stand-in, so this job is the only | |
| # thing that would notice production drifting away from the CLI. | |
| # | |
| # The suite itself is `#[ignore]`-gated, so it stays OUT of the `test` and | |
| # `e2e` jobs and only runs where it is explicitly asked for — here. | |
| # | |
| # INVARIANTS (`ci-ok` needs this job, and older rulesets may still name the | |
| # check `hosted-e2e` directly): | |
| # * NO job-level `if:` — a *skipped* required check is ambiguous to branch | |
| # protection and can wedge a PR at "Expected — waiting for status". | |
| # The kill switch gates the STEPS, never the job. | |
| # * NO `needs:` — an upstream failure would skip this job, same wedge. | |
| # * NO matrix and NO rename — the check name must stay `hosted-e2e`. | |
| # * NO `continue-on-error` — a bypass must be visible, not invisible. | |
| # The job ALWAYS runs and ALWAYS reaches success or failure. | |
| # | |
| # ESCAPE HATCH — when production is down and this is blocking merges: | |
| # Settings -> Secrets and variables -> Actions -> Variables -> | |
| # HOSTED_E2E_DISABLED = true | |
| # then "Re-run failed jobs" on any blocked PR. `vars` is read at job-run | |
| # time, so no commit and no push is needed; the job goes green with a loud | |
| # ::warning:: and a BYPASSED banner in the job summary. DELETE the variable | |
| # to re-arm. For a one-off: Actions -> CI -> Run workflow -> | |
| # hosted_e2e = force (ignore the variable) | skip (bypass this run). | |
| # ---------------------------------------------------------------------- | |
| hosted-e2e: | |
| name: hosted-e2e # may be a required check; do not rename | |
| if: github.event.pull_request.draft != true && needs.clippy.outputs.reuse != 'true' | |
| needs: clippy | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| timeout-minutes: 30 | |
| concurrency: | |
| # These are real requests against a real production service — keep it to | |
| # one run per PR ref rather than one per push. Main pushes group per | |
| # commit like the workflow: a group holds ONE pending job, so a shared | |
| # main group cancelled queued pushes and ci-ok failed them. | |
| group: hosted-e2e-${{ (github.event_name == 'push' && github.sha) || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| HOSTED_E2E_DISABLED: ${{ vars.HOSTED_E2E_DISABLED }} | |
| # The `inputs` context is empty on push/pull_request, so default to auto. | |
| HOSTED_E2E_MODE: ${{ (github.event_name == 'workflow_dispatch' && inputs.hosted_e2e) || 'auto' }} | |
| steps: | |
| - name: Resolve the kill switch | |
| id: gate | |
| run: | | |
| set -eu | |
| run=true; reason='' | |
| case "$HOSTED_E2E_MODE" in | |
| force) reason='workflow_dispatch hosted_e2e=force (kill switch ignored)' ;; | |
| skip) run=false; reason='workflow_dispatch hosted_e2e=skip' ;; | |
| *) if [ "${HOSTED_E2E_DISABLED:-}" = 'true' ]; then | |
| run=false | |
| reason='repository variable HOSTED_E2E_DISABLED=true' | |
| fi ;; | |
| esac | |
| echo "run=$run" >> "$GITHUB_OUTPUT" | |
| if [ "$run" = 'false' ]; then | |
| echo "::warning title=hosted-e2e BYPASSED::$reason" | |
| { | |
| echo '## :warning: hosted-e2e BYPASSED — no production coverage in this run' | |
| echo | |
| echo "Reason: $reason" | |
| echo | |
| echo 'Re-arm by deleting the HOSTED_E2E_DISABLED repository variable' | |
| echo '(Settings -> Secrets and variables -> Actions -> Variables).' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Checkout | |
| if: steps.gate.outputs.run == 'true' | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| if: steps.gate.outputs.run == 'true' | |
| run: scripts/rustup-retry.sh toolchain install | |
| - name: Cache cargo | |
| if: steps.gate.outputs.run == 'true' | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| shared-key: dev-ubuntu-latest | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Setup Node.js | |
| if: steps.gate.outputs.run == 'true' | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| # Node 24, NOT the 20.20.2 the other jobs pin. pnpm 10 imports | |
| # `node:sqlite` for its store index, which does not exist before | |
| # Node 22 (and is only stable in 24) — on 20 every `pnpm install` | |
| # dies with ERR_UNKNOWN_BUILTIN_MODULE. This suite drives real, | |
| # current package managers against production, so it tracks what | |
| # users actually run rather than the pin the offline suites need. | |
| node-version: '24.x' | |
| - name: Setup npm-family package managers | |
| if: steps.gate.outputs.run == 'true' | |
| env: | |
| # Every corepack shim invocation — including the suite's own | |
| # `has_command` probes — must be non-interactive, or the probe hangs | |
| # or exits non-zero and STRICT turns that into a failed leg. | |
| COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' | |
| # corepack owns pnpm and both yarn flavors. `corepack enable` alone is | |
| # not enough: it installs the shims, but `pnpm --version` still fails | |
| # for a project with no `packageManager` field — exactly the pnpm | |
| # fixture's shape — because the shim has no version to resolve. The | |
| # `prepare … --activate` lines set that global default AND pre-download | |
| # each version, so the first invocation inside a test is not also a | |
| # network fetch. | |
| # | |
| # pnpm must NOT come from `npm install -g` here: corepack has already | |
| # created its shim at the same path, and npm refuses with EEXIST. Only | |
| # bun, which corepack does not manage, comes from npm. | |
| run: | | |
| set -eu | |
| corepack enable | |
| corepack prepare pnpm@10 --activate | |
| corepack prepare yarn@1.22.22 --activate | |
| corepack prepare yarn@4.6.0 --activate | |
| npm install -g bun@1 | |
| # vlt: the same sha512-checked pack-and-install as the e2e rows. | |
| js=$(scripts/install-vlt.sh 1.2.0 "$RUNNER_TEMP/vlt-tool") | |
| { | |
| echo "SOCKET_PATCH_VLT_E2E_JS=$js" | |
| echo "SOCKET_PATCH_VLT_E2E_VERSION=1.2.0" | |
| echo "SOCKET_PATCH_VLT_E2E_REQUIRED=1" | |
| echo "SOCKET_PATCH_HOSTED_E2E_STRICT=1" | |
| } >> "$GITHUB_ENV" | |
| node --version | |
| npm --version | |
| pnpm --version | |
| bun --version | |
| node --no-warnings "$js" --version | |
| - name: Setup Python + uv | |
| if: steps.gate.outputs.run == 'true' | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: '3.12.x' | |
| - name: Install uv | |
| if: steps.gate.outputs.run == 'true' | |
| run: scripts/pip-install-retry.sh uv && uv --version | |
| - name: Setup Ruby | |
| if: steps.gate.outputs.run == 'true' | |
| uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0 | |
| with: | |
| ruby-version: '3.2.10' | |
| # The gem hosted rewrite pins into the Gemfile.lock CHECKSUMS | |
| # section, which `bundle lock --add-checksums` only emits on >= 2.6. | |
| bundler: '2.6' | |
| bundler-cache: false | |
| - name: Setup Go | |
| if: steps.gate.outputs.run == 'true' | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: '1.24' | |
| cache: false | |
| - name: Run hosted-mode production e2e | |
| if: steps.gate.outputs.run == 'true' | |
| env: | |
| # A required check must never report green on an unexercised leg: | |
| # STRICT turns the suite's local "toolchain missing" soft-skips into | |
| # hard failures. Every toolchain it needs is installed above. | |
| SOCKET_PATCH_HOSTED_E2E_STRICT: '1' | |
| COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' | |
| run: | | |
| set -u | |
| # The public proxy intermittently returns 503 "Service temporarily | |
| # over capacity" — that is the documented reason the older live-API | |
| # suites were pulled from the PR matrix (see the `e2e` job). Retry the | |
| # whole suite a couple of times before calling it a real failure, so a | |
| # transient 503 does not block merges through a required check. | |
| set -o pipefail | |
| for attempt in 1 2 3; do | |
| echo "::group::hosted-e2e attempt $attempt" | |
| # The step shell runs with -e: keep a failed attempt from ending it. | |
| status=0 | |
| cargo test -p socket-patch-cli --test e2e_hosted_production -- \ | |
| --ignored --nocapture --test-threads=4 2>&1 | tee hosted-e2e.log || status=$? | |
| echo "::endgroup::" | |
| if [ "$status" -eq 0 ]; then | |
| # The vlt leg (probe-driven: the clean refusal while the artifact | |
| # is content-encoded, the full install proof once it is not). | |
| python3 scripts/check-vlt-legs.py \ | |
| --manifest crates/socket-patch-cli/tests/vlt-leg-manifest.json hosted-e2e.log | |
| exit $? | |
| fi | |
| echo "::warning title=hosted-e2e attempt $attempt failed::retrying" | |
| sleep $((attempt * 20)) | |
| done | |
| echo "::error title=hosted-e2e::suite failed on all 3 attempts" | |
| exit 1 | |
| - name: Run vendored-mode production e2e (vlt) | |
| if: steps.gate.outputs.run == 'true' | |
| # The vendored vlt install proof against production: the service's | |
| # directory artifact in the D19 layout, then a fresh `vlt ci`. | |
| env: | |
| SOCKET_PATCH_VENDORED_E2E_STRICT: '1' | |
| run: | | |
| set -uo pipefail | |
| for attempt in 1 2 3; do | |
| echo "::group::vendored vlt production attempt $attempt" | |
| status=0 | |
| cargo test -p socket-patch-cli --test e2e_vendored_production -- \ | |
| --include-ignored vlt_pinned_matrix --nocapture 2>&1 | tee vlt-vendored-production.log || status=$? | |
| echo "::endgroup::" | |
| if [ "$status" -eq 0 ]; then | |
| python3 scripts/check-vlt-legs.py \ | |
| --manifest crates/socket-patch-cli/tests/vlt-leg-manifest.json vlt-vendored-production.log | |
| exit $? | |
| fi | |
| echo "::warning title=vendored vlt production attempt $attempt failed::retrying" | |
| sleep $((attempt * 20)) | |
| done | |
| echo "::error title=hosted-e2e::the vendored vlt production proof failed on all 3 attempts" | |
| exit 1 | |
| # The aggregate required status check for the merge queue (and PRs). It needs | |
| # every job above, so adding a job here is how it becomes merge-blocking. | |
| # Skipped jobs (the nightly `full` tier) pass; failed or cancelled ones fail. | |
| ci-ok: | |
| name: ci-ok # registered as a required check; do not rename | |
| # A superseded PR must release its concurrency slot without waiting for | |
| # an aggregate runner. Keep an unconditional verdict in the merge queue | |
| # and on main: a cancelled dependency there must never pass the gate. | |
| if: ${{ always() && (github.event_name != 'pull_request' || !cancelled()) }} | |
| needs: [clippy, node-addon, lint-ecosystems, release-readiness, test, test-release, coverage, docker-base, coverage-docker, coverage-merge, dispatch-tests, e2e-build, e2e-build-windows, e2e-build-macos, e2e, e2e-gradle-mid, e2e-windows, e2e-full, e2e-docker, yarn-classic-matrix, yarn-berry-e2e, yarn-berry-full, cargo-vex-matrix, cargo-vex-matrix-windows, cargo-vex-matrix-full, cargo-old-toolchains, hosted-e2e, e2e-macos, yarn-berry-e2e-macos, cargo-vex-matrix-macos] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Check every needed job | |
| env: | |
| RESULTS: ${{ toJSON(needs) }} | |
| run: | | |
| echo "$RESULTS" | python3 -c ' | |
| import json, sys | |
| bad = {k: v["result"] for k, v in json.load(sys.stdin).items() | |
| if v["result"] not in ("success", "skipped")} | |
| for k, v in sorted(bad.items()): | |
| print(f"::error::{k}: {v}") | |
| sys.exit(1 if bad else 0)' |