v5: fix partial-stage repair bug, cut redundant downloads #958
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: | |
| branches: [main] | |
| pull_request: | |
| 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 | |
| # Supersede stale runs on force-push / rapid PR updates. The `main` guard is | |
| # load-bearing: main runs are the ONLY rust-cache writers (save-if), so they | |
| # must never be cancelled mid-save. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| jobs: | |
| clippy: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| 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. `rustup show` | |
| # reads rust-toolchain.toml in the repo root, then installs the | |
| # pinned channel + listed components if missing. No third-party | |
| # action dependency needed for toolchain setup. | |
| run: rustup show | |
| - 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. | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run clippy | |
| run: cargo clippy --workspace --all-features -- -D warnings | |
| # Moved-module aliases (patch::vendor → vendor, patch::go_mod_edit → | |
| # vendor::go_mod_edit, patch::go_redirect → patch::redirect::golang_local) | |
| # exist only for external consumers of the published core crate. | |
| # #[deprecated] on a `pub use` re-export emits no warnings | |
| # (rust-lang/rust#30827), so the compiler cannot pressure internal code | |
| # off the old paths — this grep is the guard instead. | |
| - name: Reject internal uses of moved-module alias paths | |
| run: | | |
| if grep -rn --include='*.rs' \ | |
| -e 'patch::vendor' -e 'patch::go_mod_edit' \ | |
| -e 'patch::go_redirect' -e 'patch::bun_lock_text' \ | |
| -e 'utils::telemetry' -e 'utils::cleanup_blobs' \ | |
| -e 'utils::date' -e 'utils::fuzzy_match' \ | |
| -e 'gem_setup::' -e 'composer_setup::' -e 'pth_hook::' \ | |
| crates; then | |
| echo '::error::use the canonical module paths (crate::vendor, patch::redirect::golang_local, crate::telemetry, manifest::cleanup_blobs, api::date, crawlers::fuzzy_match); the old-path aliases exist only for external consumers' | |
| exit 1 | |
| fi | |
| # The napi addon is only ever loaded by Node, so cargo's own tests never | |
| # exercise its JS loader or the engine/provider boundary. | |
| node-addon: | |
| 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: rustup show | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| 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 | |
| run: node --test crates/socket-patch-node/npm/test/smoke.mjs | |
| # Lint the out-of-workspace packaging artifacts: the RubyGems CLI launcher | |
| # gem + the Bundler plugin gem (Ruby), and the curl|sh installer. Ruby is | |
| # pre-installed on the ubuntu-latest runner. | |
| lint-ecosystems: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Ruby — syntax-check + build the launcher gem and Bundler plugin | |
| run: | | |
| ( cd gem/socket-patch && ruby -c lib/socket_patch/launcher.rb && ruby -c exe/socket-patch && gem build socket-patch.gemspec ) | |
| ( cd gem/socket-patch-bundler && ruby -c plugins.rb && gem build socket-patch-bundler.gemspec ) | |
| # The generated-plugin templates are pure Ruby — keep them parseable. | |
| ruby -c crates/socket-patch-core/src/setup/gem/templates/plugins.rb.tmpl | |
| ruby -c crates/socket-patch-core/src/setup/gem/templates/gemspec.tmpl | |
| - name: Python — test native installer harnesses | |
| run: python3 -B -m unittest discover -s scripts/tests -v | |
| - name: Shell — shellcheck the curl|sh installer | |
| # install.sh is the other distribution artifact this job lints; it | |
| # had no coverage anywhere before the self-update work touched the | |
| # same surface. 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/bump-version.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; | |
| # - PRs that bump the workspace version (release/vX.Y.Z bump PRs): the | |
| # full gate — CHANGELOG has a dated, non-empty section for the new | |
| # version and the tag doesn't already exist. | |
| release-readiness: | |
| 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 }} | |
| run: | | |
| 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: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| runs-on: ${{ matrix.os }} | |
| # Windows runs the same suite ~1.6x slower than macOS: on the base | |
| # branch it already took 34m40s of a flat 35m budget. | |
| timeout-minutes: ${{ matrix.os == 'windows-latest' && 50 || 35 }} | |
| 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. `rustup show` | |
| # reads rust-toolchain.toml in the repo root, then installs the | |
| # pinned channel + listed components if missing. No third-party | |
| # action dependency needed for toolchain setup. | |
| run: rustup show | |
| - 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: Build | |
| run: cargo build --workspace --all-features | |
| - name: Install Go (for vexctl) | |
| # 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: Install vexctl | |
| # `go install` puts the binary in $(go env GOPATH)/bin; surface | |
| # that path to subsequent steps so `Command::new("vexctl")` in | |
| # the test resolves. Pinned to a tagged release rather than | |
| # @latest for reproducibility. | |
| # | |
| # Retried: the install compiles 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. Follow-up option if this recurs: install the pinned | |
| # release BINARY (sha256-pinned) instead of compiling, which | |
| # sidesteps module verification and drops ~100s of compile time per | |
| # leg. | |
| shell: bash | |
| run: | | |
| for attempt in 1 2 3 4 5; do | |
| if go install github.com/openvex/vexctl@v0.3.0; 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 | |
| echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" | |
| - name: Run tests | |
| # `--all-features` would also RUN the docker-e2e / setup-e2e suites, | |
| # which soft-skip as "ok" in this job (no images are built here, and | |
| # macOS/Windows have no Docker at all) — dozens of fake greens per OS | |
| # that would hide a broken skip-guard behind a passing checkmark. | |
| # Build them with --all-features (compile rot is real coverage), but | |
| # run only the default-feature suites; the dedicated e2e-docker and | |
| # setup-matrix jobs run the gated suites for real. | |
| # | |
| # `--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' | |
| run: | | |
| set -euo pipefail | |
| cargo test --workspace --all-features --no-run | |
| cargo test --workspace --no-fail-fast | |
| test-release: | |
| runs-on: ubuntu-latest | |
| # Every tests/ target is its own optimized link, and the two cargo | |
| # invocations below build the graph twice (--all-features, then the | |
| # default features): ~25m on main with ~240 test binaries, so 30m left | |
| # no headroom as suites grow. The manifest-less VEX suites share two | |
| # multi-module binaries (tests/e2e_vex_lockfile/, tests/e2e_vex_build/) | |
| # to keep the count down; the extra 10m covers the rest. | |
| 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. `rustup show` | |
| # reads rust-toolchain.toml in the repo root, then installs the | |
| # pinned channel + listed components if missing. No third-party | |
| # action dependency needed for toolchain setup. | |
| run: rustup show | |
| - 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. | |
| # Build/run split for the same reason as the `test` job: the gated | |
| # docker-e2e / setup-e2e suites only soft-skip here — compile them, | |
| # don't count their skips as passes. | |
| run: | | |
| set -euo pipefail | |
| cargo test --workspace --all-features --profile ci-release --no-run | |
| cargo test --workspace --profile ci-release | |
| coverage: | |
| # 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 — this is | |
| # report-only so contributors get visibility without flaky CI when | |
| # coverage shifts naturally with test edits. | |
| 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 | |
| # `rustup show` installs the rust-toolchain.toml channel + listed | |
| # components; `rustup component add` adds the llvm-tools-preview | |
| # bits cargo-llvm-cov needs to merge .profraw files into lcov. | |
| run: | | |
| rustup show | |
| rustup 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: Run tests with coverage | |
| # Two-step pattern: `--no-report` runs instrumented tests and | |
| # collects the raw profile data, then the two `report` calls | |
| # emit lcov + summary from the same data. Avoids re-running | |
| # tests twice. The output 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. | |
| run: | | |
| cargo llvm-cov --workspace \ | |
| --no-report | |
| cargo llvm-cov report --lcov --output-path coverage-host.lcov | |
| cargo llvm-cov report --summary-only | tee coverage-summary.txt | |
| - name: Publish coverage summary to job summary | |
| # Render the per-file table cargo-llvm-cov prints 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 | |
| uses: ./.github/actions/upload-artifact | |
| with: | |
| name: coverage-host | |
| path: coverage-host.lcov | |
| if-no-files-found: error | |
| retention-days: 30 | |
| 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). | |
| # | |
| # 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. | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ecosystem: [npm, pypi, gem, cargo, golang, maven, composer, nuget, deno] | |
| 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 | |
| # `rustup show` consumes rust-toolchain.toml; the explicit | |
| # `component add` covers llvm-tools-preview for cargo-llvm-cov. | |
| run: | | |
| rustup show | |
| rustup 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: 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: 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: Build instrumented socket-patch binary | |
| # Source `cargo llvm-cov show-env` into the current shell so this | |
| # `cargo build` picks up RUSTC_WRAPPER=cargo-llvm-cov and the | |
| # same RUSTFLAGS that the subsequent `cargo llvm-cov` test step | |
| # will use. The bin we build ends up byte-compatible with the | |
| # test binaries — same source hashes → unified coverage map at | |
| # report time. Env stays scoped to this step (intentional; | |
| # cargo llvm-cov manages its own env in the test step). | |
| run: | | |
| eval "$(cargo llvm-cov show-env --export-prefix 2>/dev/null)" | |
| cargo build --bin socket-patch | |
| - name: Configure docker-e2e coverage hooks | |
| run: | | |
| echo "SOCKET_PATCH_COV_BIN=$PWD/target/debug/socket-patch" >> "$GITHUB_ENV" | |
| # Profraw files from the in-container binary land here. | |
| # cargo-llvm-cov scans target/ for *.profraw at report time. | |
| echo "SOCKET_PATCH_COV_PROFRAW_DIR=$PWD/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="" | |
| 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" ;; | |
| esac | |
| # shellcheck disable=SC2086 # EXTRA is intentionally word-split | |
| cargo llvm-cov \ | |
| --features docker-e2e \ | |
| --no-report \ | |
| --test docker_e2e_${{ matrix.ecosystem }} $EXTRA | |
| - 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: | |
| 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: Setup Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: '3.12.x' | |
| - name: Run pypi dispatch tests | |
| run: python pypi/socket-patch/test_dispatch.py | |
| e2e: | |
| needs: test | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| suite: e2e_cargo | |
| - os: ubuntu-latest | |
| suite: e2e_golang | |
| - os: ubuntu-latest | |
| suite: e2e_maven | |
| - os: ubuntu-latest | |
| suite: e2e_composer | |
| # composer is a shipped ecosystem, so e2e_composer's tests are | |
| # NOT `#[ignore]`-gated the way the live-registry maven/nuget | |
| # suites are — the matrix default `--ignored` filter | |
| # selected zero tests here and the leg passed vacuously. | |
| # `--include-ignored` runs them, plus any capstone added later. | |
| test_filter: --include-ignored | |
| - os: ubuntu-latest | |
| suite: e2e_nuget | |
| # Host build-proof capstones: fresh-checkout install + revert | |
| # against the REAL composer/bundler toolchains, each ending in the | |
| # manifest-less VEX matrix. `#[ignore]`-gated (the unpinned `test` | |
| # job skips them); `composer:` / `bundler:` install that exact | |
| # toolchain below, runs them via `--ignored`, and exports the | |
| # suites' `_REQUIRED` + `_VERSION` gates so a leg hard-fails | |
| # instead of skipping on a missing or wrong toolchain. | |
| # ubuntu-latest only — they need the pinned toolchain, not per-OS | |
| # coverage. | |
| # | |
| # composer: 1 (packagist stopped serving composer 1 on 2025-09-01, | |
| # so the fixture resolves from an inline repository), 2.2 LTS (the | |
| # other release with the git-source download fallback the hosted | |
| # redirect must drop) and current 2. | |
| - {os: ubuntu-latest, suite: e2e_vendor_composer_build, composer: '2'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_composer_build, composer: '2.2'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_composer_build, composer: '1'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_composer_build, composer: '2'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_composer_build, composer: '2.2'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_composer_build, composer: '1'} | |
| # setup-e2e host guards run in no other job (test job = default | |
| # features; setup-matrix job = shell script). | |
| - {os: ubuntu-latest, suite: setup_matrix_composer, test_filter: host_guard} | |
| # Real-bundler gem capstones, one leg per bundler era. Boundaries: | |
| # 1.17/2.1 merged GEM section, 2.2 separate sections, 2.5 last | |
| # pre-CHECKSUMS, 2.6 CHECKSUMS, 4.0.15/4.0.21 before/after the | |
| # strict frozen check (rubygems#9750). bundler <= 2.2 needs | |
| # Ruby <= 3.3 and 1.17-2.1 need Ruby <= 3.1 (`untaint`). | |
| - {os: ubuntu-latest, suite: e2e_redirect_gem_build, ruby: '3.1', bundler: '1.17.3'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_gem_build, ruby: '3.1', bundler: '2.1.4'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_gem_build, ruby: '3.1', bundler: '2.2.33'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_gem_build, ruby: '3.3', bundler: '2.5.23'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_gem_build, ruby: '3.3', bundler: '2.6.9'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_gem_build, ruby: '3.3', bundler: '2.7.2'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_gem_build, ruby: '3.4', bundler: '4.0.15'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_gem_build, ruby: '3.4', bundler: '4.0.21'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_gem_build, ruby: '3.1', bundler: '1.17.3'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_gem_build, ruby: '3.1', bundler: '2.1.4'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_gem_build, ruby: '3.1', bundler: '2.2.33'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_gem_build, ruby: '3.3', bundler: '2.5.23'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_gem_build, ruby: '3.3', bundler: '2.6.9'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_gem_build, ruby: '3.3', bundler: '2.7.2'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_gem_build, ruby: '3.4', bundler: '4.0.15'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_gem_build, ruby: '3.4', bundler: '4.0.21'} | |
| # not #[ignore]-gated -> --include-ignored is mandatory | |
| - {os: ubuntu-latest, suite: setup_matrix_gem, ruby: '3.3', bundler: '2.7.2', test_filter: --include-ignored} | |
| # The live-API smoke suites (e2e_npm, e2e_pypi, e2e_gem, | |
| # e2e_scan) are intentionally NOT in the PR matrix — their | |
| # `#[ignore]`-gated tests hit the real public proxy at | |
| # patches-api.socket.dev, which intermittently returns | |
| # 503 "Service temporarily over capacity" outside this | |
| # repo's control. Run on demand: | |
| # | |
| # cargo test -p socket-patch-cli --test e2e_npm -- --ignored | |
| # cargo test -p socket-patch-cli --test e2e_pypi -- --ignored | |
| # cargo test -p socket-patch-cli --test e2e_gem -- --ignored | |
| # cargo test -p socket-patch-cli --test e2e_scan -- --ignored | |
| # | |
| # Same policy for the self-update live smoke (hits real | |
| # github.com releases; catches asset-naming/redirect/SUMS | |
| # drift against the published pipeline — most useful right | |
| # after a release): | |
| # | |
| # cargo test -p socket-patch-cli --test self_update_e2e -- --ignored | |
| # | |
| # PR-time coverage for the same code paths comes from the | |
| # `e2e-docker` matrix below, which runs the same flow | |
| # against a hermetic wiremock fixture. | |
| # Safety-hardening e2e suites. The fast non-ignored ones | |
| # (e2e_safety_lock, e2e_safety_yarn_pnp) run via the | |
| # standard `test` job above on all three platforms, so no | |
| # matrix entry is needed for them. The two below need real | |
| # toolchains and are #[ignore]-gated. | |
| - os: ubuntu-latest | |
| suite: e2e_safety_cargo_build | |
| - os: macos-latest | |
| suite: e2e_safety_cargo_build | |
| - os: windows-latest | |
| suite: e2e_safety_cargo_build | |
| - os: ubuntu-latest | |
| suite: e2e_safety_pnpm | |
| - os: macos-latest | |
| suite: e2e_safety_pnpm | |
| # pnpm-on-Windows uses junctions for symlinks and copies | |
| # (not hardlinks) by default, so the CoW invariant holds | |
| # vacuously. Test still runs to verify apply doesn't error | |
| # on Windows — semantic Windows nlink coverage is a | |
| # follow-up (`std::fs::Metadata` doesn't expose nlink on | |
| # Windows; needs `GetFileInformationByHandle` via | |
| # `windows-sys`). | |
| - os: windows-latest | |
| suite: e2e_safety_pnpm | |
| # Wall-bound real-package-manager redirect capstones (~150s and | |
| # ~70s of network installs + bootstrap resolutions — profile- | |
| # insensitive, measured identical in debug and release). They ran | |
| # inside the serial `test` job on every OS; #[ignore]-gated out of | |
| # it and relocated here so they still run on every PR and every | |
| # OS, but in parallel off the critical path. They use the runner's | |
| # default node/corepack, exactly as they did inside `test` — no | |
| # setup-node step, no version change. | |
| # | |
| # `npm_required` turns the npm suites' "npm not installed" soft-skip | |
| # into a hard failure. Not on Windows: `Command::new("npm")` cannot | |
| # resolve `npm.cmd` there, so that leg still skips (a known gap; see | |
| # docs/testing/npm-compatibility.md). | |
| - os: ubuntu-latest | |
| suite: e2e_redirect_npm_build | |
| npm_required: '1' | |
| - os: macos-latest | |
| suite: e2e_redirect_npm_build | |
| npm_required: '1' | |
| - os: windows-latest | |
| suite: e2e_redirect_npm_build | |
| - os: ubuntu-latest | |
| suite: e2e_redirect_rush_sim | |
| - os: macos-latest | |
| suite: e2e_redirect_rush_sim | |
| - os: windows-latest | |
| suite: e2e_redirect_rush_sim | |
| # Hermetic real-bun capstones (wiremock patch service, real `bun | |
| # install`): hosted (`e2e_redirect_bun_build`), vendored | |
| # (`e2e_vendor_bun_build`) and the hosted⇄vendored takeover / | |
| # scoped-rollback suite (`mode_migration_bun`). They are NOT | |
| # `#[ignore]`-gated, so `test_filter: --include-ignored` is | |
| # mandatory — the job default `-- --ignored` would select zero | |
| # tests and pass vacuously (the e2e_composer trap above). The | |
| # runner images ship no bun, so without the `bun:` key below the | |
| # suites soft-skip; `bun:` installs that exact release via | |
| # setup-bun and exports SOCKET_PATCH_BUN_E2E_REQUIRED=1 (+ the | |
| # pinned version), under which the suites hard-fail instead of | |
| # skipping when bun is missing, the wrong version, or the fixture | |
| # install produces no text lock. | |
| # | |
| # Lock-era legs (ubuntu only): bun's text lock has three | |
| # grammars — lockfileVersion 0 (opt-in `--save-text-lockfile`, | |
| # 1.1.39–1.1.45; 2-tuple workspace entries), 1 (default from | |
| # 1.2.0 through 1.3.x) and 2 (1.4.0+). Registry 4-tuples are | |
| # identical across them but the workspace grammar, the lockb | |
| # migration recipe and tarball digest enforcement (URL/local | |
| # tarball sha512 checked only from 1.3.10) all differ, so the | |
| # latest release alone cannot prove the rewrite + fresh install | |
| # round-trip on the locks real projects commit. 1.1.45 = last v0 | |
| # writer, 1.2.23 = v1, 1.3.14 = last pre-v2 default, 1.4.2 = v2. | |
| - os: ubuntu-latest | |
| suite: e2e_redirect_bun_build | |
| bun: '1.4.2' | |
| test_filter: --include-ignored | |
| - os: macos-latest | |
| suite: e2e_redirect_bun_build | |
| bun: '1.4.2' | |
| test_filter: --include-ignored | |
| - os: windows-latest | |
| suite: e2e_redirect_bun_build | |
| bun: '1.4.2' | |
| test_filter: --include-ignored | |
| - os: ubuntu-latest | |
| suite: e2e_redirect_bun_build | |
| bun: '1.1.45' | |
| test_filter: --include-ignored | |
| - os: ubuntu-latest | |
| suite: e2e_redirect_bun_build | |
| bun: '1.2.23' | |
| test_filter: --include-ignored | |
| - os: ubuntu-latest | |
| suite: e2e_vendor_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: windows-latest | |
| suite: e2e_vendor_bun_build | |
| bun: '1.4.2' | |
| test_filter: --include-ignored | |
| - os: ubuntu-latest | |
| suite: e2e_vendor_bun_build | |
| bun: '1.1.45' | |
| test_filter: --include-ignored | |
| - os: ubuntu-latest | |
| suite: e2e_vendor_bun_build | |
| bun: '1.2.23' | |
| test_filter: --include-ignored | |
| - os: ubuntu-latest | |
| suite: mode_migration_bun | |
| bun: '1.4.2' | |
| test_filter: --include-ignored | |
| - os: macos-latest | |
| suite: mode_migration_bun | |
| bun: '1.4.2' | |
| test_filter: --include-ignored | |
| - os: windows-latest | |
| suite: mode_migration_bun | |
| bun: '1.4.2' | |
| test_filter: --include-ignored | |
| - os: ubuntu-latest | |
| suite: mode_migration_bun | |
| bun: '1.3.14' | |
| test_filter: --include-ignored | |
| # Manifest-less VEX era legs: the last pre-v2 text-lock writer for | |
| # the hosted/vendored capstones, the v0/v1 writers for the takeover | |
| # suite, and the binary bun.lockb era (1.0 / 1.1 lines) through | |
| # e2e_bun_lockb, which reads the SOCKET_PATCH_BUN_LOCKB_* gates | |
| # exported for it below. | |
| - {os: ubuntu-latest, suite: e2e_redirect_bun_build, bun: '1.3.14', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_bun_build, bun: '1.3.14', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: mode_migration_bun, bun: '1.1.45', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: mode_migration_bun, bun: '1.2.23', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_bun_lockb, bun: '1.0.36', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_bun_lockb, bun: '1.1.45', test_filter: --include-ignored} | |
| # Real-vlt capstones (DESIGN §8.4): wiremock patch service and a local | |
| # npm registry fed from npmjs, driven by the pinned vlt release | |
| # (`node vlt.js`, installed below from a sha512-checked `npm pack`). | |
| # Every test is `#[ignore]`d and named `vlt_pinned_matrix_*`, so the | |
| # filter must be `--include-ignored vlt_pinned_matrix` (the job | |
| # default `--ignored` selects nothing). The run pipes through | |
| # scripts/check-vlt-legs.py, which fails on `0 passed` or any leg | |
| # line the manifest does not predict. The eras: A0 0.0.0-16, A | |
| # 0.0.0-32, B rc.12/rc.14 (rc.14 legs reach public npm), C rc.32, | |
| # D 1.0.4/1.0.7, E 1.1.1, F 1.2.0. | |
| - {os: ubuntu-latest, suite: e2e_redirect_vlt_build, vlt: '1.2.0', test_filter: --include-ignored vlt_pinned_matrix} | |
| - {os: macos-latest, suite: e2e_redirect_vlt_build, vlt: '1.2.0', test_filter: --include-ignored vlt_pinned_matrix} | |
| - {os: windows-latest, suite: e2e_redirect_vlt_build, vlt: '1.2.0', test_filter: --include-ignored vlt_pinned_matrix} | |
| - {os: ubuntu-latest, suite: e2e_redirect_vlt_build, vlt: '0.0.0-16', test_filter: --include-ignored vlt_pinned_matrix} | |
| - {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_redirect_vlt_build, vlt: '1.0.0-rc.14', test_filter: --include-ignored vlt_pinned_matrix} | |
| - {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_redirect_vlt_build, vlt: '1.0.4', test_filter: --include-ignored vlt_pinned_matrix} | |
| - {os: ubuntu-latest, suite: e2e_redirect_vlt_build, vlt: '1.1.1', 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: macos-latest, suite: e2e_vendor_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: ubuntu-latest, suite: e2e_vendor_vlt_build, vlt: '0.0.0-32', 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: e2e_vendor_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.4', test_filter: --include-ignored vlt_pinned_matrix} | |
| - {os: windows-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.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: windows-latest, suite: mode_migration_vlt, vlt: '1.2.0', 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: mode_migration_vlt, vlt: '1.0.0-rc.14', test_filter: --include-ignored vlt_pinned_matrix, vlt_upgrade: '1.2.0'} | |
| - {os: windows-latest, suite: mode_migration_vlt, vlt: '1.0.0-rc.14', test_filter: --include-ignored vlt_pinned_matrix} | |
| # Linux `auto` hardlinks from the global store; every OS gets the | |
| # explicit hardlink linker. | |
| - {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: macos-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_safety_vlt, vlt: '1.2.0', test_filter: --include-ignored vlt_pinned_matrix, vlt_store_linker: 'hardlink'} | |
| # rc.12 gets the definite no-hook advisory; windows rc.14 runs the | |
| # legacy DepIDs on NTFS with pre-junction symlinks. | |
| - {os: ubuntu-latest, suite: e2e_vlt, vlt: '1.2.0', test_filter: --include-ignored vlt_pinned_matrix} | |
| - {os: macos-latest, suite: e2e_vlt, vlt: '1.2.0', test_filter: --include-ignored vlt_pinned_matrix} | |
| - {os: windows-latest, suite: e2e_vlt, vlt: '1.2.0', 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} | |
| - {os: ubuntu-latest, suite: e2e_vlt, vlt: '1.0.0-rc.12', 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} | |
| - {os: ubuntu-latest, suite: e2e_vlt, vlt: '1.0.7', 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} | |
| # The named corepack pnpm hosted legs (pnpm 7-11, get-uuid, | |
| # zero-touch, --trust-lockfile). `#[ignore]`d; the pinned matrix | |
| # inside the same suite runs in pnpm-compatibility.yml, hence the | |
| # skip. Node 24 (step below): the | |
| # corepack pnpm@10/11 legs require it. | |
| - {os: ubuntu-latest, suite: e2e_redirect_pnpm_build, test_filter: '--ignored --skip pnpm_pinned_matrix'} | |
| - {os: macos-latest, suite: e2e_redirect_pnpm_build, test_filter: '--ignored --skip pnpm_pinned_matrix'} | |
| - {os: windows-latest, suite: e2e_redirect_pnpm_build, test_filter: '--ignored --skip pnpm_pinned_matrix'} | |
| # Real-uv hosted/vendored capstones ending in manifest-less VEX: | |
| # one leg per uv 0.N line + the 0.5.x boundary (0.5.4 still | |
| # re-resolves a transitive override / rejects a repointed | |
| # constraint under --locked; 0.5.5 keeps both). | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.1.45'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.2.37'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.3.5'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.4.30'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.5.3'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.5.4'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.5.5'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.5.6'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.6.17'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.7.22'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.8.24'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.9.30'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.10.12'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.11.33'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_uv_build, uv: '0.12.17'} | |
| - {os: macos-latest, suite: e2e_redirect_uv_build, uv: '0.12.17'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.1.45', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.2.37', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.3.5', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.4.30', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.5.3', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.5.4', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.5.5', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.5.6', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.6.17', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.7.22', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.8.24', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.9.30', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.10.12', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.11.33', test_filter: --include-ignored} | |
| - {os: ubuntu-latest, suite: e2e_vendor_pypi_build, uv: '0.12.17', test_filter: --include-ignored} | |
| - {os: macos-latest, suite: e2e_vendor_pypi_build, uv: '0.12.17', test_filter: --include-ignored} | |
| # The Poetry / PDM / Hatch / Pipenv / pip / deno manifest-less VEX | |
| # capstones share ONE test binary (`e2e_vex_build`, a module per | |
| # tool — one optimized link in test-release instead of six), so | |
| # each leg's `test_filter` names its tool's module and keeps | |
| # `--ignored`. | |
| # Real-Poetry hosted + vendored capstones (#[ignore]-gated, | |
| # unix-only). One leg per major / lock format: 1.0 (lock 1.0), | |
| # 1.1 (lock 1.1), 1.8 (lock 2.0), 2.x (lock 2.1). | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'poetry:: --ignored', poetry: '1.0.10'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'poetry:: --ignored', poetry: '1.1.15'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'poetry:: --ignored', poetry: '1.8.5'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'poetry:: --ignored', poetry: '2.0.1'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'poetry:: --ignored', poetry: '2.4.3'} | |
| - {os: macos-latest, suite: e2e_vex_build, test_filter: 'poetry:: --ignored', poetry: '2.4.3'} | |
| # Real PDM / Hatch capstones (wiremock Socket API that also serves | |
| # the hosted wheel; PyPI for the tool bootstrap + six). | |
| # PDM: lock 2 (1.4), refused 3.1 (1.15) and 4.2 (2.7), 4.3 (2.8), | |
| # the hishel<1 bootstrap window (2.25) and current. | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'pdm:: --ignored', pdm: '1.4.5'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'pdm:: --ignored', pdm: '1.15.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: 'pdm:: --ignored', pdm: '2.8.2'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'pdm:: --ignored', pdm: '2.25.9'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'pdm:: --ignored', pdm: '2.29.2'} | |
| - {os: macos-latest, suite: e2e_vex_build, test_filter: 'pdm:: --ignored', pdm: '2.29.2'} | |
| # Hatch: 1.0 (hatch.toml env vendoring refused, needs >= 1.2), | |
| # 1.2, the virtualenv<21 window (1.9, 1.14) and current. | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'hatch:: --ignored', hatch: '1.0.0'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'hatch:: --ignored', hatch: '1.2.1'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'hatch:: --ignored', hatch: '1.9.7'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'hatch:: --ignored', hatch: '1.14.2'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'hatch:: --ignored', hatch: '1.18.1'} | |
| - {os: macos-latest, suite: e2e_vex_build, test_filter: 'hatch:: --ignored', hatch: '1.18.1'} | |
| # Real Pipenv / pip capstones (mock patch server; the tools are | |
| # bootstrapped from PyPI). `pipenv:` / `pip:` hold one or more | |
| # space-separated releases the suite loops over. | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'pipenv:: --ignored', pipenv: '2022.12.19'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'pipenv:: --ignored', pipenv: '2023.12.1'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'pipenv:: --ignored', pipenv: '2024.4.1'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'pipenv:: --ignored', pipenv: '2025.1.3'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'pipenv:: --ignored', pipenv: '2026.8.0'} | |
| - {os: macos-latest, suite: e2e_vex_build, test_filter: 'pipenv:: --ignored', pipenv: '2022.12.19 2026.8.0'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'pip:: --ignored', pip: '22 23 24 25 26'} | |
| - {os: macos-latest, suite: e2e_vex_build, test_filter: 'pip:: --ignored', pip: '22 26'} | |
| # Real-Maven hosted + vendored capstones, one leg per Maven line: | |
| # 3.6 (pre http-blocker), 3.8 (resolver 1.6: no trusted checksums), | |
| # 3.9 (trusted checksums), 4.0 rc. | |
| - {os: ubuntu-latest, suite: e2e_redirect_maven_build, maven: '3.6.3'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_maven_build, maven: '3.8.9'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_maven_build, maven: '3.9.16'} | |
| - {os: ubuntu-latest, suite: e2e_redirect_maven_build, maven: '4.0.0-rc-6'} | |
| - {os: macos-latest, suite: e2e_redirect_maven_build, maven: '3.9.16'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_maven_build, maven: '3.6.3'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_maven_build, maven: '3.8.9'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_maven_build, maven: '3.9.16'} | |
| - {os: ubuntu-latest, suite: e2e_vendor_maven_build, maven: '4.0.0-rc-6'} | |
| - {os: macos-latest, suite: e2e_vendor_maven_build, maven: '3.9.16'} | |
| # Real .NET SDK capstones: hosted + vendored nuget, one leg per SDK | |
| # major (the suite pins the major through a sandbox global.json). | |
| - {os: ubuntu-latest, suite: e2e_nuget_dotnet_build, dotnet: '6'} | |
| - {os: ubuntu-latest, suite: e2e_nuget_dotnet_build, dotnet: '7'} | |
| - {os: ubuntu-latest, suite: e2e_nuget_dotnet_build, dotnet: '8'} | |
| - {os: ubuntu-latest, suite: e2e_nuget_dotnet_build, dotnet: '9'} | |
| - {os: ubuntu-latest, suite: e2e_nuget_dotnet_build, dotnet: '10'} | |
| - {os: macos-latest, suite: e2e_nuget_dotnet_build, dotnet: '8'} | |
| # Real deno negative capstone (no hosted/vendored wiring exists for | |
| # deno; VEX must attest nothing), one leg per major. | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'deno:: --ignored', deno: '1.46.3'} | |
| - {os: ubuntu-latest, suite: e2e_vex_build, test_filter: 'deno:: --ignored', deno: '2.9.7'} | |
| 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 | |
| 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. `rustup show` | |
| # reads rust-toolchain.toml in the repo root, then installs the | |
| # pinned channel + listed components if missing. No third-party | |
| # action dependency needed for toolchain setup. | |
| run: rustup show | |
| - 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: | |
| # Matrix suites otherwise collide on one key: only one of the ~9 | |
| # same-OS legs wins the cache reserve and the rest fail to save. | |
| # Several suites run one leg per pinned toolchain release (bun, uv, | |
| # poetry, pdm, hatch, pipenv, pip, bundler, composer, maven, dotnet, | |
| # deno, vlt), so the release is part of the key too, plus the vlt | |
| # store linker of the two ubuntu e2e_safety_vlt legs. | |
| key: ${{ matrix.suite }}-${{ matrix.vlt || matrix.bun || matrix.uv || matrix.poetry || matrix.pdm || matrix.hatch || matrix.pipenv || matrix.pip || matrix.bundler || matrix.composer || matrix.maven || matrix.dotnet || matrix.deno || 'default' }}${{ matrix.vlt_store_linker && format('-{0}', matrix.vlt_store_linker) || '' }} | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Setup Node.js | |
| if: matrix.suite == 'e2e_npm' || matrix.suite == 'e2e_scan' || matrix.suite == 'e2e_safety_pnpm' | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '20.20.2' | |
| - name: Setup pnpm | |
| if: 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: matrix.suite == 'e2e_redirect_pnpm_build' | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '24.x' | |
| - name: Setup Python | |
| if: 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: | | |
| python -m pip install --disable-pip-version-check --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. | |
| run: python -m pip install 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: | | |
| python -m pip install 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: 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@f3e473d116dcccaddc5834248c87452386958240 # v2 | |
| with: | |
| php-version: '8.2' | |
| tools: composer:${{ matrix.composer }} | |
| - name: Setup Java (Maven legs) | |
| if: matrix.maven != '' | |
| uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Install Maven ${{ matrix.maven }} | |
| if: matrix.maven != '' | |
| # Straight from the Apache archive (sha512-verified), so a leg gets | |
| # exactly the release it names rather than the runner's Maven. | |
| shell: bash | |
| env: | |
| MAVEN_VERSION: ${{ matrix.maven }} | |
| run: | | |
| major="${MAVEN_VERSION%%.*}" | |
| url="https://archive.apache.org/dist/maven/maven-${major}/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" | |
| curl -fsSL --retry 3 "$url" -o "$RUNNER_TEMP/maven.tgz" | |
| sum="$(curl -fsSL --retry 3 "$url.sha512" | cut -d' ' -f1)" | |
| echo "$sum $RUNNER_TEMP/maven.tgz" | shasum -a 512 -c - | |
| tar -xzf "$RUNNER_TEMP/maven.tgz" -C "$RUNNER_TEMP" | |
| echo "SOCKET_PATCH_MAVEN_E2E_MVN=$RUNNER_TEMP/apache-maven-${MAVEN_VERSION}/bin/mvn" >> "$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 | |
| if: matrix.vlt == '' | |
| # Suites are `#[ignore]`-gated out of the unpinned `test` job by | |
| # default, hence `--ignored`; an entry that sets `test_filter` | |
| # overrides the selector for itself only. | |
| env: | |
| # Bun legs only: turn the bun suites' "bun not installed / no text | |
| # lock" soft-skips into hard failures and make them assert the | |
| # pinned release, so a leg can never report green on an | |
| # unexercised toolchain. Both are the EMPTY string on non-bun legs, | |
| # which the suites treat as unset. | |
| SOCKET_PATCH_BUN_E2E_REQUIRED: ${{ matrix.bun != '' && '1' || '' }} | |
| SOCKET_PATCH_BUN_E2E_VERSION: ${{ matrix.bun }} | |
| # e2e_bun_lockb only. SOCKET_PATCH_BUN_LOCKB_VERSION is checked with | |
| # `var().is_ok()`, so it must stay EMPTY on every other leg. | |
| SOCKET_PATCH_BUN_LOCKB_REQUIRED: ${{ matrix.suite == 'e2e_bun_lockb' && '1' || '' }} | |
| SOCKET_PATCH_BUN_LOCKB_VERSION: ${{ matrix.suite == 'e2e_bun_lockb' && matrix.bun || '' }} | |
| SOCKET_PATCH_BUN_LOCKB_EXTENDED: ${{ matrix.suite == 'e2e_bun_lockb' && '1' || '' }} | |
| SOCKET_PATCH_BUN_LOCKB_PRODUCTION: ${{ matrix.suite == 'e2e_bun_lockb' && '1' || '' }} | |
| # The same fail-instead-of-skip + pinned-release gates for every | |
| # other real-toolchain capstone. All EMPTY on legs that do not set | |
| # the matching matrix key, which the suites treat as unset. | |
| SOCKET_PATCH_NPM_E2E_REQUIRED: ${{ matrix.npm_required || '' }} | |
| SOCKET_PATCH_CARGO_E2E_REQUIRED: ${{ matrix.suite == 'e2e_safety_cargo_build' && '1' || '' }} | |
| SOCKET_PATCH_UV_E2E_REQUIRED: ${{ matrix.uv != '' && '1' || '' }} | |
| SOCKET_PATCH_UV_E2E_VERSION: ${{ matrix.uv }} | |
| SOCKET_PATCH_POETRY_E2E_REQUIRED: ${{ matrix.poetry != '' && '1' || '' }} | |
| SOCKET_PATCH_POETRY_E2E_VERSION: ${{ matrix.poetry }} | |
| SOCKET_PATCH_PDM_E2E_REQUIRED: ${{ matrix.pdm != '' && '1' || '' }} | |
| SOCKET_PATCH_PDM_E2E_VERSION: ${{ matrix.pdm }} | |
| SOCKET_PATCH_HATCH_E2E_REQUIRED: ${{ matrix.hatch != '' && '1' || '' }} | |
| SOCKET_PATCH_HATCH_E2E_VERSION: ${{ matrix.hatch }} | |
| SOCKET_PATCH_PIPENV_E2E_REQUIRED: ${{ matrix.pipenv != '' && '1' || '' }} | |
| SOCKET_PATCH_PIPENV_E2E_VERSIONS: ${{ matrix.pipenv }} | |
| SOCKET_PATCH_PIP_E2E_REQUIRED: ${{ matrix.pip != '' && '1' || '' }} | |
| SOCKET_PATCH_PIP_E2E_VERSIONS: ${{ matrix.pip }} | |
| SOCKET_PATCH_BUNDLER_E2E_REQUIRED: ${{ matrix.bundler != '' && '1' || '' }} | |
| SOCKET_PATCH_BUNDLER_E2E_VERSION: ${{ matrix.bundler }} | |
| SOCKET_PATCH_COMPOSER_E2E_REQUIRED: ${{ matrix.composer != '' && '1' || '' }} | |
| SOCKET_PATCH_COMPOSER_E2E_VERSION: ${{ matrix.composer }} | |
| SOCKET_PATCH_MAVEN_E2E_REQUIRED: ${{ matrix.maven != '' && '1' || '' }} | |
| SOCKET_PATCH_MAVEN_E2E_VERSION: ${{ matrix.maven }} | |
| SOCKET_PATCH_DOTNET_E2E_REQUIRED: ${{ matrix.dotnet != '' && '1' || '' }} | |
| SOCKET_PATCH_DOTNET_E2E_VERSION: ${{ matrix.dotnet }} | |
| SOCKET_PATCH_DENO_E2E_REQUIRED: ${{ matrix.deno != '' && '1' || '' }} | |
| SOCKET_PATCH_DENO_E2E_VERSION: ${{ matrix.deno }} | |
| run: cargo test -p socket-patch-cli --all-features --test ${{ matrix.suite }} -- ${{ matrix.test_filter || '--ignored' }} | |
| - name: Run vlt e2e tests | |
| if: matrix.vlt != '' | |
| # One capstone binary per row, through the leg checker: it fails on | |
| # `0 passed`, a crashed binary, a missing `ran`, an unexpected skip or | |
| # an unknown leg (crates/socket-patch-cli/tests/vlt-leg-manifest.json). | |
| shell: bash | |
| env: | |
| SOCKET_PATCH_VLT_E2E_REQUIRED: ${{ matrix.vlt != '' && '1' || '' }} | |
| VLT_SUITE: ${{ matrix.suite }} | |
| VLT_TEST_FILTER: ${{ matrix.test_filter }} | |
| run: | | |
| set -uo pipefail | |
| status=0 | |
| # shellcheck disable=SC2086 # the filter is several libtest arguments | |
| cargo test -p socket-patch-cli --all-features --test "$VLT_SUITE" -- $VLT_TEST_FILTER 2>&1 | tee vlt-leg.log || status=1 | |
| py=$(command -v python3 || command -v python) | |
| "$py" scripts/check-vlt-legs.py --manifest crates/socket-patch-cli/tests/vlt-leg-manifest.json vlt-leg.log || status=1 | |
| exit "$status" | |
| # ---------------------------------------------------------------------- | |
| # 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. | |
| # | |
| # Triggered on every PR. 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: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 35 | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ecosystem: [npm, pypi, gem, cargo, golang, maven, composer, nuget, deno] | |
| 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: rustup show | |
| # No `actions/cache` here intentionally. This job builds Docker | |
| # images and would be flagged by zizmor's cache-poisoning audit. | |
| - 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: 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="" | |
| 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" ;; | |
| esac | |
| # shellcheck disable=SC2086 # EXTRA is intentionally word-split | |
| cargo test -p socket-patch-cli --features docker-e2e --test "docker_e2e_${{ matrix.ecosystem }}" $EXTRA | |
| # ---------------------------------------------------------------------- | |
| # 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 }} | |
| needs: test | |
| 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: rustup show | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| key: yarn-classic-${{ matrix.release }} | |
| 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 }}) | |
| needs: test | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # 4.0.2 bare-hex checksum writer (4.0.0-4.0.2), 4.1.0 first | |
| # `10c0/` writer, then a spread up to current. | |
| os: [ubuntu-latest] | |
| yarn: ['4.0.2', '4.1.0', '4.6.0', '4.12.0', '4.18.0'] | |
| include: | |
| - {os: macos-latest, yarn: '4.12.0'} | |
| - {os: windows-latest, yarn: '4.12.0'} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| run: rustup show | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| key: yarn-berry-${{ matrix.yarn }} | |
| 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" | |
| cargo-vex-matrix: | |
| name: cargo ${{ matrix.toolchain }} lock-v${{ matrix.lock || 'own' }} (${{ matrix.os }}) | |
| needs: test | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 40 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # 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. | |
| os: [ubuntu-latest] | |
| toolchain: ['1.82.0', '1.93.1', 'stable'] | |
| lock: ['', '1', '2', '3', '4'] | |
| include: | |
| - {os: macos-latest, toolchain: stable, lock: '1'} | |
| - {os: windows-latest, toolchain: stable, lock: '1'} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| run: rustup show | |
| - 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: rustup toolchain install "$CARGO_TEST_TOOLCHAIN" --profile minimal | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| key: cargo-vex-${{ matrix.toolchain }} | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Real-cargo hosted/vendored + manifest-less VEX | |
| 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 | |
| cargo test -p socket-patch-cli --test e2e_redirect_cargo_build --test e2e_redirect_cargo_shapes --test e2e_vendor_cargo_build --test mode_migration_cargo | |
| cargo test -p socket-patch-cli --test e2e_safety_cargo_build -- --ignored | |
| # 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]) | |
| needs: test | |
| 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: rustup show | |
| - name: Pull the old cargos under test | |
| 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: | |
| key: cargo-old-toolchains | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Vendored manifest [patch] on old cargo | |
| 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 | |
| # ---------------------------------------------------------------------- | |
| # Experimental `setup`-flow matrix (NON-BLOCKING). | |
| # | |
| # For each ecosystem/package manager, drives the full intended flow — | |
| # prepare deps + a committed patch set, run `socket-patch setup`, run | |
| # the native install, check whether the patch was applied — plus the | |
| # negative controls (no setup, empty/wrong/alt patch sets). See | |
| # tests/setup_matrix/ and scripts/setup-matrix.sh. | |
| # | |
| # This is EXPERIMENTAL and intentionally not required to pass yet: | |
| # `setup` configures install hooks for npm, PyPI, Bundler and Composer | |
| # only, so the other ecosystems' `baseline_with_setup` cases are | |
| # EXPECTED to fail (a baseline of what `setup` must eventually support). `continue-on-error: true` | |
| # means this job never blocks a PR — it must ALSO be left OUT of the | |
| # repo's required status checks (configured in the branch-protection | |
| # UI, not in this file). The orchestrator exits non-zero only on a | |
| # *regression* vs the recorded baseline; the full per-case result set | |
| # is uploaded as a JSON artifact for inspection. | |
| # ---------------------------------------------------------------------- | |
| setup-matrix: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| continue-on-error: true | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ecosystem: [npm, pypi, cargo, gem, golang, maven, composer, nuget, deno] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Docker Buildx | |
| # `driver: docker` — the per-ecosystem image's `FROM | |
| # socket-patch-test-base:latest` only resolves when buildx talks | |
| # directly to the host docker daemon (see e2e-docker above). | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 | |
| with: | |
| driver: docker | |
| - name: Install Rust | |
| run: rustup show | |
| - 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: 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 }} setup-matrix | |
| run: scripts/setup-matrix.sh run --ecosystem ${{ matrix.ecosystem }} --out "report-${{ matrix.ecosystem }}.json" | |
| - name: Upload ${{ matrix.ecosystem }} setup-matrix report | |
| if: always() | |
| uses: ./.github/actions/upload-artifact | |
| with: | |
| name: setup-matrix-${{ matrix.ecosystem }} | |
| path: report-${{ matrix.ecosystem }}.json | |
| # ---------------------------------------------------------------------- | |
| # Hosted-mode production e2e — REQUIRED status check, 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 (this job is registered in branch protection as a required | |
| # check named exactly `hosted-e2e`): | |
| # * 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 # registered in branch protection; do not rename | |
| 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 ref rather than one per push. | |
| group: hosted-e2e-${{ 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: rustup show | |
| - name: Cache cargo | |
| if: steps.gate.outputs.run == 'true' | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| key: hosted-e2e | |
| 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: python -m pip install --disable-pip-version-check 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 |