Files
computer-playbook/tests/unit/python/utils/github/variant/test_selection.py
T
kevinveenbirkenbachandClaude Opus 5 2a7455bd90 feat(ci): spread the deploy matrix over amd64 and arm64
Both architectures were claimed to work and only one was ever exercised. The
deploy matrix now carries an architecture the way it carries mode, tor, distro
and filesystem, so every run proves both.

A job cannot ask for an architecture the way it asks for a distro, because the
architecture is the machine rather than something the job installs on it. The
matrix therefore carries a runner label and `runs-on` reads it, and an
architecture without a label in RUNNERS is a build error rather than a row that
lands on the wrong hardware and still reports green. assert_architecture.sh
checks the runner it actually got, because a label that stops resolving to
arm64 would otherwise go unnoticed forever.

Assignment is rotation, not chance: two architectures against five distros and
fifteen distro/filesystem positions are coprime enough that every combination
comes round. A role narrows the pool through `architectures` in its
meta/services.yml; without one it takes whatever the rotation hands it. An
empty intersection aborts instead of silently removing the role from the run.

Images are built per architecture and pushed under an architecture-suffixed
tag, then merged into the plain tag by manifest_all.sh; pushing the plain tag
from both builds would let the last one win and send half the rows to the wrong
platform. cleanup.sh now protects the `ci-<sha>-<architecture>` children of a
protected tag, because the plain tag is an index over them and deleting a child
leaves it unpullable on that platform.

label.py is split out of axes.py, which the 500-line limit no longer fits.
Writing a job title and reading one back belong together: a retrigger that
recovers an axis wrongly replays a different deploy than the one that failed,
and a drifting artifact name makes the failure reporter download nothing. The
leading `.*` in LABEL_RE is greedy on purpose so it anchors on the last mode
glyph; a reusable-workflow caller path carries a glyph of its own, and
test_selection.py guards the case.

Verified: 313 unit tests and 48 subtests across utils/github/variant,
utils/roles and cli/meta/ci. Locally, a deploy pinned to arm64 reaches ok=1444
and its image builds log aarch64-linux-gnu paths.

Not verified here: actionlint over the seven changed workflows, and a full
`make test`. The matrix doubles the row count, so runtime and runner
availability change for every CI run from here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 07:02:44 +02:00

236 lines
9.4 KiB
Python

from __future__ import annotations
import unittest
from utils.github.variant import selection
from utils.symbol_glossary import to_emoji
def _row(name: str, variant: int) -> dict:
return {"name": name, "variant": variant}
class TestParseAscii(unittest.TestCase):
def test_a_bare_role_pins_nothing(self) -> None:
pin = selection.parse("web-app-a")
self.assertEqual(pin, selection.Pin("web-app-a"))
self.assertFalse(pin.pinned)
def test_every_axis_is_read(self) -> None:
self.assertEqual(
selection.parse("web-app-a#0,2@swarm+tor%debian/zfs:arm64"),
selection.Pin("web-app-a", (0, 2), "swarm", True, "debian", "zfs", "arm64"),
)
def test_the_distro_and_filesystem_are_optional_like_the_rest(self) -> None:
pin = selection.parse("web-app-a@swarm")
self.assertIsNone(pin.distro)
self.assertIsNone(pin.filesystem)
def test_a_filesystem_alone_still_counts_as_a_narrowing(self) -> None:
pin = selection.parse("web-app-a/ext4")
self.assertEqual(pin.filesystem, "ext4")
self.assertTrue(pin.pinned)
def test_an_unknown_distro_aborts(self) -> None:
with self.assertRaises(SystemExit):
selection.parse("web-app-a%debain")
def test_an_unknown_filesystem_aborts(self) -> None:
with self.assertRaises(SystemExit):
selection.parse("web-app-a/xfs")
def test_an_architecture_alone_still_counts_as_a_narrowing(self) -> None:
pin = selection.parse("web-app-a:arm64")
self.assertEqual(pin.architecture, "arm64")
self.assertTrue(pin.pinned)
def test_an_unknown_architecture_aborts(self) -> None:
with self.assertRaises(SystemExit):
selection.parse("web-app-a:riscv64")
def test_the_token_round_trips_through_describe(self) -> None:
token = "web-app-a#0,2@swarm+tor%debian/zfs:arm64"
self.assertEqual(selection.describe(selection.parse(token)), token)
def test_clearnet_is_the_other_onion_state(self) -> None:
self.assertFalse(selection.parse("web-app-a@compose+clearnet").tor)
def test_a_role_id_ending_in_tor_is_not_read_as_an_axis(self) -> None:
self.assertEqual(selection.parse("svc-net-tor"), selection.Pin("svc-net-tor"))
def test_an_unknown_mode_aborts(self) -> None:
with self.assertRaises(SystemExit):
selection.parse("web-app-a@swrm")
def test_an_unknown_onion_state_aborts(self) -> None:
with self.assertRaises(SystemExit):
selection.parse("web-app-a+onion")
def test_an_unparsable_token_aborts(self) -> None:
with self.assertRaises(SystemExit):
selection.parse("web-app-a#zwei")
class TestParseLabel(unittest.TestCase):
def _label(self, mode: str, tor: str) -> str:
return f"{to_emoji(mode)}{to_emoji(tor)}web-app-a#2"
def test_the_glyphs_carry_mode_and_onion(self) -> None:
self.assertEqual(
selection.parse(self._label("swarm", "tor")),
selection.Pin("web-app-a", (2,), "swarm", True),
)
def test_the_clearnet_glyph_pins_the_clearnet_state(self) -> None:
self.assertFalse(selection.parse(self._label("compose", "clearnet")).tor)
def test_the_priority_star_is_not_part_of_the_name(self) -> None:
pasted = f"{self._label('compose', 'tor')} {to_emoji('priority')}"
self.assertEqual(selection.parse(pasted).app, "web-app-a")
def test_a_host_label_pins_the_mode_only(self) -> None:
pasted = f"{to_emoji('host')}{to_emoji('test_host')}web-app-a"
self.assertEqual(
selection.parse(pasted), selection.Pin("web-app-a", (), "host")
)
def test_the_distro_and_filesystem_glyphs_are_read_too(self) -> None:
pasted = (
f"{to_emoji('swarm')}{to_emoji('tor')}"
f"{to_emoji('fedora')}{to_emoji('btrfs')}web-app-a#2"
)
self.assertEqual(
selection.parse(pasted),
selection.Pin("web-app-a", (2,), "swarm", True, "fedora", "btrfs"),
)
def test_the_architecture_glyph_is_read_too(self) -> None:
pasted = (
f"{to_emoji('swarm')}{to_emoji('tor')}{to_emoji('fedora')}"
f"{to_emoji('btrfs')}{to_emoji('arm64')}web-app-a#2"
)
self.assertEqual(
selection.parse(pasted),
selection.Pin("web-app-a", (2,), "swarm", True, "fedora", "btrfs", "arm64"),
)
def test_a_title_pasted_with_its_caller_path_aborts_rather_than_guessing(
self,
) -> None:
"""``/`` opens the filesystem axis, so a pasted caller path must abort
loudly instead of splitting the token somewhere inside the path."""
pasted = "🎶 Orchestrate CI / test-deploy-chunk-1 / " + self._label(
"swarm", "tor"
)
with self.assertRaises(SystemExit):
selection.parse(pasted)
def test_the_two_spellings_may_not_contradict_each_other(self) -> None:
with self.assertRaises(SystemExit):
selection.parse(f"{to_emoji('swarm')}web-app-a@compose")
class TestNames(unittest.TestCase):
def test_the_query_filters_on_role_ids_only(self) -> None:
pins = selection.parse_list("web-app-a#0@swarm web-app-b+tor")
self.assertEqual(selection.names(pins), "web-app-a web-app-b")
def test_a_role_pinned_twice_is_named_once(self) -> None:
pins = selection.parse_list("web-app-a#0 web-app-a#1")
self.assertEqual(selection.names(pins), "web-app-a")
_ROWS = [_row("web-app-a", 0), _row("web-app-a", 1), _row("web-app-b", 0)]
class TestApply(unittest.TestCase):
def test_no_selection_keeps_every_row_untouched(self) -> None:
self.assertEqual(selection.apply(_ROWS, []), _ROWS)
def test_a_pinned_variant_drops_the_other_variants(self) -> None:
kept = selection.apply(_ROWS, selection.parse_list("web-app-a#1"))
self.assertEqual([row["variant"] for row in kept], [1])
def test_the_pinned_axes_ride_along_on_the_row(self) -> None:
kept = selection.apply(_ROWS, selection.parse_list("web-app-a#0@swarm+tor"))
self.assertEqual(kept[0]["pin_mode"], "swarm")
self.assertTrue(kept[0]["pin_tor"])
def test_an_unpinned_row_carries_open_axes(self) -> None:
kept = selection.apply(_ROWS, selection.parse_list("web-app-b"))
self.assertEqual([kept[0]["pin_mode"], kept[0]["pin_tor"]], [None, None])
self.assertEqual(
[kept[0]["pin_distro"], kept[0]["pin_filesystem"]], [None, None]
)
def test_the_pinned_distro_and_filesystem_ride_along_too(self) -> None:
kept = selection.apply(_ROWS, selection.parse_list("web-app-a#0%debian/zfs"))
self.assertEqual(kept[0]["pin_distro"], "debian")
self.assertEqual(kept[0]["pin_filesystem"], "zfs")
def test_the_pinned_architecture_rides_along_too(self) -> None:
kept = selection.apply(_ROWS, selection.parse_list("web-app-a#0:arm64"))
self.assertEqual(kept[0]["pin_architecture"], "arm64")
def test_one_variant_that_failed_on_two_architectures_comes_back_twice(
self,
) -> None:
kept = selection.apply(
_ROWS, selection.parse_list("web-app-a#0:amd64 web-app-a#0:arm64")
)
self.assertEqual([row["pin_architecture"] for row in kept], ["amd64", "arm64"])
def test_one_variant_that_failed_on_two_distros_comes_back_twice(self) -> None:
kept = selection.apply(
_ROWS, selection.parse_list("web-app-a#0%debian web-app-a#0%fedora")
)
self.assertEqual([row["pin_distro"] for row in kept], ["debian", "fedora"])
def test_one_variant_that_failed_in_two_modes_comes_back_twice(self) -> None:
kept = selection.apply(
_ROWS,
selection.parse_list("web-app-a#0@compose+tor web-app-a#0@swarm+tor"),
)
self.assertEqual(
[(row["variant"], row["pin_mode"], row["pin_tor"]) for row in kept],
[(0, "compose", True), (0, "swarm", True)],
)
def test_the_same_narrowing_written_twice_stays_one_deploy(self) -> None:
kept = selection.apply(
_ROWS,
selection.parse_list("web-app-a#0@swarm+tor web-app-a#0@swarm+tor"),
)
self.assertEqual(len(kept), 1)
def test_a_bare_role_loses_to_a_token_that_narrows_the_same_row(self) -> None:
kept = selection.apply(
_ROWS, selection.parse_list("web-app-a web-app-a#0@swarm+tor")
)
self.assertEqual(
[(row["variant"], row["pin_mode"]) for row in kept],
[(0, "swarm"), (1, None)],
)
def test_two_pins_on_one_role_keep_their_own_axes(self) -> None:
kept = selection.apply(
_ROWS, selection.parse_list("web-app-a#0@swarm web-app-a#1@compose")
)
self.assertEqual([row["pin_mode"] for row in kept], ["swarm", "compose"])
def test_the_query_order_survives_the_selection(self) -> None:
kept = selection.apply(_ROWS, selection.parse_list("web-app-b web-app-a"))
self.assertEqual([row["name"] for row in kept], [row["name"] for row in _ROWS])
def test_a_pin_that_matches_nothing_aborts_instead_of_running_empty(self) -> None:
with self.assertRaises(SystemExit):
selection.apply(_ROWS, selection.parse_list("web-app-b#7"))
def test_a_bare_role_that_matches_nothing_is_tolerated(self) -> None:
self.assertEqual(selection.apply(_ROWS, selection.parse_list("web-app-c")), [])
if __name__ == "__main__":
unittest.main()