|
|
""" |
|
|
Tests for agent lazy loading service |
|
|
""" |
|
|
|
|
|
import asyncio |
|
|
import pytest |
|
|
from datetime import datetime, timedelta |
|
|
from unittest.mock import Mock, patch, AsyncMock |
|
|
|
|
|
from src.services.agent_lazy_loader import ( |
|
|
AgentLazyLoader, |
|
|
AgentMetadata, |
|
|
agent_lazy_loader |
|
|
) |
|
|
from src.agents.deodoro import BaseAgent |
|
|
from src.core.exceptions import AgentExecutionError |
|
|
|
|
|
|
|
|
class MockAgent(BaseAgent): |
|
|
"""Mock agent for testing.""" |
|
|
|
|
|
def __init__(self, name: str = "MockAgent"): |
|
|
super().__init__() |
|
|
self.name = name |
|
|
self.initialized = False |
|
|
|
|
|
async def initialize(self): |
|
|
self.initialized = True |
|
|
|
|
|
async def process(self, *args, **kwargs): |
|
|
return {"result": "mock"} |
|
|
|
|
|
|
|
|
class TestAgentLazyLoader: |
|
|
"""Test agent lazy loader functionality.""" |
|
|
|
|
|
@pytest.fixture |
|
|
async def lazy_loader(self): |
|
|
"""Create a lazy loader instance.""" |
|
|
loader = AgentLazyLoader( |
|
|
unload_after_minutes=1, |
|
|
max_loaded_agents=3 |
|
|
) |
|
|
await loader.start() |
|
|
yield loader |
|
|
await loader.stop() |
|
|
|
|
|
async def test_register_agent(self, lazy_loader): |
|
|
"""Test agent registration.""" |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="TestAgent", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description="Test agent", |
|
|
capabilities=["testing"], |
|
|
priority=5, |
|
|
preload=False |
|
|
) |
|
|
|
|
|
|
|
|
assert "TestAgent" in lazy_loader._registry |
|
|
metadata = lazy_loader._registry["TestAgent"] |
|
|
assert metadata.name == "TestAgent" |
|
|
assert metadata.module_path == "tests.unit.test_agent_lazy_loader" |
|
|
assert metadata.class_name == "MockAgent" |
|
|
assert metadata.description == "Test agent" |
|
|
assert metadata.capabilities == ["testing"] |
|
|
assert metadata.priority == 5 |
|
|
assert metadata.preload is False |
|
|
|
|
|
async def test_get_agent_class_lazy_load(self, lazy_loader): |
|
|
"""Test lazy loading of agent class.""" |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="TestAgent", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description="Test agent", |
|
|
capabilities=["testing"] |
|
|
) |
|
|
|
|
|
|
|
|
agent_class = await lazy_loader.get_agent_class("TestAgent") |
|
|
|
|
|
|
|
|
assert agent_class == MockAgent |
|
|
assert lazy_loader._stats["cache_misses"] == 1 |
|
|
assert lazy_loader._stats["total_loads"] == 1 |
|
|
|
|
|
|
|
|
agent_class2 = await lazy_loader.get_agent_class("TestAgent") |
|
|
assert agent_class2 == MockAgent |
|
|
assert lazy_loader._stats["cache_hits"] == 1 |
|
|
|
|
|
async def test_create_agent_instance(self, lazy_loader): |
|
|
"""Test creating agent instance.""" |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="TestAgent", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description="Test agent", |
|
|
capabilities=["testing"] |
|
|
) |
|
|
|
|
|
|
|
|
agent = await lazy_loader.create_agent("TestAgent") |
|
|
|
|
|
|
|
|
assert isinstance(agent, MockAgent) |
|
|
assert agent.initialized |
|
|
assert len(lazy_loader._instances) == 1 |
|
|
|
|
|
async def test_agent_not_found(self, lazy_loader): |
|
|
"""Test error when agent not found.""" |
|
|
with pytest.raises(AgentExecutionError) as exc_info: |
|
|
await lazy_loader.get_agent_class("NonExistentAgent") |
|
|
|
|
|
assert "not registered" in str(exc_info.value) |
|
|
|
|
|
async def test_preload_agents(self, lazy_loader): |
|
|
"""Test preloading of high-priority agents.""" |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="HighPriority", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description="High priority agent", |
|
|
capabilities=["high"], |
|
|
priority=10, |
|
|
preload=True |
|
|
) |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="LowPriority", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description="Low priority agent", |
|
|
capabilities=["low"], |
|
|
priority=1, |
|
|
preload=False |
|
|
) |
|
|
|
|
|
|
|
|
await lazy_loader._preload_agents() |
|
|
|
|
|
|
|
|
assert lazy_loader._registry["HighPriority"].loaded_class is not None |
|
|
assert lazy_loader._registry["LowPriority"].loaded_class is None |
|
|
|
|
|
async def test_memory_pressure_unloading(self, lazy_loader): |
|
|
"""Test unloading agents under memory pressure.""" |
|
|
lazy_loader.max_loaded_agents = 2 |
|
|
|
|
|
|
|
|
for i in range(3): |
|
|
lazy_loader.register_agent( |
|
|
name=f"Agent{i}", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description=f"Agent {i}", |
|
|
capabilities=[f"cap{i}"], |
|
|
preload=False |
|
|
) |
|
|
|
|
|
|
|
|
await lazy_loader.get_agent_class(f"Agent{i}") |
|
|
await asyncio.sleep(0.1) |
|
|
|
|
|
|
|
|
loaded_count = sum( |
|
|
1 for m in lazy_loader._registry.values() |
|
|
if m.loaded_class |
|
|
) |
|
|
assert loaded_count <= 2 |
|
|
|
|
|
async def test_cleanup_unused_agents(self, lazy_loader): |
|
|
"""Test cleanup of unused agents.""" |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="UnusedAgent", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description="Unused agent", |
|
|
capabilities=["unused"], |
|
|
preload=False |
|
|
) |
|
|
|
|
|
await lazy_loader.get_agent_class("UnusedAgent") |
|
|
|
|
|
|
|
|
metadata = lazy_loader._registry["UnusedAgent"] |
|
|
metadata.last_used = datetime.now() - timedelta(minutes=2) |
|
|
|
|
|
|
|
|
await lazy_loader._cleanup_unused_agents() |
|
|
|
|
|
|
|
|
assert metadata.loaded_class is None |
|
|
assert lazy_loader._stats["total_unloads"] == 1 |
|
|
|
|
|
async def test_get_available_agents(self, lazy_loader): |
|
|
"""Test getting available agents list.""" |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="Agent1", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description="Agent 1", |
|
|
capabilities=["cap1"], |
|
|
priority=10 |
|
|
) |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="Agent2", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description="Agent 2", |
|
|
capabilities=["cap2"], |
|
|
priority=5 |
|
|
) |
|
|
|
|
|
|
|
|
agents = lazy_loader.get_available_agents() |
|
|
|
|
|
|
|
|
assert len(agents) >= 2 |
|
|
assert agents[0]["priority"] >= agents[1]["priority"] |
|
|
|
|
|
|
|
|
agent1 = next(a for a in agents if a["name"] == "Agent1") |
|
|
assert agent1["description"] == "Agent 1" |
|
|
assert agent1["capabilities"] == ["cap1"] |
|
|
assert agent1["priority"] == 10 |
|
|
|
|
|
async def test_get_stats(self, lazy_loader): |
|
|
"""Test getting loader statistics.""" |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="StatsAgent", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description="Stats agent", |
|
|
capabilities=["stats"] |
|
|
) |
|
|
|
|
|
await lazy_loader.get_agent_class("StatsAgent") |
|
|
await lazy_loader.create_agent("StatsAgent") |
|
|
|
|
|
|
|
|
stats = lazy_loader.get_stats() |
|
|
|
|
|
|
|
|
assert stats["total_agents"] >= 1 |
|
|
assert stats["loaded_agents"] >= 1 |
|
|
assert stats["active_instances"] >= 1 |
|
|
assert stats["statistics"]["total_loads"] >= 1 |
|
|
assert stats["memory_usage"]["max_loaded_agents"] == 3 |
|
|
|
|
|
async def test_unload_with_active_instances(self, lazy_loader): |
|
|
"""Test that agents with active instances are not unloaded.""" |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="ActiveAgent", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="MockAgent", |
|
|
description="Active agent", |
|
|
capabilities=["active"] |
|
|
) |
|
|
|
|
|
|
|
|
agent = await lazy_loader.create_agent("ActiveAgent") |
|
|
|
|
|
|
|
|
metadata = lazy_loader._registry["ActiveAgent"] |
|
|
await lazy_loader._unload_agent(metadata) |
|
|
|
|
|
|
|
|
assert metadata.loaded_class is not None |
|
|
|
|
|
async def test_invalid_module_path(self, lazy_loader): |
|
|
"""Test loading agent with invalid module path.""" |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="InvalidAgent", |
|
|
module_path="invalid.module.path", |
|
|
class_name="MockAgent", |
|
|
description="Invalid agent", |
|
|
capabilities=["invalid"] |
|
|
) |
|
|
|
|
|
|
|
|
with pytest.raises(AgentExecutionError) as exc_info: |
|
|
await lazy_loader.get_agent_class("InvalidAgent") |
|
|
|
|
|
assert "Failed to load agent" in str(exc_info.value) |
|
|
|
|
|
async def test_invalid_class_name(self, lazy_loader): |
|
|
"""Test loading agent with invalid class name.""" |
|
|
|
|
|
lazy_loader.register_agent( |
|
|
name="InvalidClass", |
|
|
module_path="tests.unit.test_agent_lazy_loader", |
|
|
class_name="NonExistentClass", |
|
|
description="Invalid class", |
|
|
capabilities=["invalid"] |
|
|
) |
|
|
|
|
|
|
|
|
with pytest.raises(AgentExecutionError): |
|
|
await lazy_loader.get_agent_class("InvalidClass") |
|
|
|
|
|
|
|
|
@pytest.mark.asyncio |
|
|
async def test_global_lazy_loader(): |
|
|
"""Test global lazy loader instance.""" |
|
|
from src.services.agent_lazy_loader import get_agent_lazy_loader |
|
|
|
|
|
|
|
|
loader = await get_agent_lazy_loader() |
|
|
|
|
|
|
|
|
assert loader._running |
|
|
|
|
|
|
|
|
loader2 = await get_agent_lazy_loader() |
|
|
assert loader is loader2 |
|
|
|
|
|
|
|
|
await loader.stop() |