183 lines
5.6 KiB
Python
183 lines
5.6 KiB
Python
"""Tests for InProcessBackend: spawn, shutdown, send_message, and contextvars."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from openharness.swarm.in_process import (
|
|
InProcessBackend,
|
|
TeammateContext,
|
|
get_teammate_context,
|
|
set_teammate_context,
|
|
)
|
|
from openharness.swarm.types import TeammateMessage, TeammateSpawnConfig
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fixtures
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.fixture
|
|
def spawn_config():
|
|
return TeammateSpawnConfig(
|
|
name="worker",
|
|
team="test-team",
|
|
prompt="hello",
|
|
cwd="/tmp",
|
|
parent_session_id="sess-001",
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def backend(tmp_path, monkeypatch):
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
return InProcessBackend()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TeammateContext
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_teammate_context_defaults():
|
|
ctx = TeammateContext(
|
|
agent_id="w@t",
|
|
agent_name="w",
|
|
team_name="t",
|
|
)
|
|
assert ctx.color is None
|
|
assert ctx.plan_mode_required is False
|
|
assert not ctx.cancel_event.is_set()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ContextVar get / set
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_get_teammate_context_returns_none_outside_task():
|
|
# Outside any async task, the contextvar should be None
|
|
result = get_teammate_context()
|
|
assert result is None
|
|
|
|
|
|
async def test_set_and_get_teammate_context():
|
|
ctx = TeammateContext(agent_id="x@y", agent_name="x", team_name="y")
|
|
set_teammate_context(ctx)
|
|
assert get_teammate_context() is ctx
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# InProcessBackend.spawn
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
async def test_spawn_returns_success_result(backend, spawn_config):
|
|
result = await backend.spawn(spawn_config)
|
|
assert result.success is True
|
|
assert result.agent_id == "worker@test-team"
|
|
assert result.backend_type == "in_process"
|
|
assert result.task_id.startswith("in_process_")
|
|
|
|
|
|
async def test_spawn_duplicate_returns_failure(backend, spawn_config):
|
|
await backend.spawn(spawn_config)
|
|
# Spawn again while first is still running
|
|
result = await backend.spawn(spawn_config)
|
|
assert result.success is False
|
|
assert result.error is not None
|
|
|
|
|
|
async def test_spawn_creates_active_agent(backend, spawn_config):
|
|
await backend.spawn(spawn_config)
|
|
assert backend.is_active("worker@test-team")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# InProcessBackend.shutdown
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
async def test_shutdown_unknown_agent_returns_false(backend):
|
|
result = await backend.shutdown("nonexistent@team")
|
|
assert result is False
|
|
|
|
|
|
async def test_graceful_shutdown(backend, spawn_config):
|
|
await backend.spawn(spawn_config)
|
|
assert backend.is_active("worker@test-team")
|
|
|
|
result = await backend.shutdown("worker@test-team", timeout=2.0)
|
|
assert result is True
|
|
assert not backend.is_active("worker@test-team")
|
|
|
|
|
|
async def test_force_shutdown(backend, spawn_config):
|
|
await backend.spawn(spawn_config)
|
|
result = await backend.shutdown("worker@test-team", force=True, timeout=2.0)
|
|
assert result is True
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# InProcessBackend.send_message
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
async def test_send_message_writes_to_mailbox(backend, tmp_path, monkeypatch):
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
config = TeammateSpawnConfig(
|
|
name="rcvr",
|
|
team="myteam",
|
|
prompt="wait",
|
|
cwd="/tmp",
|
|
parent_session_id="s",
|
|
)
|
|
await backend.spawn(config)
|
|
|
|
msg = TeammateMessage(text="work on it", from_agent="leader")
|
|
# Should not raise
|
|
await backend.send_message("rcvr@myteam", msg)
|
|
|
|
# Verify the message was written to mailbox
|
|
from openharness.swarm.mailbox import TeammateMailbox
|
|
mailbox = TeammateMailbox(team_name="myteam", agent_id="rcvr")
|
|
messages = await mailbox.read_all(unread_only=False)
|
|
assert any(m.payload.get("content") == "work on it" for m in messages)
|
|
|
|
await backend.shutdown("rcvr@myteam", force=True)
|
|
|
|
|
|
async def test_send_message_invalid_agent_id_raises(backend):
|
|
with pytest.raises(ValueError, match="agentName@teamName"):
|
|
await backend.send_message("no-at-sign", TeammateMessage(text="hi", from_agent="l"))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# active_agents / shutdown_all
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
async def test_active_agents_lists_running(backend, spawn_config):
|
|
await backend.spawn(spawn_config)
|
|
active = backend.active_agents()
|
|
assert "worker@test-team" in active
|
|
|
|
|
|
async def test_shutdown_all(backend, tmp_path, monkeypatch):
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
for name in ("a", "b"):
|
|
cfg = TeammateSpawnConfig(
|
|
name=name,
|
|
team="t",
|
|
prompt="run",
|
|
cwd="/tmp",
|
|
parent_session_id="s",
|
|
)
|
|
await backend.spawn(cfg)
|
|
|
|
await backend.shutdown_all(force=True, timeout=2.0)
|
|
assert backend.active_agents() == []
|