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)"
    )