Files
computer-playbook/tests/unit/python/utils/roles/test_guide.py
T
kevinveenbirkenbachandClaude Opus 5.5 91022ad37a fix(ci): let only the primary app's declared variant bring the guide host
Every role's base services carry `docs.enabled: "{{ 'web-app-docs' in
group_names }}"`, and the static closure counts any "in group_names" flag
as possibly true (is_explicit_truth). A round therefore pulled web-app-docs
in through any member whose active variant did not pin docs off, and a
round hands every role its index or clamps it to variant 0, where 84 roles
declare docs on. The README replay and everything it drags in (dashboard,
matomo, keycloak, prometheus with its MCP partners) landed in 365 of 469
rounds instead of one per role, which ran CI run 40625679270 out of
resources: Listmonk#0 memory, OpenProject#0 I/O in the DR drill,
Nextcloud#0/#2 budget (4310-4539 s in the replay each, 60 s over),
BookWyrm#0 drill disk, Matrix#1/#3 pids in the replay; none of those
primaries declares docs in the failing variant.

The guide host now stays on only where the primary app's own variant
declares it (declares_guide, the predicate instructions.py already used);
every other role has it switched off (guide_off), in the planner's round
overrides and in the swarm include set. The host_vars bake drops a docs
edge whenever the round does not deploy web-app-docs. Measured for
listmonk, openproject, translate and mariadb: web-app-docs is deployed in
exactly each primary's declared guide round, swarm and planner agree in
all 12 rounds, and rounds shed up to ten apps (mariadb#1 19 -> 9).

test_guide_rides_the_primary_only pins that on real role data (it fails
with the rule disabled); test_variant_pin_required leaves the guide host
out of its clone comparison and no longer counts the docs key as a
pairing edge, because the replay no longer follows a partner's index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:49:51 +01:00

109 lines
4.2 KiB
Python

import unittest
import unittest.mock as mock
from typing import ClassVar
from utils.cache.applications import get_variants
from utils.roles.guide import (
declares_guide,
guide_deployable,
guide_off,
guide_variant,
smallest_variant,
)
def _variant(*enabled: str) -> dict:
return {"services": {name: {"enabled": True} for name in enabled}}
class TestDeclaresGuide(unittest.TestCase):
def test_a_literal_true_declares_it(self) -> None:
self.assertTrue(declares_guide({"docs": {"enabled": True}}))
def test_the_group_gated_base_flag_does_not(self) -> None:
self.assertFalse(
declares_guide({"docs": {"enabled": "{{ 'web-app-docs' in group_names }}"}})
)
def test_a_role_without_the_edge_does_not(self) -> None:
self.assertFalse(declares_guide({"postgres": {"enabled": True}}))
class TestGuideOff(unittest.TestCase):
def test_the_edge_is_switched_off_and_its_other_keys_kept(self) -> None:
services = {"docs": {"bond": 0.5, "enabled": True, "shared": True}, "a": {}}
self.assertEqual(
guide_off(services),
{"docs": {"bond": 0.5, "enabled": False, "shared": False}, "a": {}},
)
def test_a_role_without_the_edge_is_left_as_it_is(self) -> None:
self.assertEqual(guide_off({"a": {"enabled": True}}), {"a": {"enabled": True}})
class TestSmallestVariant(unittest.TestCase):
def test_the_variant_with_the_fewest_enabled_services_wins(self) -> None:
variants = [_variant("a", "b", "c"), _variant("a"), _variant("a", "b")]
self.assertEqual(smallest_variant(variants), "1")
def test_a_tie_goes_to_the_lower_index(self) -> None:
self.assertEqual(smallest_variant([_variant("a"), _variant("b")]), "0")
def test_a_disabled_service_does_not_count(self) -> None:
variants = [
{"services": {"a": {"enabled": True}, "b": {"enabled": False}}},
_variant("a", "b"),
]
self.assertEqual(smallest_variant(variants), "0")
def test_a_variant_without_services_counts_as_empty(self) -> None:
self.assertEqual(smallest_variant([_variant("a"), {}]), "1")
def test_a_variant_the_sweep_cuts_hands_the_replay_on(self) -> None:
"""A row no sweep deploys would drop the role from the test entirely."""
variants = [_variant("a"), _variant("a", "b"), _variant("a", "b", "c")]
self.assertEqual(smallest_variant(variants, {"1", "2"}), "1")
def test_a_role_with_no_deployed_variant_carries_nothing(self) -> None:
self.assertEqual(smallest_variant([_variant("a")], set()), "")
class TestGuideVariant(unittest.TestCase):
_VARIANTS: ClassVar[dict] = {"web-app-x": [_variant("a", "b"), _variant("a")]}
_ALL: ClassVar[set] = {"0", "1"}
def test_the_smallest_deployed_variant_carries_the_replay(self) -> None:
with mock.patch("utils.roles.guide.guide_deployable", return_value="compose"):
self.assertEqual(
guide_variant("web-app-x", self._VARIANTS, self._ALL), ("1", "compose")
)
def test_a_role_the_guide_cannot_deploy_carries_nothing(self) -> None:
with mock.patch("utils.roles.guide.guide_deployable", return_value=""):
self.assertEqual(
guide_variant("web-app-x", self._VARIANTS, self._ALL), ("", "")
)
def test_without_variant_data_no_row_is_the_smallest(self) -> None:
self.assertEqual(guide_variant("web-app-x", None, self._ALL), ("", ""))
self.assertEqual(guide_variant("web-app-x", {}, self._ALL), ("", ""))
class TestGuideDeployable(unittest.TestCase):
"""Against the real roles, which is what the marker is computed over."""
def test_a_role_without_a_readme_production_block_is_excluded(self) -> None:
self.assertEqual(guide_deployable("does-not-exist"), "")
def test_the_mode_follows_whether_the_role_ships_a_stack(self) -> None:
self.assertEqual(guide_deployable("web-app-nextcloud"), "compose")
def test_the_replay_reaches_more_roles_than_the_single_random_pick(self) -> None:
"""The job it replaced tested exactly one role per run."""
marked = [app for app in get_variants() if guide_deployable(app)]
self.assertGreater(len(marked), 100)
if __name__ == "__main__":
unittest.main()