auto-coder 0.1.363__py3-none-any.whl → 0.1.364__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/METADATA +2 -2
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/RECORD +33 -18
- autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +1 -1
- autocoder/auto_coder_runner.py +2 -0
- autocoder/common/__init__.py +2 -0
- autocoder/common/file_checkpoint/__init__.py +21 -0
- autocoder/common/file_checkpoint/backup.py +264 -0
- autocoder/common/file_checkpoint/examples.py +217 -0
- autocoder/common/file_checkpoint/manager.py +404 -0
- autocoder/common/file_checkpoint/models.py +156 -0
- autocoder/common/file_checkpoint/store.py +383 -0
- autocoder/common/file_checkpoint/test_backup.py +242 -0
- autocoder/common/file_checkpoint/test_manager.py +570 -0
- autocoder/common/file_checkpoint/test_models.py +360 -0
- autocoder/common/file_checkpoint/test_store.py +327 -0
- autocoder/common/file_checkpoint/test_utils.py +297 -0
- autocoder/common/file_checkpoint/utils.py +119 -0
- autocoder/common/rulefiles/autocoderrules_utils.py +114 -55
- autocoder/common/save_formatted_log.py +76 -5
- autocoder/common/v2/agent/agentic_edit.py +318 -197
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +2 -2
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +27 -4
- autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +322 -0
- autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +83 -61
- autocoder/compilers/normal_compiler.py +64 -0
- autocoder/events/event_manager_singleton.py +133 -4
- autocoder/linters/normal_linter.py +373 -0
- autocoder/linters/python_linter.py +4 -2
- autocoder/version.py +1 -1
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.364.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import json
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from autocoder.common.file_checkpoint.models import (
|
|
7
|
+
FileChange, ChangeRecord, DiffResult, ApplyResult, UndoResult
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
class TestFileChange:
|
|
11
|
+
"""FileChange类的单元测试"""
|
|
12
|
+
|
|
13
|
+
def test_init(self):
|
|
14
|
+
"""测试FileChange初始化"""
|
|
15
|
+
fc = FileChange(
|
|
16
|
+
file_path="test.py",
|
|
17
|
+
content="print('hello')",
|
|
18
|
+
is_new=True,
|
|
19
|
+
is_deletion=False
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
assert fc.file_path == "test.py"
|
|
23
|
+
assert fc.content == "print('hello')"
|
|
24
|
+
assert fc.is_new is True
|
|
25
|
+
assert fc.is_deletion is False
|
|
26
|
+
|
|
27
|
+
def test_from_dict(self):
|
|
28
|
+
"""测试从字典创建FileChange对象"""
|
|
29
|
+
data = {
|
|
30
|
+
'file_path': 'test.py',
|
|
31
|
+
'content': "print('hello')",
|
|
32
|
+
'is_new': True,
|
|
33
|
+
'is_deletion': False
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fc = FileChange.from_dict(data)
|
|
37
|
+
|
|
38
|
+
assert fc.file_path == "test.py"
|
|
39
|
+
assert fc.content == "print('hello')"
|
|
40
|
+
assert fc.is_new is True
|
|
41
|
+
assert fc.is_deletion is False
|
|
42
|
+
|
|
43
|
+
def test_to_dict(self):
|
|
44
|
+
"""测试将FileChange对象转换为字典"""
|
|
45
|
+
fc = FileChange(
|
|
46
|
+
file_path="test.py",
|
|
47
|
+
content="print('hello')",
|
|
48
|
+
is_new=True,
|
|
49
|
+
is_deletion=False
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
data = fc.to_dict()
|
|
53
|
+
|
|
54
|
+
assert data['file_path'] == "test.py"
|
|
55
|
+
assert data['content'] == "print('hello')"
|
|
56
|
+
assert data['is_new'] is True
|
|
57
|
+
assert data['is_deletion'] is False
|
|
58
|
+
|
|
59
|
+
def test_default_values(self):
|
|
60
|
+
"""测试默认值"""
|
|
61
|
+
fc = FileChange(
|
|
62
|
+
file_path="test.py",
|
|
63
|
+
content="print('hello')"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
assert fc.is_new is False
|
|
67
|
+
assert fc.is_deletion is False
|
|
68
|
+
|
|
69
|
+
def test_from_dict_missing_optionals(self):
|
|
70
|
+
"""测试从缺少可选字段的字典创建"""
|
|
71
|
+
data = {
|
|
72
|
+
'file_path': 'test.py',
|
|
73
|
+
'content': "print('hello')"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fc = FileChange.from_dict(data)
|
|
77
|
+
|
|
78
|
+
assert fc.file_path == "test.py"
|
|
79
|
+
assert fc.content == "print('hello')"
|
|
80
|
+
assert fc.is_new is False
|
|
81
|
+
assert fc.is_deletion is False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TestChangeRecord:
|
|
85
|
+
"""ChangeRecord类的单元测试"""
|
|
86
|
+
|
|
87
|
+
def test_init(self):
|
|
88
|
+
"""测试ChangeRecord初始化"""
|
|
89
|
+
cr = ChangeRecord(
|
|
90
|
+
change_id="123",
|
|
91
|
+
timestamp=1234567890.0,
|
|
92
|
+
file_path="test.py",
|
|
93
|
+
backup_id="456",
|
|
94
|
+
is_new=True,
|
|
95
|
+
is_deletion=False,
|
|
96
|
+
group_id="789"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
assert cr.change_id == "123"
|
|
100
|
+
assert cr.timestamp == 1234567890.0
|
|
101
|
+
assert cr.file_path == "test.py"
|
|
102
|
+
assert cr.backup_id == "456"
|
|
103
|
+
assert cr.is_new is True
|
|
104
|
+
assert cr.is_deletion is False
|
|
105
|
+
assert cr.group_id == "789"
|
|
106
|
+
|
|
107
|
+
def test_create(self):
|
|
108
|
+
"""测试创建一个新的变更记录"""
|
|
109
|
+
before_time = datetime.now().timestamp()
|
|
110
|
+
|
|
111
|
+
cr = ChangeRecord.create(
|
|
112
|
+
file_path="test.py",
|
|
113
|
+
backup_id="456",
|
|
114
|
+
is_new=True,
|
|
115
|
+
is_deletion=False,
|
|
116
|
+
group_id="789"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
after_time = datetime.now().timestamp()
|
|
120
|
+
|
|
121
|
+
assert cr.file_path == "test.py"
|
|
122
|
+
assert cr.backup_id == "456"
|
|
123
|
+
assert cr.is_new is True
|
|
124
|
+
assert cr.is_deletion is False
|
|
125
|
+
assert cr.group_id == "789"
|
|
126
|
+
|
|
127
|
+
# UUID和时间戳应该是自动生成的
|
|
128
|
+
assert cr.change_id is not None
|
|
129
|
+
assert len(cr.change_id) > 0
|
|
130
|
+
assert before_time <= cr.timestamp <= after_time
|
|
131
|
+
|
|
132
|
+
def test_from_dict(self):
|
|
133
|
+
"""测试从字典创建ChangeRecord对象"""
|
|
134
|
+
data = {
|
|
135
|
+
'change_id': '123',
|
|
136
|
+
'timestamp': 1234567890.0,
|
|
137
|
+
'file_path': 'test.py',
|
|
138
|
+
'backup_id': '456',
|
|
139
|
+
'is_new': True,
|
|
140
|
+
'is_deletion': False,
|
|
141
|
+
'group_id': '789'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
cr = ChangeRecord.from_dict(data)
|
|
145
|
+
|
|
146
|
+
assert cr.change_id == "123"
|
|
147
|
+
assert cr.timestamp == 1234567890.0
|
|
148
|
+
assert cr.file_path == "test.py"
|
|
149
|
+
assert cr.backup_id == "456"
|
|
150
|
+
assert cr.is_new is True
|
|
151
|
+
assert cr.is_deletion is False
|
|
152
|
+
assert cr.group_id == "789"
|
|
153
|
+
|
|
154
|
+
def test_to_dict(self):
|
|
155
|
+
"""测试将ChangeRecord对象转换为字典"""
|
|
156
|
+
cr = ChangeRecord(
|
|
157
|
+
change_id="123",
|
|
158
|
+
timestamp=1234567890.0,
|
|
159
|
+
file_path="test.py",
|
|
160
|
+
backup_id="456",
|
|
161
|
+
is_new=True,
|
|
162
|
+
is_deletion=False,
|
|
163
|
+
group_id="789"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
data = cr.to_dict()
|
|
167
|
+
|
|
168
|
+
assert data['change_id'] == "123"
|
|
169
|
+
assert data['timestamp'] == 1234567890.0
|
|
170
|
+
assert data['file_path'] == "test.py"
|
|
171
|
+
assert data['backup_id'] == "456"
|
|
172
|
+
assert data['is_new'] is True
|
|
173
|
+
assert data['is_deletion'] is False
|
|
174
|
+
assert data['group_id'] == "789"
|
|
175
|
+
|
|
176
|
+
def test_default_values(self):
|
|
177
|
+
"""测试默认值"""
|
|
178
|
+
cr = ChangeRecord(
|
|
179
|
+
change_id="123",
|
|
180
|
+
timestamp=1234567890.0,
|
|
181
|
+
file_path="test.py",
|
|
182
|
+
backup_id="456"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
assert cr.is_new is False
|
|
186
|
+
assert cr.is_deletion is False
|
|
187
|
+
assert cr.group_id is None
|
|
188
|
+
|
|
189
|
+
def test_from_dict_missing_optionals(self):
|
|
190
|
+
"""测试从缺少可选字段的字典创建"""
|
|
191
|
+
data = {
|
|
192
|
+
'change_id': '123',
|
|
193
|
+
'timestamp': 1234567890.0,
|
|
194
|
+
'file_path': 'test.py',
|
|
195
|
+
'backup_id': '456'
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
cr = ChangeRecord.from_dict(data)
|
|
199
|
+
|
|
200
|
+
assert cr.change_id == "123"
|
|
201
|
+
assert cr.timestamp == 1234567890.0
|
|
202
|
+
assert cr.file_path == "test.py"
|
|
203
|
+
assert cr.backup_id == "456"
|
|
204
|
+
assert cr.is_new is False
|
|
205
|
+
assert cr.is_deletion is False
|
|
206
|
+
assert cr.group_id is None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class TestDiffResult:
|
|
210
|
+
"""DiffResult类的单元测试"""
|
|
211
|
+
|
|
212
|
+
def test_init(self):
|
|
213
|
+
"""测试DiffResult初始化"""
|
|
214
|
+
dr = DiffResult(
|
|
215
|
+
file_path="test.py",
|
|
216
|
+
old_content="print('hello')",
|
|
217
|
+
new_content="print('world')",
|
|
218
|
+
is_new=False,
|
|
219
|
+
is_deletion=False
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
assert dr.file_path == "test.py"
|
|
223
|
+
assert dr.old_content == "print('hello')"
|
|
224
|
+
assert dr.new_content == "print('world')"
|
|
225
|
+
assert dr.is_new is False
|
|
226
|
+
assert dr.is_deletion is False
|
|
227
|
+
|
|
228
|
+
def test_default_values(self):
|
|
229
|
+
"""测试默认值"""
|
|
230
|
+
dr = DiffResult(
|
|
231
|
+
file_path="test.py",
|
|
232
|
+
old_content="print('hello')",
|
|
233
|
+
new_content="print('world')"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
assert dr.is_new is False
|
|
237
|
+
assert dr.is_deletion is False
|
|
238
|
+
|
|
239
|
+
def test_get_diff_summary_modify(self):
|
|
240
|
+
"""测试获取修改文件的差异摘要"""
|
|
241
|
+
dr = DiffResult(
|
|
242
|
+
file_path="test.py",
|
|
243
|
+
old_content="line1\nline2\nline3",
|
|
244
|
+
new_content="line1\nline2\nline3\nline4"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
summary = dr.get_diff_summary()
|
|
248
|
+
|
|
249
|
+
assert "修改文件" in summary
|
|
250
|
+
assert "test.py" in summary
|
|
251
|
+
assert "原始行数: 3" in summary
|
|
252
|
+
assert "新行数: 4" in summary
|
|
253
|
+
|
|
254
|
+
def test_get_diff_summary_new(self):
|
|
255
|
+
"""测试获取新文件的差异摘要"""
|
|
256
|
+
dr = DiffResult(
|
|
257
|
+
file_path="test.py",
|
|
258
|
+
old_content=None,
|
|
259
|
+
new_content="print('hello')",
|
|
260
|
+
is_new=True
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
summary = dr.get_diff_summary()
|
|
264
|
+
|
|
265
|
+
assert "新文件" in summary
|
|
266
|
+
assert "test.py" in summary
|
|
267
|
+
|
|
268
|
+
def test_get_diff_summary_delete(self):
|
|
269
|
+
"""测试获取删除文件的差异摘要"""
|
|
270
|
+
dr = DiffResult(
|
|
271
|
+
file_path="test.py",
|
|
272
|
+
old_content="print('hello')",
|
|
273
|
+
new_content="",
|
|
274
|
+
is_deletion=True
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
summary = dr.get_diff_summary()
|
|
278
|
+
|
|
279
|
+
assert "删除文件" in summary
|
|
280
|
+
assert "test.py" in summary
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class TestApplyResult:
|
|
284
|
+
"""ApplyResult类的单元测试"""
|
|
285
|
+
|
|
286
|
+
def test_init(self):
|
|
287
|
+
"""测试ApplyResult初始化"""
|
|
288
|
+
ar = ApplyResult(success=True)
|
|
289
|
+
|
|
290
|
+
assert ar.success is True
|
|
291
|
+
assert ar.change_ids == []
|
|
292
|
+
assert ar.errors == {}
|
|
293
|
+
assert ar.has_errors is False
|
|
294
|
+
|
|
295
|
+
def test_add_error(self):
|
|
296
|
+
"""测试添加错误信息"""
|
|
297
|
+
ar = ApplyResult(success=True)
|
|
298
|
+
|
|
299
|
+
assert ar.success is True
|
|
300
|
+
assert ar.has_errors is False
|
|
301
|
+
|
|
302
|
+
ar.add_error("test.py", "文件不存在")
|
|
303
|
+
|
|
304
|
+
assert ar.success is False
|
|
305
|
+
assert ar.has_errors is True
|
|
306
|
+
assert "test.py" in ar.errors
|
|
307
|
+
assert ar.errors["test.py"] == "文件不存在"
|
|
308
|
+
|
|
309
|
+
def test_add_change_id(self):
|
|
310
|
+
"""测试添加成功应用的变更ID"""
|
|
311
|
+
ar = ApplyResult(success=True)
|
|
312
|
+
|
|
313
|
+
assert len(ar.change_ids) == 0
|
|
314
|
+
|
|
315
|
+
ar.add_change_id("123")
|
|
316
|
+
ar.add_change_id("456")
|
|
317
|
+
|
|
318
|
+
assert len(ar.change_ids) == 2
|
|
319
|
+
assert "123" in ar.change_ids
|
|
320
|
+
assert "456" in ar.change_ids
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class TestUndoResult:
|
|
324
|
+
"""UndoResult类的单元测试"""
|
|
325
|
+
|
|
326
|
+
def test_init(self):
|
|
327
|
+
"""测试UndoResult初始化"""
|
|
328
|
+
ur = UndoResult(success=True)
|
|
329
|
+
|
|
330
|
+
assert ur.success is True
|
|
331
|
+
assert ur.restored_files == []
|
|
332
|
+
assert ur.errors == {}
|
|
333
|
+
assert ur.has_errors is False
|
|
334
|
+
|
|
335
|
+
def test_add_error(self):
|
|
336
|
+
"""测试添加错误信息"""
|
|
337
|
+
ur = UndoResult(success=True)
|
|
338
|
+
|
|
339
|
+
assert ur.success is True
|
|
340
|
+
assert ur.has_errors is False
|
|
341
|
+
|
|
342
|
+
ur.add_error("test.py", "文件不存在")
|
|
343
|
+
|
|
344
|
+
assert ur.success is False
|
|
345
|
+
assert ur.has_errors is True
|
|
346
|
+
assert "test.py" in ur.errors
|
|
347
|
+
assert ur.errors["test.py"] == "文件不存在"
|
|
348
|
+
|
|
349
|
+
def test_add_restored_file(self):
|
|
350
|
+
"""测试添加成功恢复的文件"""
|
|
351
|
+
ur = UndoResult(success=True)
|
|
352
|
+
|
|
353
|
+
assert len(ur.restored_files) == 0
|
|
354
|
+
|
|
355
|
+
ur.add_restored_file("test.py")
|
|
356
|
+
ur.add_restored_file("main.py")
|
|
357
|
+
|
|
358
|
+
assert len(ur.restored_files) == 2
|
|
359
|
+
assert "test.py" in ur.restored_files
|
|
360
|
+
assert "main.py" in ur.restored_files
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import sqlite3
|
|
5
|
+
import tempfile
|
|
6
|
+
import shutil
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from unittest.mock import patch, MagicMock
|
|
10
|
+
|
|
11
|
+
from autocoder.common.file_checkpoint.store import FileChangeStore
|
|
12
|
+
from autocoder.common.file_checkpoint.models import ChangeRecord
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def temp_test_dir():
|
|
16
|
+
"""提供一个临时的测试目录"""
|
|
17
|
+
temp_dir = tempfile.mkdtemp()
|
|
18
|
+
yield temp_dir
|
|
19
|
+
shutil.rmtree(temp_dir)
|
|
20
|
+
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def temp_store_dir():
|
|
23
|
+
"""提供一个临时的存储目录"""
|
|
24
|
+
temp_dir = tempfile.mkdtemp()
|
|
25
|
+
yield temp_dir
|
|
26
|
+
shutil.rmtree(temp_dir)
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def sample_change_record():
|
|
30
|
+
"""创建一个用于测试的变更记录"""
|
|
31
|
+
return ChangeRecord.create(
|
|
32
|
+
file_path="test.py",
|
|
33
|
+
backup_id="backup123",
|
|
34
|
+
is_new=False,
|
|
35
|
+
is_deletion=False,
|
|
36
|
+
group_id="group456"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
class TestFileChangeStore:
|
|
40
|
+
"""FileChangeStore类的单元测试"""
|
|
41
|
+
|
|
42
|
+
def test_init_with_custom_dir(self, temp_store_dir):
|
|
43
|
+
"""测试使用自定义目录初始化"""
|
|
44
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
45
|
+
|
|
46
|
+
assert store.store_dir == temp_store_dir
|
|
47
|
+
assert os.path.exists(temp_store_dir)
|
|
48
|
+
assert os.path.exists(os.path.join(temp_store_dir, "changes.db"))
|
|
49
|
+
|
|
50
|
+
def test_init_with_default_dir(self, monkeypatch):
|
|
51
|
+
"""测试使用默认目录初始化"""
|
|
52
|
+
# 创建一个临时主目录
|
|
53
|
+
temp_home = tempfile.mkdtemp()
|
|
54
|
+
try:
|
|
55
|
+
# 模拟用户主目录
|
|
56
|
+
monkeypatch.setattr(os.path, 'expanduser', lambda path: temp_home)
|
|
57
|
+
|
|
58
|
+
store = FileChangeStore()
|
|
59
|
+
|
|
60
|
+
expected_dir = os.path.join(temp_home, ".autocoder", "changes")
|
|
61
|
+
assert store.store_dir == expected_dir
|
|
62
|
+
assert os.path.exists(expected_dir)
|
|
63
|
+
assert os.path.exists(os.path.join(expected_dir, "changes.db"))
|
|
64
|
+
finally:
|
|
65
|
+
shutil.rmtree(temp_home)
|
|
66
|
+
|
|
67
|
+
def test_save_change(self, temp_store_dir, sample_change_record):
|
|
68
|
+
"""测试保存变更记录"""
|
|
69
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
70
|
+
|
|
71
|
+
# 保存变更记录
|
|
72
|
+
change_id = store.save_change(sample_change_record)
|
|
73
|
+
|
|
74
|
+
# 检查返回的变更ID
|
|
75
|
+
assert change_id == sample_change_record.change_id
|
|
76
|
+
|
|
77
|
+
# 检查数据库中是否存在该记录
|
|
78
|
+
conn = sqlite3.connect(os.path.join(temp_store_dir, "changes.db"))
|
|
79
|
+
cursor = conn.cursor()
|
|
80
|
+
cursor.execute("SELECT * FROM changes WHERE change_id = ?", (change_id,))
|
|
81
|
+
row = cursor.fetchone()
|
|
82
|
+
conn.close()
|
|
83
|
+
|
|
84
|
+
assert row is not None
|
|
85
|
+
assert row[0] == change_id # change_id
|
|
86
|
+
assert row[1] == sample_change_record.timestamp # timestamp
|
|
87
|
+
assert row[2] == sample_change_record.file_path # file_path
|
|
88
|
+
assert row[3] == sample_change_record.backup_id # backup_id
|
|
89
|
+
assert row[4] == 0 # is_new (转为int)
|
|
90
|
+
assert row[5] == 0 # is_deletion (转为int)
|
|
91
|
+
assert row[6] == sample_change_record.group_id # group_id
|
|
92
|
+
|
|
93
|
+
def test_get_change(self, temp_store_dir, sample_change_record):
|
|
94
|
+
"""测试获取变更记录"""
|
|
95
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
96
|
+
|
|
97
|
+
# 保存变更记录
|
|
98
|
+
change_id = store.save_change(sample_change_record)
|
|
99
|
+
|
|
100
|
+
# 获取变更记录
|
|
101
|
+
retrieved_record = store.get_change(change_id)
|
|
102
|
+
|
|
103
|
+
# 检查获取的记录
|
|
104
|
+
assert retrieved_record is not None
|
|
105
|
+
assert retrieved_record.change_id == sample_change_record.change_id
|
|
106
|
+
assert retrieved_record.timestamp == sample_change_record.timestamp
|
|
107
|
+
assert retrieved_record.file_path == sample_change_record.file_path
|
|
108
|
+
assert retrieved_record.backup_id == sample_change_record.backup_id
|
|
109
|
+
assert retrieved_record.is_new == sample_change_record.is_new
|
|
110
|
+
assert retrieved_record.is_deletion == sample_change_record.is_deletion
|
|
111
|
+
assert retrieved_record.group_id == sample_change_record.group_id
|
|
112
|
+
|
|
113
|
+
def test_get_nonexistent_change(self, temp_store_dir):
|
|
114
|
+
"""测试获取不存在的变更记录"""
|
|
115
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
116
|
+
|
|
117
|
+
# 尝试获取不存在的记录
|
|
118
|
+
retrieved_record = store.get_change("nonexistent_id")
|
|
119
|
+
|
|
120
|
+
# 应该返回None
|
|
121
|
+
assert retrieved_record is None
|
|
122
|
+
|
|
123
|
+
def test_get_changes_by_group(self, temp_store_dir):
|
|
124
|
+
"""测试按组获取变更记录"""
|
|
125
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
126
|
+
group_id = "group123"
|
|
127
|
+
|
|
128
|
+
# 创建并保存三个同组的变更记录
|
|
129
|
+
for i in range(3):
|
|
130
|
+
record = ChangeRecord.create(
|
|
131
|
+
file_path=f"file{i}.py",
|
|
132
|
+
backup_id=f"backup{i}",
|
|
133
|
+
group_id=group_id
|
|
134
|
+
)
|
|
135
|
+
store.save_change(record)
|
|
136
|
+
|
|
137
|
+
# 创建一个不同组的变更记录
|
|
138
|
+
other_record = ChangeRecord.create(
|
|
139
|
+
file_path="other.py",
|
|
140
|
+
backup_id="other_backup",
|
|
141
|
+
group_id="other_group"
|
|
142
|
+
)
|
|
143
|
+
store.save_change(other_record)
|
|
144
|
+
|
|
145
|
+
# 获取同组的变更记录
|
|
146
|
+
group_records = store.get_changes_by_group(group_id)
|
|
147
|
+
|
|
148
|
+
# 检查记录数量和组ID
|
|
149
|
+
assert len(group_records) == 3
|
|
150
|
+
for record in group_records:
|
|
151
|
+
assert record.group_id == group_id
|
|
152
|
+
|
|
153
|
+
def test_get_latest_changes(self, temp_store_dir):
|
|
154
|
+
"""测试获取最近的变更记录"""
|
|
155
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
156
|
+
|
|
157
|
+
# 创建并保存五个变更记录,时间戳递增
|
|
158
|
+
for i in range(5):
|
|
159
|
+
record = ChangeRecord.create(
|
|
160
|
+
file_path=f"file{i}.py",
|
|
161
|
+
backup_id=f"backup{i}"
|
|
162
|
+
)
|
|
163
|
+
# 手动设置时间戳,确保顺序
|
|
164
|
+
record.timestamp = 1000 + i
|
|
165
|
+
store.save_change(record)
|
|
166
|
+
|
|
167
|
+
# 获取最近的3个变更记录
|
|
168
|
+
latest_records = store.get_latest_changes(limit=3)
|
|
169
|
+
|
|
170
|
+
# 检查记录数量和顺序
|
|
171
|
+
assert len(latest_records) == 3
|
|
172
|
+
assert latest_records[0].timestamp > latest_records[1].timestamp
|
|
173
|
+
assert latest_records[1].timestamp > latest_records[2].timestamp
|
|
174
|
+
|
|
175
|
+
def test_get_changes_by_file(self, temp_store_dir):
|
|
176
|
+
"""测试按文件获取变更记录"""
|
|
177
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
178
|
+
file_path = "test.py"
|
|
179
|
+
|
|
180
|
+
# 创建并保存同一文件的三个变更记录
|
|
181
|
+
for i in range(3):
|
|
182
|
+
record = ChangeRecord.create(
|
|
183
|
+
file_path=file_path,
|
|
184
|
+
backup_id=f"backup{i}"
|
|
185
|
+
)
|
|
186
|
+
store.save_change(record)
|
|
187
|
+
|
|
188
|
+
# 创建一个不同文件的变更记录
|
|
189
|
+
other_record = ChangeRecord.create(
|
|
190
|
+
file_path="other.py",
|
|
191
|
+
backup_id="other_backup"
|
|
192
|
+
)
|
|
193
|
+
store.save_change(other_record)
|
|
194
|
+
|
|
195
|
+
# 获取同一文件的变更记录
|
|
196
|
+
file_records = store.get_changes_by_file(file_path)
|
|
197
|
+
|
|
198
|
+
# 检查记录数量和文件路径
|
|
199
|
+
assert len(file_records) == 3
|
|
200
|
+
for record in file_records:
|
|
201
|
+
assert record.file_path == file_path
|
|
202
|
+
|
|
203
|
+
def test_delete_change(self, temp_store_dir, sample_change_record):
|
|
204
|
+
"""测试删除变更记录"""
|
|
205
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
206
|
+
|
|
207
|
+
# 保存变更记录
|
|
208
|
+
change_id = store.save_change(sample_change_record)
|
|
209
|
+
|
|
210
|
+
# 确认记录存在
|
|
211
|
+
assert store.get_change(change_id) is not None
|
|
212
|
+
|
|
213
|
+
# 删除记录
|
|
214
|
+
success = store.delete_change(change_id)
|
|
215
|
+
|
|
216
|
+
# 检查删除结果
|
|
217
|
+
assert success is True
|
|
218
|
+
assert store.get_change(change_id) is None
|
|
219
|
+
|
|
220
|
+
# 检查文件系统中的JSON文件是否也被删除
|
|
221
|
+
json_file = os.path.join(temp_store_dir, f"{change_id}.json")
|
|
222
|
+
assert not os.path.exists(json_file)
|
|
223
|
+
|
|
224
|
+
def test_delete_nonexistent_change(self, temp_store_dir):
|
|
225
|
+
"""测试删除不存在的变更记录"""
|
|
226
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
227
|
+
|
|
228
|
+
# 尝试删除不存在的记录
|
|
229
|
+
success = store.delete_change("nonexistent_id")
|
|
230
|
+
|
|
231
|
+
# 应该返回False
|
|
232
|
+
assert success is False
|
|
233
|
+
|
|
234
|
+
def test_get_change_groups(self, temp_store_dir):
|
|
235
|
+
"""测试获取变更组列表"""
|
|
236
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
237
|
+
|
|
238
|
+
# 创建两个不同组的变更记录
|
|
239
|
+
group1_id = "group1"
|
|
240
|
+
group2_id = "group2"
|
|
241
|
+
|
|
242
|
+
# 为第一个组创建两个记录
|
|
243
|
+
for i in range(2):
|
|
244
|
+
record = ChangeRecord.create(
|
|
245
|
+
file_path=f"file{i}.py",
|
|
246
|
+
backup_id=f"backup{i}",
|
|
247
|
+
group_id=group1_id
|
|
248
|
+
)
|
|
249
|
+
store.save_change(record)
|
|
250
|
+
|
|
251
|
+
# 为第二个组创建一个记录
|
|
252
|
+
record = ChangeRecord.create(
|
|
253
|
+
file_path="otherfile.py",
|
|
254
|
+
backup_id="other_backup",
|
|
255
|
+
group_id=group2_id
|
|
256
|
+
)
|
|
257
|
+
store.save_change(record)
|
|
258
|
+
|
|
259
|
+
# 获取变更组列表
|
|
260
|
+
groups = store.get_change_groups()
|
|
261
|
+
|
|
262
|
+
# 检查组数量
|
|
263
|
+
assert len(groups) == 2
|
|
264
|
+
|
|
265
|
+
# 检查组信息
|
|
266
|
+
groups_dict = {g[0]: (g[1], g[2]) for g in groups}
|
|
267
|
+
assert group1_id in groups_dict
|
|
268
|
+
assert group2_id in groups_dict
|
|
269
|
+
assert groups_dict[group1_id][1] == 2 # 第一个组有2个记录
|
|
270
|
+
assert groups_dict[group2_id][1] == 1 # 第二个组有1个记录
|
|
271
|
+
|
|
272
|
+
def test_clean_old_history(self, temp_store_dir):
|
|
273
|
+
"""测试清理旧历史记录"""
|
|
274
|
+
store = FileChangeStore(store_dir=temp_store_dir, max_history=5)
|
|
275
|
+
|
|
276
|
+
# 创建10个变更记录
|
|
277
|
+
for i in range(10):
|
|
278
|
+
record = ChangeRecord.create(
|
|
279
|
+
file_path=f"file{i}.py",
|
|
280
|
+
backup_id=f"backup{i}"
|
|
281
|
+
)
|
|
282
|
+
# 手动设置时间戳,确保顺序
|
|
283
|
+
record.timestamp = 1000 + i
|
|
284
|
+
store.save_change(record)
|
|
285
|
+
|
|
286
|
+
# 获取所有变更记录
|
|
287
|
+
conn = sqlite3.connect(os.path.join(temp_store_dir, "changes.db"))
|
|
288
|
+
cursor = conn.cursor()
|
|
289
|
+
cursor.execute("SELECT COUNT(*) FROM changes")
|
|
290
|
+
count = cursor.fetchone()[0]
|
|
291
|
+
conn.close()
|
|
292
|
+
|
|
293
|
+
# 检查记录数量,应该只保留max_history个
|
|
294
|
+
assert count == 5 # max_history
|
|
295
|
+
|
|
296
|
+
def test_thread_safety(self, temp_store_dir, sample_change_record):
|
|
297
|
+
"""测试线程安全性"""
|
|
298
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
299
|
+
|
|
300
|
+
# 保存变更记录
|
|
301
|
+
change_id = store.save_change(sample_change_record)
|
|
302
|
+
|
|
303
|
+
# 模拟两个线程同时访问
|
|
304
|
+
with patch('threading.RLock') as mock_lock:
|
|
305
|
+
# 设置锁为MagicMock对象
|
|
306
|
+
mock_lock_instance = MagicMock()
|
|
307
|
+
mock_lock.return_value = mock_lock_instance
|
|
308
|
+
|
|
309
|
+
# 重新创建存储对象(会使用模拟的锁)
|
|
310
|
+
store = FileChangeStore(store_dir=temp_store_dir)
|
|
311
|
+
|
|
312
|
+
# 执行一些需要锁的操作
|
|
313
|
+
store.get_change(change_id)
|
|
314
|
+
|
|
315
|
+
# 创建一个新的记录对象(使用不同的ID)
|
|
316
|
+
new_record = ChangeRecord.create(
|
|
317
|
+
file_path=sample_change_record.file_path,
|
|
318
|
+
backup_id=sample_change_record.backup_id,
|
|
319
|
+
is_new=sample_change_record.is_new,
|
|
320
|
+
is_deletion=sample_change_record.is_deletion,
|
|
321
|
+
group_id=sample_change_record.group_id
|
|
322
|
+
)
|
|
323
|
+
store.save_change(new_record)
|
|
324
|
+
|
|
325
|
+
# 检查锁是否被使用
|
|
326
|
+
assert mock_lock_instance.__enter__.call_count >= 2
|
|
327
|
+
assert mock_lock_instance.__exit__.call_count >= 2
|