annator-command-center / tests /memory_leaks /test_agent_execution_leaks.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 8)
aef804e verified
Raw
History Blame Contribute Delete
13.3 kB
"""
Agent Execution Memory Leak Tests
This module contains memory leak detection tests for agent execution operations.
These tests detect Python-level memory leaks during repeated agent execution using
Bloomberg's memray profiler.
Test Categories:
- Single-agent execution leaks: Memory growth during 100 serial executions
- Concurrent agent execution leaks: Thread-safe memory accumulation detection
- Agent registry operations: Memory leaks during agent registration/updates
Invariants Tested:
- INV-01: Agent execution should not grow memory (>10MB over 100 executions)
- INV-02: Agent execution should not accumulate allocations (>1000 per execution)
- INV-03: Concurrent execution should not leak thread-local memory
- INV-04: Agent registry operations should not leak (registration, updates)
Performance Targets:
- Single-agent execution: <10MB memory growth (100 iterations)
- Concurrent execution: <20MB memory growth (10 threads × 10 iterations)
- Agent registry: <5MB memory growth (100 operations)
Requirements:
- Python 3.11+ (memray requirement)
- memray>=1.12.0 (install with: pip install memray)
Usage:
# Run all agent execution leak tests
pytest backend/tests/memory_leaks/test_agent_execution_leaks.py -v
# Run specific test
pytest backend/tests/memory_leaks/test_agent_execution_leaks.py::test_agent_execution_no_leak -v
# Run with memory leak marker
pytest backend/tests/memory_leaks/ -v -m memory_leak
Phase: 243-01 (Memory & Performance Bug Discovery)
See: .planning/phases/243-memory-and-performance-bug-discovery/243-01-PLAN.md
"""
from typing import Optional
from unittest.mock import Mock, patch
import pytest
# =============================================================================
# Test: Single-Agent Execution Memory Leaks
# =============================================================================
@pytest.mark.memory_leak
@pytest.mark.slow
def test_agent_execution_no_leak(memray_session, check_memory_growth):
"""
Test that agent execution does not leak memory over 100 iterations.
INVARIANT: Agent execution should not grow memory (>10MB over 100 executions)
STRATEGY:
- Execute mock agent 100 times in loop (amplification for leak detection)
- Track memory using memray_session fixture
- Assert <10MB memory growth (threshold from Phase 243-01 requirements)
RADII:
- 100 iterations sufficient to amplify small leaks (1KB/iter → 100KB)
- Detects cumulative leaks from unclosed connections, cache misses
- Based on industry standard for memory leak testing (Google, Meta)
Test Metadata:
- Iterations: 100
- Threshold: 10MB
- Amplification: 100x (small leaks become detectable)
Examples:
>>> # Run test (requires memray)
>>> pytest test_agent_execution_leaks.py::test_agent_execution_no_leak -v
Phase: 243-01
TQ-01 through TQ-05 compliant (invariant-first, documented, clear assertions)
Args:
memray_session: memray.Tracker fixture for memory profiling
check_memory_growth: Helper fixture for asserting memory thresholds
Raises:
AssertionError: If memory growth exceeds 10MB threshold
"""
from core.agent_governance_service import AgentGovernanceService
# Mock database session (avoid real DB operations during memory testing)
db_session = Mock()
governance = AgentGovernanceService(db_session)
# Execute agent 100 times (amplification loop)
for i in range(100):
try:
# Mock agent execution (avoid real LLM calls, network I/O)
with patch.object(governance, 'register_or_update_agent') as mock_exec:
mock_exec.return_value = Mock(
id=f"agent_{i}",
name=f"Test Agent {i}",
status="STUDENT"
)
# Execute agent (testing memory, not functionality)
governance.register_or_update_agent(
name=f"TestAgent{i}",
category="test",
module_path="test.module",
class_name="TestAgent"
)
except Exception as e:
# Log but continue (testing memory, not functionality)
pytest.logger.warning(f"Iteration {i} failed: {e}")
# Assert memory growth <10MB (INV-01)
check_memory_growth(
memray_session,
threshold_mb=10.0,
context_msg="Agent execution (100 iterations)"
)
@pytest.mark.memory_leak
@pytest.mark.slow
def test_agent_execution_allocation_count(memray_session, assert_allocation_count):
"""
Test that agent execution does not accumulate excessive allocations.
INVARIANT: Agent execution should not accumulate allocations (>1000 per execution)
STRATEGY:
- Execute mock agent 100 times in loop
- Track allocation count using memray.Stats
- Assert <1000 allocations per execution
RADII:
- 100 iterations provide statistical significance
- Detects allocation hotspots (object creation, string formatting)
- Based on industry benchmarks (Chrome, Firefox leak detection)
Test Metadata:
- Iterations: 100
- Max allocations: 1000
- Per-execution limit: 10 allocations/iteration
Phase: 243-01
TQ-01 through TQ-05 compliant
Args:
memray_session: memray.Tracker fixture for memory profiling
assert_allocation_count: Helper fixture for allocation thresholds
Raises:
AssertionError: If allocation count exceeds 1000 threshold
"""
from core.agent_governance_service import AgentGovernanceService
# Mock database session
db_session = Mock()
governance = AgentGovernanceService(db_session)
# Execute agent 100 times
for i in range(100):
try:
with patch.object(governance, 'register_or_update_agent') as mock_exec:
mock_exec.return_value = Mock(
id=f"agent_{i}",
name=f"Test Agent {i}",
status="STUDENT"
)
governance.register_or_update_agent(
name=f"TestAgent{i}",
category="test",
module_path="test.module",
class_name="TestAgent"
)
except Exception as e:
pytest.logger.warning(f"Iteration {i} failed: {e}")
# Assert allocation count <1000 (INV-02)
assert_allocation_count(
memray_session,
max_allocations=1000,
context_msg="Agent execution allocation count"
)
# =============================================================================
# Test: Concurrent Agent Execution Memory Leaks
# =============================================================================
@pytest.mark.memory_leak
@pytest.mark.slow
def test_agent_concurrent_execution_leaks(memray_session, check_memory_growth):
"""
Test that concurrent agent execution does not leak thread-local memory.
INVARIANT: Concurrent execution should not leak thread-local memory
STRATEGY:
- Test concurrent agent execution (10 parallel threads, 10 iterations each)
- Detect thread-unsafe memory accumulation (thread-local storage, GIL contention)
- Assert memory growth proportional to concurrency (<20MB for 10×10)
RADII:
- 10 threads × 10 iterations = 100 total executions
- Detects threading leaks (unclosed thread-local connections)
- Based on industry standards (Python threading best practices)
Test Metadata:
- Threads: 10
- Iterations per thread: 10
- Total executions: 100
- Threshold: 20MB (2× single-agent threshold due to concurrency)
Examples:
>>> # Run test (requires memray)
>>> pytest test_agent_execution_leaks.py::test_agent_concurrent_execution_leaks -v
Phase: 243-01
TQ-01 through TQ-05 compliant
Args:
memray_session: memray.Tracker fixture for memory profiling
check_memory_growth: Helper fixture for asserting memory thresholds
Raises:
AssertionError: If memory growth exceeds 20MB threshold
"""
import concurrent.futures
from core.agent_governance_service import AgentGovernanceService
# Mock database session
db_session = Mock()
def execute_agent_thread(thread_id: int, iteration: int) -> None:
"""Execute agent in thread (testing thread-local memory)."""
governance = AgentGovernanceService(db_session)
try:
with patch.object(governance, 'register_or_update_agent') as mock_exec:
mock_exec.return_value = Mock(
id=f"agent_{thread_id}_{iteration}",
name=f"Test Agent {thread_id}-{iteration}",
status="STUDENT"
)
governance.register_or_update_agent(
name=f"TestAgent{thread_id}_{iteration}",
category="test",
module_path="test.module",
class_name="TestAgent"
)
except Exception as e:
pytest.logger.warning(f"Thread {thread_id} iteration {iteration} failed: {e}")
# Execute agents concurrently (10 threads × 10 iterations)
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
futures = []
for thread_id in range(10):
for iteration in range(10):
future = executor.submit(execute_agent_thread, thread_id, iteration)
futures.append(future)
# Wait for all threads to complete
concurrent.futures.wait(futures)
# Assert memory growth <20MB (INV-03)
check_memory_growth(
memray_session,
threshold_mb=20.0,
context_msg="Concurrent agent execution (10 threads × 10 iterations)"
)
# =============================================================================
# Test: Agent Registry Operations Memory Leaks
# =============================================================================
@pytest.mark.memory_leak
@pytest.mark.slow
def test_agent_registry_no_leak(memray_session, check_memory_growth):
"""
Test that agent registry operations do not leak memory.
INVARIANT: Agent registry operations should not leak memory
STRATEGY:
- Perform 100 agent registration operations
- Perform 100 agent update operations
- Assert <5MB memory growth (registry operations are lightweight)
RADII:
- 100 operations provide statistical significance
- Detects cache leaks, query result accumulation
- Registry operations should be memory-efficient (no caching bloat)
Test Metadata:
- Registrations: 100
- Updates: 100
- Total operations: 200
- Threshold: 5MB (lightweight operations)
Phase: 243-01
TQ-01 through TQ-05 compliant
Args:
memray_session: memray.Tracker fixture for memory profiling
check_memory_growth: Helper fixture for asserting memory thresholds
Raises:
AssertionError: If memory growth exceeds 5MB threshold
"""
from core.agent_governance_service import AgentGovernanceService
# Mock database session
db_session = Mock()
governance = AgentGovernanceService(db_session)
# Perform 100 agent registrations
for i in range(100):
try:
with patch.object(db_session, 'query') as mock_query:
mock_query.return_value.filter.return_value.first.return_value = None
with patch.object(db_session, 'add'):
with patch.object(db_session, 'commit'):
governance.register_or_update_agent(
name=f"TestAgent{i}",
category="test",
module_path=f"test.module{i}",
class_name="TestAgent"
)
except Exception as e:
pytest.logger.warning(f"Registration {i} failed: {e}")
# Perform 100 agent updates
for i in range(100):
try:
# Mock existing agent (for update path)
existing_agent = Mock(
id=f"agent_{i}",
name=f"Test Agent {i}",
status="STUDENT"
)
with patch.object(db_session, 'query') as mock_query:
mock_query.return_value.filter.return_value.first.return_value = existing_agent
with patch.object(db_session, 'commit'):
governance.register_or_update_agent(
name=f"UpdatedAgent{i}",
category="test",
module_path=f"test.module{i}",
class_name="TestAgent"
)
except Exception as e:
pytest.logger.warning(f"Update {i} failed: {e}")
# Assert memory growth <5MB (INV-04)
check_memory_growth(
memray_session,
threshold_mb=5.0,
context_msg="Agent registry operations (100 registrations + 100 updates)"
)