tests/spa/test_apply_group_settings.py¶
Source from this local checkout, regenerated when the reader rebuilds.
Line links use #L<number>; a GitHub line range opens its first line.
1 """Contract tests for SpaCommander.apply_group_settings — recomposition, generation.2 3 Design sections 3, 5, 9; test matrix T6, T15, T27. The stage is a real vertex4 with one real group and no worker started: what these tests ask is what the5 effective configuration BECOMES, and no process is needed to answer that.6 """7 8 import pytest9 10 from genro_asgi_multiworker_spa.orchestration import GroupHandler, SpaCommander11 12 MEMORY_CEILING = 1_000_00013 14 15 @pytest.fixture16 def commander(tmp_path):17 """A vertex with one group, a profiles folder and the two immutable levels."""18 spa_commander = SpaCommander(19 tmp_path / "frozen_users",20 profiles_path=tmp_path / "profiles",21 recipe_settings={"worker_memory_admission_percent": 70.0, "worker_max_number": 3},22 env_settings={"worker_max_users": 16},23 )24 GroupHandler(25 spa_commander,26 "standard",27 memory_concession_bytes=MEMORY_CEILING,28 worker_memory_admission_percent=70.0,29 worker_max_number=3,30 worker_max_users=16,31 instance_dir=tmp_path / "i",32 frozen_users_path=tmp_path / "frozen_users",33 )34 return spa_commander35 36 37 async def test_recompute_independent_of_previous_profile(commander):38 # wf:contract: T6 — apply(P2) after apply(P1) equals apply(P2) alone, key by39 # wf:contract: key: every apply recomposes defaults ⊕ recipe_settings ⊕40 # wf:contract: profile ⊕ env_settings; a key present in P1 and absent in P241 # wf:contract: falls back to env, recipe or default, in that order (T5 sibling42 # wf:contract: at the HTTP level lives in phase 6).43 first = {"worker_memory_admission_percent": 60.0, "worker_min_life_seconds": 5.0}44 second = {"worker_min_life_seconds": 9.0}45 commander.profile_store.write("p1", first)46 commander.profile_store.write("p2", second)47 48 await commander.apply_group_settings(profile_name="p1", source="reload")49 after_both = await commander.apply_group_settings(profile_name="p2", source="reload")50 51 # The recipe level is back in charge of what P1 had overridden, and the env52 # level still wins over the recipe on its own key.53 assert after_both["effective_settings"]["worker_memory_admission_percent"] == 70.054 assert after_both["effective_settings"]["worker_min_life_seconds"] == 9.055 assert after_both["effective_settings"]["worker_max_users"] == 1656 # A key nobody names anywhere is the dataclass default.57 assert after_both["effective_settings"]["cpu_close_percent"] is None58 59 alone = SpaCommander(60 commander.freeze_handler.root_path,61 profiles_path=commander.profile_store.folder,62 recipe_settings=dict(commander.recipe_settings),63 env_settings=dict(commander.env_settings),64 )65 GroupHandler(66 alone,67 "standard",68 memory_concession_bytes=MEMORY_CEILING,69 instance_dir=commander.configured_group.worker_settings["instance_dir"],70 frozen_users_path=commander.freeze_handler.root_path,71 )72 fresh = await alone.apply_group_settings(profile_name="p2", source="reload")73 74 assert fresh["effective_settings"] == after_both["effective_settings"]75 assert fresh["active_profile"] == "p2"76 assert alone.active_profile == "p2"77 78 79 async def test_derived_setpoint_worker_memory_max_percent(commander):80 # wf:contract: T15 — applying worker_max_number without an explicit81 # wf:contract: worker_memory_max_percent makes the next judgment read82 # wf:contract: quota/N; an explicit value wins; removing the explicit value83 # wf:contract: restores the derivation.84 group = commander.configured_group85 86 await commander.apply_group_settings(profile={"worker_max_number": 4})87 assert group.worker_memory_max_percent == 100.0 / 488 89 await commander.apply_group_settings(90 profile={"worker_max_number": 4, "worker_memory_max_percent": 80.0}91 )92 assert group.worker_memory_max_percent == 80.093 94 await commander.apply_group_settings(profile={"worker_max_number": 4})95 assert group.worker_memory_max_percent == 100.0 / 496 97 98 async def test_generation_advances_on_idempotent_apply(commander):99 # wf:contract: T27 — applying the same profile twice yields empty100 # wf:contract: changed_settings, outcome "applied", and a generation that101 # wf:contract: advances anyway: the audit counts successful attempts, not102 # wf:contract: differences.103 commander.profile_store.write("steady", {"cpu_close_percent": 30.0})104 105 first = await commander.apply_group_settings(profile_name="steady", source="reload")106 second = await commander.apply_group_settings(profile_name="steady", source="reload")107 108 assert first["changed_settings"] == {"cpu_close_percent": 30.0}109 assert second["changed_settings"] == {}110 assert second["outcome"] == "applied"111 assert (first["generation"], second["generation"]) == (2, 3)112 assert commander.configuration_generation == 3113 # Same settings, same fingerprint: the digest says what is in force, not114 # how many times it was asked for.115 assert commander.last_apply["generation"] == 3116 assert commander.last_apply["outcome"] == "applied"117 assert commander.last_apply["digest"] == commander._settings_digest(118 second["effective_settings"]119 )