Files
openswarm/backend/tests/test_restart_sentinel.py

55 lines
1.9 KiB
Python

"""Agent self-restart handshake: a workspace's restart.sh touches
.openswarm/restart-requested and the AppRuntimeManager watcher consumes it and
restarts every attached instance of that workspace. This is the only restart
path an agent has (no API token, uvicorn runs without --reload), so pin both
the pickup and the sentinel consumption restart.sh's wait loop depends on."""
import asyncio
import os
import pytest
from backend.apps.outputs import runtime as runtime_mod
from backend.apps.outputs.runtime import AppRuntime, AppRuntimeManager
@pytest.mark.asyncio
async def test_sentinel_restarts_all_attached_instances(tmp_path, monkeypatch):
monkeypatch.setattr(runtime_mod, "RESTART_SENTINEL_POLL_SECONDS", 0.05)
restarted: list[int] = []
async def p_fake_start(self):
return True
async def p_fake_restart(self):
restarted.append(self.instance)
return True
monkeypatch.setattr(AppRuntime, "start", p_fake_start)
monkeypatch.setattr(AppRuntime, "restart", p_fake_restart)
# The stub spawns no process; the watcher only restarts live runtimes, so fake liveness.
monkeypatch.setattr(AppRuntime, "running", property(lambda self: True))
mgr = AppRuntimeManager()
ws = str(tmp_path)
await mgr.attach("ws1", ws, 1)
await mgr.attach("ws1", ws, 2)
os.makedirs(os.path.join(ws, ".openswarm"), exist_ok=True)
sentinel = os.path.join(ws, ".openswarm", "restart-requested")
with open(sentinel, "w", encoding="utf-8") as f:
f.write("")
for _ in range(100):
await asyncio.sleep(0.05)
if len(restarted) >= 2:
break
# Sentinel consumed (restart.sh's wait loop unblocks) and BOTH instances bounced.
assert not os.path.exists(sentinel)
assert sorted(restarted) == [1, 2]
# No sentinel -> no further restarts.
await asyncio.sleep(0.2)
assert sorted(restarted) == [1, 2]
if mgr.restart_watch_task:
mgr.restart_watch_task.cancel()