import datetime as dt import unittest from pathlib import Path from tools.ci import nightly from tools.ci import nightly_set as ns NOW = "2026-10-28T12:00:00+00:00" PATTERNS = ns.guarded_patterns((Path(__file__).resolve().parents[1] / ".github/CODEOWNERS").read_text()) def pr(n, head="h", state="open", files=("crates/horizon-world/src/lib.rs",), base="main"): return ns.PullRequest(n, state, f"{head}{n}", f"Fix {n}", f"dev{n}", base, list(files)) def lookup(*prs): known = {p.number: p for p in prs} return lambda n: known.get(n) def run(state, text, login="kacpherek", *prs): return ns.apply(state, ns.parse(text), login, NOW, lookup(*prs), PATTERNS) T = dt.datetime(2026, 10, 28, 12, 0, tzinfo=dt.timezone.utc) def ref(name, sha, hours): return nightly.Ref(name, sha, T + dt.timedelta(hours=hours)) class NightlyTests(unittest.TestCase): def test_the_newest_union_newer_than_main_is_built(self): main = ref("main", "m", 0) unions = [ref("union/a", "a", 1), ref("union/b", "b", 3), ref("union/c", "c", -2)] self.assertEqual(nightly.choose(main, unions, contains=lambda a, b: False).name, "union/b") def test_main_when_no_union_is_newer(self): main = ref("main", "m", 5) unions = [ref("union/a", "a", 1), ref("union/b", "b", 3)] self.assertEqual(nightly.choose(main, unions, contains=lambda a, b: False).name, "main") self.assertEqual(nightly.choose(main, [], contains=lambda a, b: False).name, "main") def test_a_union_main_already_holds_is_skipped(self): main = ref("main", "m", 0) unions = [ref("union/landed", "l", 2), ref("union/open", "o", 1)] landed = lambda a, b: a == "l" # noqa: E731 self.assertEqual(nightly.choose(main, unions, contains=landed).name, "union/open") def test_only_old_dated_nightlies_are_pruned(self): tags = [{"tagName": "nightly", "isPrerelease": True}, {"tagName": "nightly-2026-10-20", "isPrerelease": True}, {"tagName": "nightly-2026-10-21", "isPrerelease": True}, {"tagName": "nightly-2026-10-19", "isPrerelease": False}, {"tagName": "v0.2.80", "isPrerelease": False}, {"tagName": "nightly-2026-10-15-extra", "isPrerelease": True}] self.assertEqual(nightly.stale(tags, dt.date(2026, 10, 28), 7), ["nightly-2026-10-20"]) def test_notes_list_what_is_not_on_main(self): text = nightly.notes("2026-10-28", ref("union/a", "0c0206714abc", 1), [{"number": 2476, "title": "Fix pistons", "headRefOid": "x"}]) self.assertIn("Nightly 2026-10-28 ยท 0c02067", text) self.assertIn("- Fix pistons (#2476)", text) self.assertIn("Everything in it is on main.", nightly.notes("2026-10-28", ref("main", "m" * 40, 0), [])) def test_notes_name_the_commit_and_its_permanent_source_tag(self): sha = "0c0206714abc" + "0" * 28 text = nightly.notes("2026-10-28", ref("union/a", sha, 1), []) self.assertIn(f"Source: commit {sha}, kept for good as the tag `nightly-source/2026-10-28-0c02067`", text) main = nightly.Ref("main", "m" * 40, None) self.assertIn("`nightly-source/2026-10-28-bbbbbbb`", nightly.set_notes("2026-10-28", nightly.Ref("b", "b" * 40, None), main, [], [])) def test_the_source_tag_outlives_every_prune(self): tag = nightly.source_tag("2026-10-20", "a" * 40) self.assertEqual(nightly.stale([{"tagName": tag, "isPrerelease": True}], dt.date(2026, 11, 28), 7), []) self.assertIsNone(nightly.DATED.match(tag)) class ScheduledTests(unittest.TestCase): def test_the_scheduled_nightly_takes_new_heads_and_drops_closed_ones(self): state = run({}, "/nightly 1 2 3 4", "kacpherek", pr(1), pr(2), pr(3), pr(4)).state get = lookup(pr(1, head="new"), pr(2, state="merged"), pr(3, state="closed"), pr(4, files=[".github/workflows/x.yml"])) kept, left_out = nightly.refresh(state["prs"], get, PATTERNS) self.assertEqual([(e["number"], e["head"]) for e in kept], [(1, "new1")]) self.assertEqual(len(left_out), 3) self.assertIn("merged", left_out[0]) def test_notes_list_each_pull_request_and_who_picked_it(self): prs = run({}, "/nightly 1 2", "kacpherek", pr(1), pr(2)).state["prs"] main = nightly.Ref("main", "m" * 40, None) text = nightly.set_notes("2026-10-28", nightly.Ref("nightly/build-2026-10-28-1", "b" * 40, None), main, prs, ["#3 Fix 3: closed, left out"]) self.assertIn("can include pull requests not reviewed yet, picked by trusted reviewers", text) self.assertIn("- Fix 1 (#1) by dev1, at h1, picked by kacpherek", text) self.assertIn("### Left out (1)", text) if __name__ == "__main__": unittest.main() WORKFLOWS = Path(__file__).resolve().parents[1] / ".github" / "workflows" class UntrustedBuildTests(unittest.TestCase): """A nightly can hold unreviewed pull requests: nothing its build leaves may reach a release (game-build.yml).""" def setUp(self): self.game = (WORKFLOWS / "game-build.yml").read_text() def test_only_the_release_is_trusted(self): self.assertIn("trusted: true", (WORKFLOWS / "release.yml").read_text()) nightly_build = (WORKFLOWS / "nightly-build.yml").read_text() self.assertIn("trusted: false", nightly_build) self.assertNotIn("trusted: true", nightly_build) self.assertIn("default: false", self.game.split("trusted:", 1)[1].split("developer_id:", 1)[0]) def test_an_untrusted_build_neither_shares_nor_saves_the_release_cache(self): self.assertIn("shared-key: ${{ inputs.trusted && 'release' || 'nightly' }}-${{ matrix.name }}", self.game) self.assertIn("save-if: ${{ inputs.trusted }}", self.game) self.assertEqual(self.game.count("Swatinem/rust-cache"), 1) def test_an_untrusted_build_never_runs_on_the_release_runner(self): runs_on = [line for line in self.game.splitlines() if "runs-on:" in line] self.assertEqual(len(runs_on), 2) pr_runner = "fromJSON('[\"self-hosted\", \"macOS\", \"horizon-game\"]')" for line in runs_on: self.assertIn(f"inputs.trusted && vars.MAC_RUNNER || {pr_runner}", line) def test_a_trusted_build_never_reads_the_shared_sccache(self): calls = self.game.count("bash tools/ci/runner-env.sh") self.assertEqual(self.game.count("HORIZON_NO_SCCACHE: ${{ inputs.trusted && '1' || '' }}"), calls) env = (Path(__file__).resolve().parents[1] / "tools/ci/runner-env.sh").read_text() self.assertEqual(env.count('-z "${HORIZON_NO_SCCACHE:-}"'), 2) class PruneBuildsTests(unittest.TestCase): TODAY = dt.date(2026, 11, 10) def test_only_old_unpublished_builds_go(self): branches = { "nightly/build-2026-10-27-1": "a", # old: goes "nightly/build-2026-10-27-2": "pub", # old, but a published nightly- points to it: stays "nightly/build-2026-11-02-1": "c", # old enough by one day: goes "nightly/build-2026-11-03-1": "d", # exactly 7 days: stays "nightly/build-2026-11-09-3": "e", # recent: stays } self.assertEqual(nightly.stale_builds(branches, {"pub"}, self.TODAY, 7), ["nightly/build-2026-10-27-1", "nightly/build-2026-11-02-1"]) def test_the_newest_build_always_stays(self): branches = {"nightly/build-2026-10-01-1": "a", "nightly/build-2026-10-01-2": "b"} self.assertEqual(nightly.stale_builds(branches, set(), self.TODAY, 7), ["nightly/build-2026-10-01-1"]) self.assertEqual(nightly.stale_builds({"nightly/build-2026-10-01-1": "a"}, set(), self.TODAY, 7), []) # the newest by number, not by text: -10 is newer than -9 branches = {"nightly/build-2026-10-01-9": "a", "nightly/build-2026-10-01-10": "b"} self.assertEqual(nightly.stale_builds(branches, set(), self.TODAY, 7), ["nightly/build-2026-10-01-9"]) def test_nothing_else_is_touched(self): branches = {"nightly-state": "s", "nightly/build-x": "x", "main": "m", "nightly/build-2026-10-01-1-fix": "f", "feature/nightly/build-2026-10-01-1": "g", "nightly/build-2026-10-02-1": "n"} self.assertEqual(nightly.stale_builds(branches, set(), self.TODAY, 7), []) def test_the_source_is_archived_from_git_signed_attached_and_tagged(self): build = (WORKFLOWS / "nightly-build.yml").read_text() pick = build.split("\n build:", 1)[0] self.assertIn('git archive --format=tar.gz --prefix="horizon-${SHA:0:7}/" -o horizon-source.tar.gz "$SHA"', pick) sign = build.split("\n sign:", 1)[1].split("\n publish:", 1)[0] self.assertIn("dist/horizon-source.tar.gz", sign.split("sign_release.sh nightly", 1)[1]) publish = build.split("\n publish:", 1)[1].split("\n answer:", 1)[0] self.assertIn('"dist/horizon-source.tar.gz#Source of this Nightly', publish) self.assertIn('nightly.py keep-source "$SHA" "$DATE"', publish) def test_the_workflow_prunes_on_schedule_only(self): build = (WORKFLOWS / "nightly-build.yml").read_text() step = build.split("Old nightly/build-* branches deleted", 1)[1].split("- uses:", 1)[0] self.assertIn("if: github.event_name == 'schedule'", step) self.assertIn("prune-builds --days 7", step) self.assertIn("GITHUB_STEP_SUMMARY", step)