mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
62 lines
2.1 KiB
Python
62 lines
2.1 KiB
Python
"""The generic Board Program orchestrator loop — replaces
|
|||
|
|
``_roadmap_engine_loop`` / ``_x_feature_spotlight_loop`` (see
|
||
|
|
tests/unit/services/test_board_program_engine.py for the engine's own
|
||
|
|
trigger/dedup/LEARN coverage; this file covers the orchestrator-loop shell
|
||
|
|
only: interval computation + the sleep/tick/heartbeat wiring).
|
||
|
|
|
||
|
|
Unlike the two collapsed loops, ``_board_program_loop`` has no single static
|
||
|
|
disablement gate — each program's enablement is checked per-tick inside
|
||
|
|
``BoardProgramEngine``, DB-backed — so there is no "returns immediately when
|
||
|
|
disabled" behavior to test here; that guarantee now lives in
|
||
|
|
test_board_program_engine.py's disabled-program coverage.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import Any
|
||
|
|
from unittest.mock import AsyncMock, patch
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from roboco.runtime.orchestrator import AgentOrchestrator
|
||
|
|
|
||
|
|
TWO_TICKS = 2
|
||
|
|
ONE_HOUR_SECONDS = 3600
|
||
|
|
|
||
|
|
|
||
|
|
def _orch() -> Any:
|
||
|
|
"""Bypass __init__ — the loop helper under test needs only the
|
||
|
|
heartbeats dict and ``_running``."""
|
||
|
|
o = AgentOrchestrator.__new__(AgentOrchestrator)
|
||
|
|
o._loop_heartbeats = {}
|
||
|
|
o._running = True
|
||
|
|
return o
|
||
|
|
|
||
|
|
|
||
|
|
def test_board_program_interval_is_shortest_registered_cadence_capped() -> None:
|
||
|
|
orch = _orch()
|
||
|
|
interval = orch._board_program_interval_seconds()
|
||
|
|
# x_feature's 1-day default is the shortest of the two registered
|
||
|
|
# programs, well above the 300s floor — capped at the 3600s ceiling.
|
||
|
|
assert interval == ONE_HOUR_SECONDS
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_board_program_loop_one_tick_exception_does_not_crash_loop() -> None:
|
||
|
|
"""A raising cycle is logged and the loop keeps ticking — mirrors every
|
||
|
|
other engine loop's ``except Exception: logger.exception(...)`` shape."""
|
||
|
|
orch = _orch()
|
||
|
|
calls = {"n": 0}
|
||
|
|
|
||
|
|
async def _cycle() -> None:
|
||
|
|
calls["n"] += 1
|
||
|
|
if calls["n"] >= TWO_TICKS:
|
||
|
|
orch._running = False
|
||
|
|
raise RuntimeError("boom")
|
||
|
|
|
||
|
|
orch._run_board_program_cycle = AsyncMock(side_effect=_cycle)
|
||
|
|
|
||
|
|
with patch("asyncio.sleep", new=AsyncMock()):
|
||
|
|
await orch._board_program_loop()
|
||
|
|
|
||
|
|
assert calls["n"] == TWO_TICKS
|