File size: 13,252 Bytes
aef804e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | """
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)"
)
|