Files
computer-playbook/tests/unit/python/utils/github/variant/test_selection.py
T
kevinveenbirkenbachandClaude Opus 5 5b8465260f feat(ci): deploy only the variants a change reaches
An image bump in a provider redeployed every variant of every consumer,
including the variants that switch that provider off and therefore never
deploy the bumped image at all. The diff-derived whitelist now narrows each
affected role to the variants whose own round closure still reaches a seed,
emitted as `role#0,2` selection tokens. That closure is walked on the same
variant-merged services maps the matrix planner builds its rounds from, so the
narrowing follows a parent's variant choice across every generation of the
chain. It covers every auto-derived whitelist: the weekly update PRs,
Dependabot, and any other diff-driven run.

The narrowing never shrinks the matrix on partial information. A seed role
keeps every variant, because its own files changed. A role reached only
through a run_after edge keeps every variant too, because the round closure
does not follow that edge and a role whose hits are empty is therefore
unnarrowed rather than dropped.

A pinned token naming a role the discovery query does not return now warns
instead of aborting the run. The diff-derived whitelist legitimately names
roles the lifecycle envelope filters out, and once it pins variants such a
token would abort every chunk; role names are still checked against roles/
before the run starts, so a typo is caught one layer earlier.

The round's variant merge moves out of the deploy inventory package into
utils/roles/applications/variants.py. The CI selection has to build the same
maps, and importing the deploy package for them pulls in a module that reads
INFINITO_SRC_DIR at import time, which the runner host does not set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 22:51:03 +02:00

210 lines
8.3 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"),
selection.Pin("web-app-a", (0, 2), "swarm", True, "debian", "zfs"),
)
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_the_token_round_trips_through_describe(self) -> None:
token = "web-app-a#0,2@swarm+tor%debian/zfs"
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_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_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")), [])
def test_a_pin_on_an_undiscovered_role_is_tolerated_like_a_bare_one(self) -> None:
self.assertEqual(
selection.apply(_ROWS, selection.parse_list("web-app-c#0,2")), []
)
if __name__ == "__main__":
unittest.main()