Verify sequential raid requests in budget scenario

This commit is contained in:
2026-11-03 10:31:44 +03:00
parent 692ae77af7
commit a41a4d17cf
3 changed files with 47 additions and 11 deletions
+19 -5
View File
@@ -262,17 +262,31 @@ expect.feedback = "Тор-М2 развёрнут (integrated, multi)"
command = "airdef deploy s400"
expect.feedback = "С-400 развёрнут (integrated, multi)"
# Two maximum supported waves are issued before simulation advances, so
# their 160 airborne units overlap while 64 defensive systems track them.
[[step]]
command = "airdef wave mix 80 4000"
# The next raid can only be ordered after the first plan is complete.
[[step]]
command = "airdef wave kh101 80 4000"
[[step]]
ticks = 450
frames = 225
expect.chat = ["Противник готовит налёт: ищет площадки, местность подгружается", "Площадок за пределами загруженной карты: 1: оттуда пуск без площадки, с высоты полёта", "ВОЗДУШНАЯ ТРЕВОГА: налёт, 80 целей (один сектор)"]
[[step]]
command = "airdef wave kh101 80 4000"
expect.chat = []
[[step]]
ticks = 600
frames = 300
expect.chat = ["Противник готовит налёт: ищет площадки, местность подгружается", "Площадок за пределами загруженной карты: 1: оттуда пуск без площадки, с высоты полёта", "ВОЗДУШНАЯ ТРЕВОГА: налёт, 80 целей (один сектор)"]
[[step]]
command = "airdef stats"
expect.feedback = "В воздухе: 12, ракет: 0, ПВО: 64, сбито: 0, поражений: 0, разбились о препятствия: 0; удары по ПУ: пущено 0, уничтожено 0, подавлено 0, промахов 0, сбито ПВО 0; мир: 0 секций (0 КБ), запросов 652, неизвестно 0, лучей 5642 (ударов 0, неизв. 5642), клеток 5642; взрывы в блоках: 0 (подтверждено 0, без перемен 0, нет чанка 0), чужих ответов 0"
[[step]]
ticks = 400
frames = 200
[[step]]
command = "airdef clear"
+13 -6
View File
@@ -167,10 +167,10 @@ def test_budgets():
"scenario": "tests/budgets/wasm_budget.toml",
"status": "failed",
"exit_code": None,
"scenario_counts": {"scripts": 1, "steps": None, "raids": 2,
"max_units_per_raid": 80, "raid_units": 160,
"air_defense_units": None, "layout_unit_limit": None,
"frames": 300, "ticks": 602},
"scenario_counts": {"scripts": 1, "steps": None, "raid_requests": None,
"raid_units_requested": None, "air_defense_units": None,
"layout_unit_limit": None, "frames": None, "ticks": None,
"accepted_raids_observed": None, "peak_airborne_observed": None},
"runtime_limits": {"source": "horizon/crates/horizon-mods/src/runtime.rs",
"metering": "source_configured; measured_per_scenario: unavailable"},
}
@@ -206,8 +206,15 @@ def test_budgets():
wasmtime = re.search(r'\[\[package\]\]\s*name = "wasmtime"\s*version = "([^"]+)"', lock_text)
report["wasmtime_version"] = wasmtime.group(1) if wasmtime else None
fixture = ROOT / "airdef-mod" / "tests" / "budgets" / "wasm_budget.toml"
report["scenario_counts"]["steps"] = len(re.findall(r"(?m)^\[\[step\]\]$", fixture.read_text()))
report["scenario_counts"]["air_defense_units"] = len(re.findall(r'(?m)^command = "airdef deploy [^"]+"', fixture.read_text()))
fixture_text = fixture.read_text()
counts = report["scenario_counts"]
counts["steps"] = len(re.findall(r"(?m)^\[\[step\]\]$", fixture_text))
counts["air_defense_units"] = len(re.findall(r'(?m)^command = "airdef deploy [^"]+"', fixture_text))
requests = re.findall(r'(?m)^command = "airdef wave \S+ (\d+)(?: [^"]*)?"', fixture_text)
counts["raid_requests"] = len(requests)
counts["raid_units_requested"] = sum(map(int, requests))
for field in ("ticks", "frames"):
counts[field] = sum(map(int, re.findall(rf"(?m)^{field}\s*=\s*(\d+)\s*$", fixture_text)))
report["scenario_sha256"] = hashlib.sha256(fixture.read_bytes()).hexdigest()
mod()
+15
View File
@@ -94,6 +94,21 @@ class BudgetReportFailureTests(unittest.TestCase):
self.assertEqual(report["status"], "failed")
return report
def test_counts_are_requests_and_derived_from_fixture(self):
(self.mod_root / "tests/budgets/wasm_budget.toml").write_text(
'[[step]]\ncommand = "airdef deploy s400"\n'
'[[step]]\ncommand = "airdef wave kh101 80 4000"\n'
'[[step]]\nticks = 450\nframes = 225\n'
'[[step]]\ncommand = "airdef wave mix 40 1500"\n'
)
with mock.patch.object(airdef, "mod", side_effect=SystemExit("stop after provenance")):
counts = self.invoke_and_require_failure()["scenario_counts"]
self.assertEqual((counts["steps"], counts["air_defense_units"]), (4, 1))
self.assertEqual((counts["raid_requests"], counts["raid_units_requested"]), (2, 120))
self.assertEqual((counts["ticks"], counts["frames"]), (450, 225))
self.assertIsNone(counts["accepted_raids_observed"])
self.assertIsNone(counts["peak_airborne_observed"])
def test_early_system_exit_writes_report_and_skips_backend(self):
backend = mock.Mock()
with mock.patch.object(airdef, "mod", side_effect=SystemExit("synthetic build failure")), \