File size: 8,651 Bytes
b6ae7b8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""
Benchmarks for Stack 2.9 - Memory Usage Tests
Memory profiling benchmarks.
"""

import pytest
import sys
import gc
from pathlib import Path
from unittest.mock import MagicMock, patch

# Add stack_cli to path
sys.path.insert(0, str(Path(__file__).parent.parent / "stack_cli"))

from stack_cli.agent import StackAgent, create_agent


class TestMemoryUsage:
    """Test memory usage patterns."""

    def test_agent_memory_baseline(self):
        """Test baseline agent memory usage."""
        with patch('stack_cli.context.create_context_manager'):
            agent = StackAgent()
            
            # Should not use excessive memory
            import sys
            agent_size = sys.getsizeof(agent)
            
            # Baseline should be reasonable
            assert agent_size < 10000

    def test_conversation_history_memory(self):
        """Test conversation history memory usage."""
        with patch('stack_cli.context.create_context_manager'):
            with patch('stack_cli.tools.get_tool') as mock_get_tool:
                mock_tool = MagicMock(return_value={"success": True})
                mock_get_tool.return_value = mock_tool
                
                agent = StackAgent()
                
                # Add many conversations
                for i in range(100):
                    agent.process(f"query {i}")
                
                # History should be accessible
                assert len(agent.conversation_history) <= 200  # May truncate

    def test_session_memory_growth(self):
        """Test session memory growth."""
        with patch('stack_cli.context.create_context_manager'):
            agent = StackAgent()
            session = agent.context_manager.session
            
            # Add many operations
            for i in range(100):
                session.add_message("user", f"message {i}" * 10)
                session.add_tool_usage("read", {"success": True})
            
            summary = session.get_summary()
            
            # Summary should be generated correctly
            assert summary["messages_count"] == 100
            assert summary["tools_used_count"] == 100


class TestContextMemory:
    """Test context memory usage."""

    def test_context_manager_memory(self):
        """Test context manager memory footprint."""
        with patch('stack_cli.context.create_context_manager'):
            with patch('stack_cli.context.Path') as mock_path:
                with patch.object(Path, 'exists', return_value=False):
                    from stack_cli.context import ContextManager
                    
                    cm = ContextManager("/tmp/test")
                    
                    import sys
                    cm_size = sys.getsizeof(cm)
                    
                    # Should be reasonable
                    assert cm_size < 5000

    def test_projects_dict_memory(self):
        """Test projects dictionary memory."""
        with patch('stack_cli.context.create_context_manager'):
            with patch('stack_cli.context.Path') as mock_path:
                with patch.object(Path, 'exists', return_value=False):
                    from stack_cli.context import ContextManager
                    
                    cm = ContextManager("/tmp")
                    
                    # Should have empty projects dict initially
                    assert len(cm.projects) == 0


class TestToolMemory:
    """Test tool-related memory usage."""

    def test_tools_dict_memory(self):
        """Test TOOLS dictionary size."""
        from stack_cli.tools import TOOLS
        
        import sys
        tools_size = sys.getsizeof(TOOLS)
        
        # Should be reasonable for dict
        assert tools_size < 10000

    def test_tool_schemas_memory(self):
        """Test tool schemas memory."""
        from stack_cli.tools import get_tool_schemas
        
        schemas = get_tool_schemas()
        
        import sys
        schemas_size = sys.getsizeof(schemas)
        
        # Should be reasonable
        assert schemas_size < 10000


class TestGarbageCollection:
    """Test garbage collection."""

    def test_gc_after_agent_creation(self):
        """Test GC after agent creation."""
        gc.collect()
        
        with patch('stack_cli.context.create_context_manager'):
            agent = StackAgent()
        
        # Force gc
        gc.collect()
        
        # Agent should still work
        assert agent is not None

    def test_gc_after_queries(self):
        """Test GC after many queries."""
        gc.collect()
        
        with patch('stack_cli.context.create_context_manager'):
            with patch('stack_cli.tools.get_tool') as mock_get_tool:
                mock_tool = MagicMock(return_value={"success": True})
                mock_get_tool.return_value = mock_tool
                
                agent = StackAgent()
                
                for i in range(50):
                    agent.process(f"query {i}")
        
        gc.collect()
        
        # Should still work
        assert len(agent.conversation_history) > 0


class TestMemoryLeaks:
    """Test for potential memory leaks."""

    def test_no_leak_in_loop(self):
        """Test no memory leak in processing loop."""
        with patch('stack_cli.context.create_context_manager'):
            with patch('stack_cli.tools.get_tool') as mock_get_tool:
                mock_tool = MagicMock(return_value={"success": True})
                mock_get_tool.return_value = mock_tool
                
                agent = StackAgent()
                
                initial_history_len = len(agent.conversation_history)
                
                # Process many queries
                for i in range(200):
                    agent.process(f"query {i}")
                
                final_history_len = len(agent.conversation_history)
                
                # Should not grow indefinitely (may truncate)
                assert final_history_len <= initial_history_len + 200

    def test_session_cleanup(self):
        """Test session cleanup."""
        with patch('stack_cli.context.create_context_manager'):
            agent = StackAgent()
            session = agent.context_manager.session
            
            # Add data
            for i in range(20):
                session.add_message("user", f"msg {i}")
            
            # Clear should work
            session.messages.clear()
            
            assert len(session.messages) == 0


class TestMemoryEfficiency:
    """Test memory efficiency."""

    def test_response_size(self):
        """Test response object size."""
        with patch('stack_cli.context.create_context_manager'):
            from stack_cli.agent import AgentResponse
            import sys
            
            response = AgentResponse(
                content="test content",
                tool_calls=[],
                confidence=0.9
            )
            
            size = sys.getsizeof(response)
            
            # Should be reasonable
            assert size < 1000

    def test_tool_call_size(self):
        """Test tool call object size."""
        from stack_cli.agent import ToolCall
        import sys
        
        call = ToolCall(
            tool_name="read",
            arguments={"path": "test.py"},
            result={"success": True}
        )
        
        size = sys.getsizeof(call)
        
        # Should be small
        assert size < 500


class TestResourceCleanup:
    """Test resource cleanup."""

    def test_context_cleanup(self):
        """Test context cleanup."""
        with patch('stack_cli.context.create_context_manager'):
            with patch('stack_cli.context.Path') as mock_path:
                with patch.object(Path, 'exists', return_value=False):
                    from stack_cli.context import ContextManager
                    
                    cm = ContextManager("/tmp")
                    
                    # Should have cleanup method or be disposable
                    assert cm is not None

    def test_agent_disposal(self):
        """Test agent can be disposed."""
        with patch('stack_cli.context.create_context_manager'):
            agent = StackAgent()
            
            # Should be able to reference agent
            assert agent is not None
            
            # Should be able to create new one
            agent2 = StackAgent()
            assert agent2 is not None


if __name__ == "__main__":
    pytest.main([__file__, "-v"])