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
10 changes: 9 additions & 1 deletion scripts/ci/nightly_build_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

import datetime as dt
import os
import re
import sys
import time
from pathlib import Path
Expand All @@ -67,8 +68,15 @@
# a refusal or a stuck queue there is re-run as attempt 2, which takes Blacksmith. The DEV compile
# slot (glaeda-dev-*) is not one, so it is never offered: runs 40446901975 and 40448164753 died at
# "Set up runner" on glaeda-dev-std-xcode-26.6 and nothing re-ran them.
# glaeda's nightly slot (glaeda#1684, runner.nightlySlot): one runner per mini whose label no PR job asks for and
# whose hook takes only nightly jobs, ahead of every job waiting on that mini. A build queued on the shared side label
# waited behind every PR job queued there first (2026-10-27 14:08Z: run 40518198747 was never acquired on the light
# side label). main's rescue watches it (owned_pool_rescue.LANE_LABEL), so vars.CI_NIGHTLY_NEXT_BUILD_RUNNER may name it.
NIGHTLY_SLOT_LABEL = re.compile(r"glaeda-nightly-(?:xl|std|light)-xcode-[0-9]+(?:\.[0-9]+)*")


def owned_label(label: str) -> bool:
return pool.persistent(label)
return pool.persistent(label) or bool(NIGHTLY_SLOT_LABEL.fullmatch(label or ""))


# How long a run waits for the dedicated builder while it is busy, and how often it looks. Its
Expand Down
7 changes: 4 additions & 3 deletions scripts/ci/runner_label_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,11 @@ def nightly_next_build_reason(label: str) -> str | None:
nightly.yml's nightly-next app build reads it first, for every event and
attempt. It may name an owned side or dev label (glaeda-side-<class>-... or
glaeda-dev-<class>-...), the same trust as CI_SIDE_LANE_RUNNER, which that
build already takes. Anything else is held to the workflow policy. Empty is
fine (off).
build already takes, or glaeda's nightly slot (glaeda-nightly-<class>-...,
glaeda#1684): one runner per owned mini whose hook admits only nightly jobs.
Anything else is held to the workflow policy. Empty is fine (off).
"""
for prefix in (SIDE_LANE_PREFIX, "glaeda-dev-"):
for prefix in (SIDE_LANE_PREFIX, "glaeda-dev-", "glaeda-nightly-"):
if label.startswith(prefix) and _owned_pattern().fullmatch("glaeda-" + label[len(prefix):]):
return None
return forbidden_reason(label)
Expand Down
18 changes: 18 additions & 0 deletions tests/test_nightly_build_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@ def test_a_retry_unread_runners_or_a_non_owned_label_picks_no_owned_mac(self) ->
with self.subTest(label=label):
self.assertEqual(picker.owned_route(label, idle, "1")[0], "")

def test_the_nightly_slot_comes_first_while_idle(self) -> None:
# vars.CI_NIGHTLY_NEXT_BUILD_RUNNER names glaeda's nightly slot, then the side label: an idle slot takes the
# build, and a busy one leaves it to the side label, never a wait.
slot = "glaeda-nightly-std-xcode-26.6"
self.assertTrue(picker.owned_label(slot))
for label in ("glaeda-nightly", "glaeda-nightly-std", "glaeda-nightlyx-std-xcode-26.6"):
self.assertFalse(picker.owned_label(label), label)
labels = f"{slot} {SIDE}"

def route(runners: list) -> str: # attempt 1, with or without owned_route's attempt argument
try:
return picker.owned_route(labels, runners, "1")[0]
except TypeError:
return picker.owned_route(labels, runners)[0]

self.assertEqual(route([runner(SIDE), runner(slot)]), slot)
self.assertEqual(route([runner(SIDE), runner(slot, busy=True)]), SIDE)

def test_a_label_the_rescue_does_not_watch_is_never_offered(self) -> None:
# The DEV slot: a refusal there is never re-run (runs 40446901975, 40448164753).
sys.path.insert(0, str(ROOT / "scripts" / "ci"))
Expand Down
6 changes: 4 additions & 2 deletions tests/test_runner_label_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,12 @@ def test_every_runner_variable_a_workflow_reads_is_reported(self) -> None:
class NightlyNextBuildVariable(unittest.TestCase):
def test_an_owned_side_or_dev_label_or_blacksmith_is_allowed(self) -> None:
name = "CI_NIGHTLY_NEXT_BUILD_RUNNER"
for label in ("glaeda-dev-std-xcode-26.6", "glaeda-side-std-xcode-26.6", "blacksmith-6vcpu-macos-26", ""):
for label in ("glaeda-dev-std-xcode-26.6", "glaeda-side-std-xcode-26.6", "glaeda-nightly-std-xcode-26.6",
"blacksmith-6vcpu-macos-26", ""):
with self.subTest(label=label):
self.assertEqual(drifted_runner_variables({name: label}), [])
for label in ("glaeda-std-xcode-26.6", "glaeda-runner-cmux7s-mac-mini-nightly-next", "self-hosted"):
for label in ("glaeda-std-xcode-26.6", "glaeda-runner-cmux7s-mac-mini-nightly-next", "self-hosted",
"glaeda-nightly-nonsense", "glaeda-nightly-std"):
with self.subTest(label=label):
self.assertTrue(drifted_runner_variables({name: label}))

Expand Down
Loading