Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/bun-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@ jobs:

- name: Cache cargo
# save-if keeps writes on main so open PRs do not churn the repo's
# 10 GiB cache budget; rust-cache's automatic key already includes
# the runner OS, so one logical key serves all three builds.
# 10 GiB cache budget. Share dependency artifacts with the other
# debug=0 E2E builders on the same runner image.
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: bun-native
shared-key: e2e-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}

- name: Build CLI
Expand Down Expand Up @@ -390,7 +390,7 @@ jobs:
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
# Keep binaries linked against different glibc versions separate.
key: bun-native-binary-${{ matrix.os }}
shared-key: e2e-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}

- name: Setup Python
Expand Down
202 changes: 136 additions & 66 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .github/workflows/composer-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ jobs:
- run: rustup show
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: composer-compat
shared-key: dev-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/go-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
- run: rustup show
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: go-compat
shared-key: dev-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/gradle-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ jobs:
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: gradle-compat
shared-key: e2e-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}

- name: Check the Gradle test-name prefixes
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pdm-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: pdm-compat
shared-key: dev-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Compile the CLI and the capstone once
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pipenv-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: pipenv-compat
shared-key: dev-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/poetry-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: poetry-compat
shared-key: dev-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- run: cargo build --locked -p socket-patch-cli
- uses: ./.github/actions/upload-artifact
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sbt-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ jobs:
- run: rustup show
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: sbt-compat
shared-key: dev-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/vlt-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ jobs:
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: vlt-e2e
shared-key: e2e-${{ matrix.os }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Compile the CLI and the vlt capstones once
shell: bash
Expand Down
104 changes: 104 additions & 0 deletions scripts/tests/test_ci_scheduling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Keep CI's required verdict complete while scheduling each OS independently."""

import importlib.util
import re
import unittest
from pathlib import Path


ROOT = Path(__file__).parents[2]
spec = importlib.util.spec_from_file_location("ci_rows", Path(__file__).with_name("test_ci_vlt_rows.py"))
reader = importlib.util.module_from_spec(spec)
spec.loader.exec_module(reader)
TEXT = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8")
JOBS = reader.jobs(TEXT)


def dependencies(job):
for line in JOBS[job]:
match = re.match(r"^ needs: (.*)$", line)
if match:
return {s.strip() for s in match[1].strip("[]").split(",")}
return set()


def ancestors(job, visiting=()):
if job in visiting:
raise AssertionError(f"dependency cycle: {visiting + (job,)}")
parents = dependencies(job)
return parents | {parent for dep in parents for parent in ancestors(dep, visiting + (job,))}


class Scheduling(unittest.TestCase):
def test_required_verdict_includes_every_job(self):
# A successful aggregate must never hide a failed OS builder/consumer
# introduced when a matrix is split into independently scheduled jobs.
self.assertEqual(dependencies("ci-ok"), set(JOBS) - {"ci-ok"})
self.assertIn(" if: always()", JOBS["ci-ok"])
self.assertIn('if v["result"] not in ("success", "skipped")', "\n".join(JOBS["ci-ok"]))
for job in JOBS:
ancestors(job) # All dependencies exist and the graph is acyclic.

def test_expensive_jobs_cannot_start_after_a_failed_preflight(self):
cheap = {"clippy", "lint-ecosystems", "release-readiness", "dispatch-tests", "ci-ok"}
for job in set(JOBS) - cheap:
with self.subTest(job=job):
self.assertIn("clippy", ancestors(job))
# Job-level always() would defeat failure/skip propagation.
self.assertFalse(any(line.startswith(" if:") and "always()" in line
for line in JOBS[job]))
self.assertIn(" if: github.event.pull_request.draft != true", JOBS["clippy"])
preflight = "\n".join(JOBS["clippy"])
self.assertIn("cargo check --locked --workspace --all-targets --all-features", preflight)

def test_consumers_wait_only_for_their_own_os_build(self):
for family in ("e2e", "cargo-vex-matrix"):
for suffix, os_name in (("", "ubuntu-latest"), ("-windows", "windows-latest"),
("-macos", "macos-latest"), ("-full", "ubuntu-latest")):
job = family + suffix
builder = "e2e-build" + (suffix if suffix in ("-windows", "-macos") else "")
with self.subTest(job=job):
self.assertEqual(dependencies(job), {builder})
self.assertEqual({row["os"] for row in reader.matrix_include(JOBS[job])}, {os_name})
self.assertIn(f" os: [{os_name}]", JOBS[builder])
self.assertEqual(ancestors(job), {builder, "clippy"})

def test_os_builds_use_the_same_artifact_contract(self):
producer = "\n".join(JOBS["e2e-build"])
self.assertIn(" steps: &e2e-build-steps", producer)
self.assertIn("--all-features --tests --no-run", producer)
self.assertIn("name: e2e-bin-${{ matrix.os }}", producer)
self.assertIn("--os \"$BUNDLE_OS\"", producer)
for suffix in ("windows", "macos"):
self.assertIn(" steps: *e2e-build-steps", JOBS[f"e2e-build-{suffix}"])
self.assertIn(" env: *e2e-build-env", JOBS[f"e2e-build-{suffix}"])
for family in ("e2e", "cargo-vex-matrix"):
self.assertIn("pattern: e2e-bin-${{ matrix.os }}*", "\n".join(JOBS[family]))
for job in ("e2e-build-macos", "e2e-macos", "cargo-vex-matrix-macos", "yarn-berry-e2e-macos"):
self.assertIn(" if: github.event_name != 'pull_request'", JOBS[job])

def test_row_reader_preserves_both_os_siblings(self):
jobs = reader.jobs("""jobs:
example:
strategy:
matrix:
include:
- {os: ubuntu-latest, suite: linux_only}
example-windows:
strategy:
matrix:
include:
- {os: windows-latest, suite: windows_only}
example-macos:
strategy:
matrix:
include:
- {os: macos-latest, suite: macos_only}
""")
rows = reader.job_rows(jobs, "example")
self.assertEqual(len(rows), 3)
self.assertEqual({r["suite"] for r in rows}, {"linux_only", "windows_only", "macos_only"})


if __name__ == "__main__":
unittest.main()
9 changes: 5 additions & 4 deletions scripts/tests/test_ci_vlt_rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,12 @@ def matrix_include(job_lines):


def job_rows(jobs_by_id, job):
"""`matrix_include` of a job plus its `<job>-macos` sibling, which holds
the macOS rows that run off the pull_request path."""
"""All rows of a job family, including its independent OS siblings."""
rows = matrix_include(jobs_by_id[job])
if f"{job}-macos" in jobs_by_id:
rows += matrix_include(jobs_by_id[f"{job}-macos"])
for os_name in ("windows", "macos"):
sibling = f"{job}-{os_name}"
if sibling in jobs_by_id:
rows += matrix_include(jobs_by_id[sibling])
return rows


Expand Down
Loading