-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_index_progress.py
More file actions
213 lines (162 loc) · 7.28 KB
/
Copy pathtest_index_progress.py
File metadata and controls
213 lines (162 loc) · 7.28 KB
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
"""
Тесты для системы отслеживания прогресса индексации.
"""
import inspect
import tempfile
import time
from pathlib import Path
from unittest.mock import MagicMock
class TestProgressCallback:
"""Тесты для progress callback механизма."""
def test_callback_updates_progress(self):
"""Callback обновляет _last_progress."""
from src.mcp.server import _create_progress_callback, _last_progress, _progress_lock
# Очищаем перед тестом
with _progress_lock:
_last_progress.clear()
cb = _create_progress_callback("test_project")
cb("file.py", 5, 10, "scanning")
with _progress_lock:
assert "test_project" in _last_progress
assert _last_progress["test_project"]["files_done"] == 5
assert _last_progress["test_project"]["files_total"] == 10
assert _last_progress["test_project"]["percent"] == 50.0
def test_callback_complete_phase(self):
"""Callback с phase=complete устанавливает 100%."""
from src.mcp.server import _create_progress_callback, _last_progress, _progress_lock
with _progress_lock:
_last_progress.clear()
cb = _create_progress_callback("test_project")
cb("file.py", 10, 10, "complete")
with _progress_lock:
assert _last_progress["test_project"]["percent"] == 100.0
assert _last_progress["test_project"]["phase"] == "complete"
def test_callback_handles_zero_total(self):
"""Callback не падает при total=0."""
from src.mcp.server import _create_progress_callback, _last_progress, _progress_lock
with _progress_lock:
_last_progress.clear()
cb = _create_progress_callback("test_project")
cb("file.py", 0, 0, "scanning")
with _progress_lock:
assert _last_progress["test_project"]["percent"] == 0.0
def test_callback_error_does_not_crash(self):
"""Ошибка в callback не прерывает работу."""
import src.mcp.server as server_module
from src.mcp.server import _create_progress_callback
# Подменяем _last_progress на объект который бросит ошибку
# ВАЖНО: не удерживаем _progress_lock во время вызова callback — иначе deadlock!
original = server_module._last_progress
server_module._last_progress = None # type: ignore
try:
cb = _create_progress_callback("test_project")
# Не должно упасть — callback оборачивает в try/except
cb("file.py", 1, 10, "scanning")
finally:
# Восстанавливаем
server_module._last_progress = original
def test_callback_tracks_timestamp(self):
"""Callback записывает timestamp."""
from src.mcp.server import _create_progress_callback, _last_progress, _progress_lock
with _progress_lock:
_last_progress.clear()
before = time.time()
cb = _create_progress_callback("test_project")
cb("file.py", 1, 10, "scanning")
after = time.time()
with _progress_lock:
ts = _last_progress["test_project"]["timestamp"]
assert before <= ts <= after
class TestCleanupOldProgress:
"""Тесты для очистки старых записей прогресса."""
def test_cleanup_removes_expired_entries(self):
"""Записи старше 1 часа удаляются."""
from src.mcp.server import _cleanup_old_progress, _last_progress, _progress_lock
with _progress_lock:
_last_progress.clear()
_last_progress["old_project"] = {
"phase": "complete",
"files_done": 10,
"files_total": 10,
"percent": 100.0,
"timestamp": time.time() - 7200, # 2 часа назад
}
_last_progress["new_project"] = {
"phase": "scanning",
"files_done": 5,
"files_total": 10,
"percent": 50.0,
"timestamp": time.time(),
}
_cleanup_old_progress()
with _progress_lock:
assert "old_project" not in _last_progress
assert "new_project" in _last_progress
def test_cleanup_keeps_recent_entries(self):
"""Свежие записи не удаляются."""
from src.mcp.server import _cleanup_old_progress, _last_progress, _progress_lock
with _progress_lock:
_last_progress.clear()
_last_progress["recent"] = {
"phase": "complete",
"files_done": 10,
"files_total": 10,
"percent": 100.0,
"timestamp": time.time() - 300, # 5 минут назад
}
_cleanup_old_progress()
with _progress_lock:
assert "recent" in _last_progress
def test_cleanup_empty_progress(self):
"""Очистка не падает на пустом прогрессе."""
from src.mcp.server import _cleanup_old_progress, _last_progress, _progress_lock
with _progress_lock:
_last_progress.clear()
# Не должно упасть
_cleanup_old_progress()
with _progress_lock:
assert len(_last_progress) == 0
class TestIndexerProgressCallback:
"""Тесты для progress callback в indexer."""
def test_indexer_accepts_callback(self):
"""Indexer принимает progress_callback параметр."""
from pathlib import Path
from src.core.indexing.indexer import Indexer
indexer = Indexer(
Path("/tmp/test.db"),
MagicMock(),
MagicMock(),
project_path=Path("/tmp")
)
# Проверяем что метод принимает callback
import inspect
sig = inspect.signature(indexer.index_project)
assert "progress_callback" in sig.parameters
def test_callback_is_optional(self):
"""progress_callback опциональный."""
from src.core.indexing.indexer import Indexer
import inspect
# Check signature without instantiating (avoids DB locks)
sig = inspect.signature(Indexer.index_project)
param = sig.parameters["progress_callback"]
assert param.default is None
class TestProgressLockThreadSafety:
"""Тесты потокобезопасности."""
def test_lock_protects_concurrent_access(self):
"""Lock защищает от concurrent access."""
from src.mcp.server import _last_progress, _progress_lock
errors = []
def writer():
try:
for i in range(100):
with _progress_lock:
_last_progress["test"] = {"value": i}
except Exception as e:
errors.append(e)
import threading
threads = [threading.Thread(target=writer) for _ in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
assert len(errors) == 0