Files
computer-playbook/tests/unit/python/cli/administration/deploy/ci/test_trigger.py
T
kevinveenbirkenbach 1e78a02c34 fix(ci-trigger): resume where the last run left off
The regular line restarted at the head on every retrigger. resume_offset
compared the source run's deploy tokens against a freshly computed
ranking, but every axis rotates with the sweep number and a retrigger is
a new run with a new one: measured against the current 277-row ranking,
2 of 277 tokens matched. The walk stopped on its first row every time,
and the 66 rows past the job budget never deployed at all.

Membership now compares role#variant, the identity the rotation cannot
move, and a row counts as proven only when every job it had was green.
A priority row deploys its whole cross-product, so one role#variant can
carry several verdicts, and a green compose job must not carry a red
swarm one past the line. The offset also inherits the source run's own
offset as its floor rather than falling back to the head.

The priority line drops --include-unrun with it: only the hard failures
and the priority entries the run never deployed belong there. The rows
it never reached are the regular line's turn now that the offset walks.
2026-09-03 13:38:07 +02:00

388 lines
15 KiB
Python

from __future__ import annotations
import io
import subprocess
import unittest
import unittest.mock as mock
from contextlib import redirect_stdout
from typing import ClassVar
from cli.administration.deploy.ci import gh, runs
from cli.administration.deploy.ci.trigger import __main__ as trigger
from cli.meta.ci import matrix
from tests.utils.ci.job_names import deploy_job_name
from tests.utils.ci.run_name import render
from utils.github.variant.pools import DISTROS, FILESYSTEMS
def _job(mode: str, app: str, conclusion: str) -> dict:
return {
"name": deploy_job_name(mode, app, "0,1"),
"status": "completed",
"conclusion": conclusion,
}
def _green(app: str) -> dict:
"""The deploy job that proves the ranking row ``app#0``."""
return {
"name": deploy_job_name("docker", app, "0"),
"status": "completed",
"conclusion": "success",
}
_JOBS = [
_job("docker", "web-app-x", "success"),
_job("swarm", "web-app-x", "failure"),
_job("docker", "web-app-y", "failure"),
_job("swarm", "web-app-y", "success"),
]
_AXES = f"%{DISTROS[0]}"
"""The distro glyph _JOBS carry, spelled back out as ASCII. The filesystem is
deliberately absent: a title states the assigned kind, not the effective one."""
_FAILED_TOKENS = (
f"web-app-x#0,1@swarm+clearnet{_AXES} web-app-y#0,1@compose+clearnet{_AXES}"
)
"""What _JOBS failed at, exactly: x in swarm, y in compose, both on variant
shard 0,1, both on the clearnet and both on the same distro."""
_RUN_URL = "https://github.com/o/r/actions/runs/55" # nocheck: url
_SOURCE_CONFIG = {
"distros": "arch centos",
"mode": "swarm",
"lifecycles": "stable",
"filesystem": "btrfs",
"chunk_gate": "false",
"workspace": "false",
}
_SOURCE_RUN = {"jobs": _JOBS, "displayTitle": render(_SOURCE_CONFIG)}
class TestTriggerMain(unittest.TestCase):
def _run(
self,
argv: list[str],
run: dict | None = None,
inputs: dict[str, str] | None = None,
entries: list[dict] | None = None,
) -> tuple[int, list]:
calls: list[tuple] = []
buf = io.StringIO()
with (
mock.patch.object(gh, "current_branch", return_value="feature/x"),
mock.patch.object(gh, "resolve_repo", return_value="o/r"),
mock.patch.object(runs, "find_last_deploy_run", return_value=run),
mock.patch.object(runs, "inputs_from_jobs", return_value=inputs or {}),
mock.patch.object(matrix, "entries_of", return_value=entries or []),
mock.patch.object(
runs,
"dispatch_workflow",
side_effect=lambda wf, ref, wl="", priority="", config=None, repo=None: (
calls.append((wf, ref, wl, priority, config, repo))
),
),
redirect_stdout(buf),
):
rc = trigger.main(argv)
return rc, calls
def test_default_triggers_all(self) -> None:
rc, calls = self._run([])
self.assertEqual(rc, 0)
self.assertEqual(
calls, [("entry-manual-steer.yml", "feature/x", "__ALL__", "", {}, "o/r")]
)
def test_apps_explicit_list(self) -> None:
rc, calls = self._run(["--apps", "web-app-a web-app-b"])
self.assertEqual(rc, 0)
self.assertEqual(calls[0][2], "web-app-a web-app-b")
self.assertEqual(calls[0][3], "")
def test_failed_total_sends_priority_without_whitelist(self) -> None:
rc, calls = self._run(["--failed"], run={"_jobs": _JOBS})
self.assertEqual(rc, 0)
self.assertEqual(calls[0][2], "")
self.assertEqual(calls[0][3], _FAILED_TOKENS)
def test_a_leftover_scope_argument_no_longer_narrows_the_modes(self) -> None:
for scope in ("swarm", "compose", "total"):
with self.subTest(scope=scope):
_rc, calls = self._run(["--failed", scope], run={"_jobs": _JOBS})
self.assertEqual(calls[0][3], _FAILED_TOKENS)
def test_every_failed_mode_of_one_role_comes_back_separately(self) -> None:
both = [
_job("docker", "web-app-x", "failure"),
_job("swarm", "web-app-x", "failure"),
]
_rc, calls = self._run(["--failed"], run={"_jobs": both})
self.assertEqual(
calls[0][3],
f"web-app-x#0,1@compose+clearnet{_AXES} web-app-x#0,1@swarm+clearnet{_AXES}",
)
def test_never_deployed_priority_roles_join_the_retrigger(self) -> None:
source = {"jobs": _JOBS, "displayTitle": render(_SOURCE_CONFIG)}
calls: list = []
with (
mock.patch.object(gh, "current_branch", return_value="feature/x"),
mock.patch.object(gh, "resolve_repo", return_value="o/r"),
mock.patch.object(gh, "fetch_run", return_value=source),
mock.patch.object(
runs,
"inputs_from_jobs",
return_value={"priority": "web-app-x web-app-never"},
),
mock.patch.object(matrix, "entries_of", return_value=[]),
mock.patch.object(
runs,
"dispatch_workflow",
side_effect=lambda wf, ref, wl="", priority="", config=None, repo=None: (
calls.append(priority)
),
),
redirect_stdout(io.StringIO()),
):
rc = trigger.main(["--failed", "--run", _RUN_URL])
self.assertEqual(rc, 0)
self.assertEqual(
calls[0],
f"web-app-never {_FAILED_TOKENS}",
)
def test_an_unreadable_job_log_aborts_instead_of_dropping_the_priority(
self,
) -> None:
source = {
"jobs": _JOBS,
"displayTitle": render({**_SOURCE_CONFIG, "priority": "web-app-x"}),
}
with (
mock.patch.object(gh, "current_branch", return_value="feature/x"),
mock.patch.object(gh, "resolve_repo", return_value="o/r"),
mock.patch.object(gh, "fetch_run", return_value=source),
mock.patch.object(runs, "inputs_from_jobs", return_value={}),
redirect_stdout(io.StringIO()),
self.assertRaises(SystemExit),
):
trigger.main(["--failed", "--run", _RUN_URL])
def test_a_never_deployed_priority_role_alone_still_dispatches(self) -> None:
green = [
_job("docker", "web-app-x", "success"),
_job("swarm", "web-app-x", "success"),
]
source = {"jobs": green, "displayTitle": render({})}
calls: list = []
with (
mock.patch.object(gh, "current_branch", return_value="feature/x"),
mock.patch.object(gh, "resolve_repo", return_value="o/r"),
mock.patch.object(gh, "fetch_run", return_value=source),
mock.patch.object(
runs,
"inputs_from_jobs",
return_value={"priority": "web-app-x web-app-never"},
),
mock.patch.object(matrix, "entries_of", return_value=[]),
mock.patch.object(
runs,
"dispatch_workflow",
side_effect=lambda wf, ref, wl="", priority="", config=None, repo=None: (
calls.append(priority)
),
),
redirect_stdout(io.StringIO()),
):
rc = trigger.main(["--failed", "--run", _RUN_URL])
self.assertEqual(rc, 0)
self.assertEqual(calls[0], "web-app-never")
_RANKING: ClassVar[list[dict]] = [
{
"apps": f"web-app-{name}",
"variant": "0",
"mode": "compose",
"tor": "false",
"distro": DISTROS[0],
"filesystem": FILESYSTEMS[0],
"priority": "false",
}
for name in ("a", "b", "c")
]
def test_the_carried_offset_is_the_floor_for_the_new_one(self) -> None:
"""Without it the retrigger restarts at the head every time and
redeploys the stretch its predecessors already proved."""
_rc, calls = self._run(
["--failed"],
run={"_jobs": _JOBS},
inputs={"offset": "web-app-b#0"},
entries=self._RANKING,
)
self.assertEqual(calls[0][4]["offset"], "web-app-b#0")
def test_the_green_stretch_behind_the_carried_offset_moves_it_on(self) -> None:
_rc, calls = self._run(
["--failed"],
run={"_jobs": [*_JOBS, _green("web-app-b"), _green("web-app-c")]},
inputs={"offset": "web-app-b#0"},
entries=self._RANKING,
)
self.assertEqual(calls[0][4]["offset"], "web-app-c#0")
def test_failed_nothing_does_not_dispatch(self) -> None:
green = [
_job("docker", "web-app-x", "success"),
_job("swarm", "web-app-x", "success"),
]
rc, calls = self._run(["--failed"], run={"_jobs": green})
self.assertEqual(rc, 0)
self.assertEqual(calls, [])
def test_failed_with_run_url_uses_that_run(self) -> None:
calls: list = []
with (
mock.patch.object(gh, "current_branch", return_value="feature/x"),
mock.patch.object(gh, "resolve_repo", return_value="o/r"),
mock.patch.object(gh, "fetch_run", return_value=_SOURCE_RUN) as fetch,
mock.patch.object(runs, "find_last_deploy_run") as find_last,
mock.patch.object(matrix, "entries_of", return_value=[]),
mock.patch.object(
runs,
"dispatch_workflow",
side_effect=lambda wf, ref, wl="", priority="", config=None, repo=None: (
calls.append((priority, config))
),
),
redirect_stdout(io.StringIO()),
):
rc = trigger.main(["--failed", "--run", _RUN_URL])
self.assertEqual(rc, 0)
self.assertEqual(calls[0], (_FAILED_TOKENS, _SOURCE_CONFIG))
fetch.assert_called_once()
find_last.assert_not_called()
def test_failed_with_bare_run_id_resolves_against_branch_repo(self) -> None:
calls: list = []
with (
mock.patch.object(gh, "current_branch", return_value="feature/x"),
mock.patch.object(gh, "resolve_repo", return_value="o/r"),
mock.patch.object(gh, "fetch_run", return_value=_SOURCE_RUN) as fetch,
mock.patch.object(runs, "find_last_deploy_run") as find_last,
mock.patch.object(matrix, "entries_of", return_value=[]),
mock.patch.object(
runs,
"dispatch_workflow",
side_effect=lambda wf, ref, wl="", priority="", config=None, repo=None: (
calls.append((priority, config))
),
),
redirect_stdout(io.StringIO()),
):
rc = trigger.main(["--failed", "--run", "55"])
self.assertEqual(rc, 0)
self.assertEqual(calls[0], (_FAILED_TOKENS, _SOURCE_CONFIG))
fetch.assert_called_once_with("55", repo="o/r")
find_last.assert_not_called()
def test_apps_with_a_run_reproduces_its_configuration(self) -> None:
calls: list = []
with (
mock.patch.object(gh, "current_branch", return_value="feature/x"),
mock.patch.object(gh, "resolve_repo", return_value="o/r"),
mock.patch.object(gh, "fetch_run", return_value=_SOURCE_RUN),
mock.patch.object(matrix, "entries_of", return_value=[]),
mock.patch.object(
runs,
"dispatch_workflow",
side_effect=lambda wf, ref, wl="", priority="", config=None, repo=None: (
calls.append((wl, priority, config))
),
),
redirect_stdout(io.StringIO()),
):
rc = trigger.main(["--apps", "web-app-a", "--run", _RUN_URL])
self.assertEqual(rc, 0)
self.assertEqual(calls[0], ("web-app-a", "", _SOURCE_CONFIG))
def test_chunk_gate_override_replaces_the_carried_value(self) -> None:
calls: list = []
with (
mock.patch.object(gh, "current_branch", return_value="feature/x"),
mock.patch.object(gh, "resolve_repo", return_value="o/r"),
mock.patch.object(gh, "fetch_run", return_value=_SOURCE_RUN),
mock.patch.object(matrix, "entries_of", return_value=[]),
mock.patch.object(
runs,
"dispatch_workflow",
side_effect=lambda wf, ref, wl="", priority="", config=None, repo=None: (
calls.append(config)
),
),
redirect_stdout(io.StringIO()),
):
rc = trigger.main(["--failed", "--run", _RUN_URL, "--chunk-gate", "true"])
self.assertEqual(rc, 0)
self.assertEqual(calls[0], {**_SOURCE_CONFIG, "chunk_gate": "true"})
def test_chunk_gate_override_without_a_source_run(self) -> None:
rc, calls = self._run(["--chunk-gate", "false"])
self.assertEqual(rc, 0)
self.assertEqual(calls[0][4], {"chunk_gate": "false"})
def test_chunk_gate_rejects_other_values(self) -> None:
with self.assertRaises(SystemExit) as ctx, redirect_stdout(io.StringIO()):
trigger.main(["--chunk-gate", "maybe"])
self.assertEqual(ctx.exception.code, 2)
def test_failed_no_run_found(self) -> None:
rc, calls = self._run(["--failed"], run=None)
self.assertEqual(rc, 1)
self.assertEqual(calls, [])
def test_apps_and_failed_mutually_exclusive(self) -> None:
with self.assertRaises(SystemExit) as ctx, redirect_stdout(io.StringIO()):
trigger.main(["--failed", "--apps", "x"])
self.assertEqual(ctx.exception.code, 2)
class TestBranchRemote(unittest.TestCase):
def _resolve(self, upstream: str | None, push_default: str | None) -> str:
remotes = "fork\norigin"
def fake_run(args: list[str]) -> str:
if args[1] == "rev-parse":
if upstream is None:
raise subprocess.CalledProcessError(128, args)
return upstream
if args[1] == "remote" and len(args) == 2:
return remotes
if args[1] == "config":
if push_default is None:
raise subprocess.CalledProcessError(1, args)
return push_default
raise AssertionError(f"unexpected git call: {args}")
with mock.patch.object(gh, "_run", side_effect=fake_run):
return gh._branch_remote()
def test_tracking_remote_wins(self) -> None:
self.assertEqual(self._resolve("origin/main", "fork"), "origin")
def test_push_default_beats_origin_without_upstream(self) -> None:
self.assertEqual(self._resolve(None, "fork"), "fork")
def test_origin_when_no_upstream_and_no_push_default(self) -> None:
self.assertEqual(self._resolve(None, None), "origin")
def test_unknown_push_default_is_ignored(self) -> None:
self.assertEqual(self._resolve(None, "gone"), "origin")
if __name__ == "__main__":
unittest.main()