Repository navigation
cmux-next: the fast tier runs as a build-fleet client when every macOS label is full or dead #28391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fb35fdf
feeafbd
dc6ebe6
0eeb114
08450ce
31fc489
bafaae2
8a28c62
beb46e2
564a4e8
3be0dd7
0860227
cc11800
40a4d4e
2944b6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -372,13 +372,21 @@ def test_all_full_routes_reserve_only_requested_fleet_jobs(self): | |||||
| {LARGE: 5, SMALL: 10}, {LARGE: 1, SMALL: 1}, cascade.seen(shot, NOW), saturated=True) | ||||||
| self.assertEqual(result.fleet_jobs, ("fast-tier",)) | ||||||
|
|
||||||
| def test_gateway_output_is_reserved_without_wiring_raw_mac_jobs(self): | ||||||
| def test_no_tier_runs_on_the_gateway_itself(self): | ||||||
| """fleet_jobs never sends a whole Mac job (checkout, build) to gateway_runner: a listed tier's | ||||||
| <tier>-fleet client runs there (the checks-workflow guard GatewayRunsOnlyTheFleetClient) | ||||||
| and the tier only reports its verdict.""" | ||||||
| jobs = yaml.safe_load(WORKFLOW.read_text())["jobs"] | ||||||
| self.assertEqual(jobs["macos-placement"]["outputs"]["fleet_jobs"], "${{ steps.place.outputs.fleet_jobs }}") | ||||||
| wiring = Wiring() | ||||||
| for tier in (*CASCADED, *GUI_JOBS): | ||||||
| self.assertNotIn("outputs.fleet_jobs", jobs[tier]["runs-on"]) | ||||||
| place = next(step for step in jobs["macos-placement"]["steps"] if step.get("id") == "place") | ||||||
| self.assertNotIn("FLEET_CLIENT_JOBS", place["env"]) | ||||||
| with self.subTest(tier=tier): | ||||||
| self.assertNotIn("gateway_runner", str(jobs[tier]["runs-on"])) | ||||||
| context = wiring.context() | ||||||
| context["needs"]["macos-placement"]["outputs"].update( | ||||||
| fleet_jobs=f" {tier} ", gateway_runner="gateway-test", placed_attempt="1") | ||||||
| self.assertNotEqual(evaluate(jobs[tier]["runs-on"], context), "gateway-test") | ||||||
|
|
||||||
| def test_gateway_reservation_is_empty_without_requested_jobs(self): | ||||||
| shot = snapshot(minutes_old=1) | ||||||
|
|
@@ -928,7 +936,9 @@ def test_no_job_reads_a_hosted_route(self): | |||||
| self.assertNotIn(f"outputs.{key}", text) | ||||||
| jobs = yaml.safe_load(text)["jobs"] | ||||||
| # The fast tier and the canary keep their own budget: no cold hosted build needs 45 minutes. | ||||||
| self.assertEqual(jobs["fast-tier"]["timeout-minutes"], 20) | ||||||
| # (Five while it only reports its build-fleet client's verdict: GatewayRunsOnlyTheFleetClient.) | ||||||
| fast = jobs["fast-tier"]["timeout-minutes"] | ||||||
| self.assertEqual(evaluate(fast, {"needs": {"fast-tier-fleet": {"result": "skipped"}}}), 20) | ||||||
| canary = jobs["swift-canary"]["timeout-minutes"] | ||||||
| self.assertEqual(evaluate(canary, {"needs": {"swift-canary-fleet": {"outputs": {"verdict": ""}}}}), 20) | ||||||
| self.assertEqual(jobs["cmux-scheme-compile"]["timeout-minutes"], 60) | ||||||
|
|
@@ -2148,6 +2158,64 @@ def test_the_sign_runner_reads_the_same_rule(self): | |||||
| self.assertEqual(sign.dead(snap, NOW), set(cascade.dead(snap, NOW))) | ||||||
|
|
||||||
|
|
||||||
| class FleetClientJobs(unittest.TestCase): | ||||||
| """With every owned and Blacksmith macOS label full or dead, only the jobs that have a build-fleet client | ||||||
| (FLEET_CLIENT_JOBS: cmux-next.yml's <job>-fleet) are listed in fleet_jobs; nothing is while any label | ||||||
| can take the job, and the workflow lists only jobs that have their client.""" | ||||||
|
|
||||||
| LIVE, WAITING = {LARGE: 5, SMALL: 10}, {LARGE: 1, SMALL: 1} | ||||||
|
|
||||||
| def shot(self, **side: object) -> dict: | ||||||
| shot = snapshot(minutes_old=1, large=1, small=1) | ||||||
| for label in (LARGE, SMALL, SIDE): | ||||||
| shot["pools"][label] = {"cap": 1, "running": 1, "queued": 1, | ||||||
| "listening": 0, "held_offline": {"units": 1, "cpu": 0, "livelock": 0}, | ||||||
| "oldest_queued_s": 240, "as_of": "2026-10-19T21:59:00Z"} | ||||||
| shot["pools"][SIDE].update(side) | ||||||
| return shot | ||||||
|
|
||||||
| def place(self, owned=(), shot=None, live=None, **overrides): | ||||||
| return cascade.place(env(**overrides), owned, live or self.LIVE, self.WAITING, | ||||||
| cascade.seen(shot or self.shot(), NOW), saturated=True) | ||||||
|
|
||||||
| def test_only_listed_jobs_take_the_fleet(self): | ||||||
| self.assertEqual(self.place(FLEET_CLIENT_JOBS="fast-tier").fleet_jobs, ("fast-tier",)) | ||||||
| self.assertEqual(self.place(FLEET_CLIENT_JOBS="fast-tier swift-canary").fleet_jobs, | ||||||
| ("fast-tier", "swift-canary")) | ||||||
| self.assertEqual(self.place().fleet_jobs, ()) | ||||||
| self.assertEqual(self.place(FLEET_CLIENT_JOBS="").fleet_jobs, ()) | ||||||
|
|
||||||
| def test_nothing_is_listed_while_a_label_can_take_the_job(self): | ||||||
| # A Blacksmith label under its declared cap. | ||||||
| shot = self.shot() | ||||||
| shot["pools"][LARGE].update(cap=5, running=1) | ||||||
| self.assertEqual(self.place(shot=shot, live={LARGE: 1, SMALL: 10}, FLEET_CLIENT_JOBS="fast-tier").fleet_jobs, | ||||||
| ()) | ||||||
| # The side label has a listener, by a fresh health record (a stale record or snapshot reads full). | ||||||
| fresh = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This fixture does not test the fresh listener case it claims to cover. Because Prompt for AI agents
Suggested change
|
||||||
| listening = self.shot(listening=1, held_offline={"units": 0, "cpu": 0, "livelock": 0}, as_of=fresh) | ||||||
| listening["generated_at"] = fresh | ||||||
| self.assertEqual(self.place(shot=listening, FLEET_CLIENT_JOBS="fast-tier").fleet_jobs, ()) | ||||||
| # No snapshot: nothing is known full. | ||||||
| self.assertEqual(cascade.place(env(FLEET_CLIENT_JOBS="fast-tier"), (), self.LIVE, self.WAITING, None, | ||||||
| saturated=True).fleet_jobs, ()) | ||||||
|
|
||||||
| def test_the_output_is_space_delimited_like_the_blacksmith_lists(self): | ||||||
| with tempfile.TemporaryDirectory() as tmp: | ||||||
| out = Path(tmp) / "out" | ||||||
| cascade.write(self.place(FLEET_CLIENT_JOBS="fast-tier"), {"GITHUB_OUTPUT": str(out)}) | ||||||
| self.assertIn("fleet_jobs= fast-tier \n", out.read_text()) | ||||||
|
|
||||||
| def test_the_workflow_lists_only_jobs_with_a_client(self): | ||||||
| jobs = yaml.safe_load(WORKFLOW.read_text(encoding="utf-8"))["jobs"] | ||||||
| place = next(step for step in jobs["macos-placement"]["steps"] if step.get("id") == "place") | ||||||
| listed = str(place["env"]["FLEET_CLIENT_JOBS"]).split() | ||||||
| self.assertEqual(listed, ["fast-tier"]) | ||||||
| for job in listed: | ||||||
| with self.subTest(job=job): | ||||||
| self.assertIn(job, cascade.CASCADE_JOBS, "only a cascade job is ever left unplaced") | ||||||
| self.assertIn(f"{job}-fleet", jobs, "a listed job needs its <job>-fleet client") | ||||||
|
|
||||||
|
|
||||||
| class GuiOverflow(unittest.TestCase): | ||||||
| """The GUI jobs' Blacksmith leg (swift-test, daemon-test, generated-files): the largest size that starts them now. | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: This exception validates only the hosted branch for a fork's own repository, not a fork PR targeting
manaflow-ai. Require the existing fork-PR gate before allowingvars.LINUX_RUNNER, otherwise this guard can approve a selector that queues fork PRs on self-hosted Linux.Prompt for AI agents