forked from 1jehuang/jcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_swarm_debug.py
More file actions
executable file
·473 lines (405 loc) · 14.6 KB
/
test_swarm_debug.py
File metadata and controls
executable file
·473 lines (405 loc) · 14.6 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
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
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
#!/usr/bin/env python3
"""
Test script for swarm debug socket commands.
Tests all the new swarm commands including proposals, touches, timestamps, etc.
"""
import socket
import json
import time
import sys
import os
from pathlib import Path
SOCKET_PATH = f"/run/user/{os.getuid()}/jcode-debug.sock"
MAIN_SOCKET_PATH = f"/run/user/{os.getuid()}/jcode.sock"
REPO_ROOT = Path(__file__).resolve().parent.parent
def send_cmd(cmd, session_id=None, timeout=10):
"""Send a debug command and return the response."""
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect(SOCKET_PATH)
req = {"type": "debug_command", "id": 1, "command": cmd}
if session_id:
req["session_id"] = session_id
sock.send((json.dumps(req) + '\n').encode())
# Read response with non-blocking to handle slow responses
data = b''
sock.setblocking(False)
start = time.time()
while time.time() - start < timeout:
try:
chunk = sock.recv(4096)
if chunk:
data += chunk
# Check if we have a complete JSON response
try:
resp = json.loads(data.decode())
sock.close()
return resp.get('ok', False), resp.get('output', '')
except json.JSONDecodeError:
pass
except BlockingIOError:
time.sleep(0.05)
sock.close()
if data:
try:
resp = json.loads(data.decode())
return resp.get('ok', False), resp.get('output', '')
except json.JSONDecodeError:
return False, f"Invalid JSON: {data.decode()[:100]}"
raise TimeoutError("timed out")
def create_session(cwd="/tmp"):
"""Create a headless session for testing."""
ok, output = send_cmd(f"create_session:{cwd}")
if ok:
return json.loads(output).get('session_id')
return None
def destroy_session(session_id):
"""Destroy a test session."""
send_cmd(f"destroy_session:{session_id}")
def test_basic_swarm_commands():
"""Test basic swarm listing commands."""
print("\n=== Testing Basic Swarm Commands ===")
tests = [
("swarm:list", "List all swarms"),
("swarm:members", "List all members"),
("swarm:coordinators", "List coordinators"),
("swarm:plans", "List plans"),
("swarm:context", "List shared context"),
("swarm:touches", "List file touches"),
("swarm:conflicts", "List conflicts"),
("swarm:interrupts", "List interrupts"),
("swarm:proposals", "List proposals"),
]
passed = 0
failed = 0
for cmd, desc in tests:
ok, output = send_cmd(cmd)
if ok:
print(f" ✓ {cmd}: {desc}")
# Verify output is valid JSON
try:
parsed = json.loads(output) if output and output.strip() not in ['', '{}'] else output
passed += 1
except json.JSONDecodeError:
# Some outputs might be plain strings
passed += 1
else:
print(f" ✗ {cmd}: {desc} - FAILED: {output[:100]}")
failed += 1
return passed, failed
def test_swarm_touches_timestamps():
"""Test that file touches include timestamps."""
print("\n=== Testing File Touches with Timestamps ===")
passed = 0
failed = 0
# Check touches output format (even if empty)
ok, output = send_cmd("swarm:touches")
if ok:
touches = json.loads(output)
# Check that if there are touches, they have timestamp_unix
if len(touches) > 0:
if 'timestamp_unix' in touches[0]:
print(" ✓ swarm:touches includes timestamp_unix field")
passed += 1
else:
print(f" ✗ swarm:touches missing timestamp_unix: {list(touches[0].keys())}")
failed += 1
else:
print(" ⚠ No touches to verify timestamp format (empty list is valid)")
passed += 1
else:
print(f" ✗ swarm:touches failed: {output[:100]}")
failed += 1
return passed, failed
def test_swarm_member_timestamps():
"""Test that swarm:members includes timestamps."""
print("\n=== Testing Swarm Member Timestamps ===")
ok, output = send_cmd("swarm:members")
if not ok:
print(f" ✗ swarm:members failed: {output[:100]}")
return 0, 1
passed = 0
failed = 0
try:
members = json.loads(output)
if len(members) == 0:
print(" ⚠ No members to test (empty swarm)")
return 1, 0
# Check for timestamp fields
sample = members[0]
required_fields = ['joined_secs_ago', 'status_changed_secs_ago']
for field in required_fields:
if field in sample:
print(f" ✓ swarm:members has {field}")
passed += 1
else:
print(f" ✗ swarm:members missing {field}")
failed += 1
except json.JSONDecodeError as e:
print(f" ✗ Invalid JSON from swarm:members: {e}")
failed += 1
return passed, failed
def test_swarm_session_details():
"""Test swarm:session command format (without creating sessions)."""
print("\n=== Testing swarm:session Details ===")
passed = 0
failed = 0
# Test with a made-up session ID - should return an error gracefully
ok, output = send_cmd("swarm:session:nonexistent_session_123")
if not ok:
# Error is expected for nonexistent session
if "not found" in output.lower() or "unknown" in output.lower() or "no session" in output.lower():
print(" ✓ swarm:session handles unknown session gracefully")
passed += 1
else:
print(f" ✓ swarm:session returns error for unknown session: {output[:80]}")
passed += 1
else:
print(f" ⚠ swarm:session unexpectedly succeeded for unknown session")
passed += 1
return passed, failed
def test_swarm_context_timestamps():
"""Test that shared context entries have timestamps."""
print("\n=== Testing Shared Context Timestamps ===")
passed = 0
failed = 0
# Check context output format (even if empty)
ok, output = send_cmd("swarm:context")
if ok:
contexts = json.loads(output)
if len(contexts) > 0:
sample = contexts[0]
if 'created_secs_ago' in sample and 'updated_secs_ago' in sample:
print(" ✓ swarm:context has timestamp fields")
passed += 1
else:
print(f" ✗ swarm:context missing timestamps: {list(sample.keys())}")
failed += 1
else:
print(" ⚠ No context entries to verify timestamp format (empty list is valid)")
passed += 1
else:
print(f" ✗ swarm:context failed: {output[:100]}")
failed += 1
return passed, failed
def test_swarm_proposals():
"""Test plan proposal commands."""
print("\n=== Testing Plan Proposals ===")
passed = 0
failed = 0
# Test basic proposals list
ok, output = send_cmd("swarm:proposals")
if ok:
try:
proposals = json.loads(output)
print(f" ✓ swarm:proposals returns valid JSON ({len(proposals)} proposals)")
passed += 1
except json.JSONDecodeError:
print(f" ✗ swarm:proposals invalid JSON: {output[:100]}")
failed += 1
else:
print(f" ✗ swarm:proposals failed: {output[:100]}")
failed += 1
# Test proposals for a swarm
ok, output = send_cmd("swarm:proposals:/tmp")
if ok:
try:
proposals = json.loads(output)
print(f" ✓ swarm:proposals:/tmp returns valid JSON")
passed += 1
except json.JSONDecodeError:
print(f" ✗ swarm:proposals:/tmp invalid JSON: {output[:100]}")
failed += 1
else:
# Might fail if swarm doesn't exist, which is OK
if "No proposal" in output or output == "[]":
print(f" ✓ swarm:proposals:/tmp handles missing swarm correctly")
passed += 1
else:
print(f" ✗ swarm:proposals:/tmp failed: {output[:100]}")
failed += 1
return passed, failed
def test_swarm_touches_filtering():
"""Test file touches swarm filtering."""
print("\n=== Testing File Touches Swarm Filtering ===")
ok, output = send_cmd("swarm:touches:swarm:/tmp")
if ok:
try:
touches = json.loads(output)
print(f" ✓ swarm:touches:swarm:/tmp returns valid JSON ({len(touches)} touches)")
return 1, 0
except json.JSONDecodeError:
print(f" ✗ swarm:touches:swarm:/tmp invalid JSON: {output[:100]}")
return 0, 1
else:
print(f" ✗ swarm:touches:swarm:/tmp failed: {output[:100]}")
return 0, 1
def test_swarm_conflicts_details():
"""Test that conflicts include full access history."""
print("\n=== Testing Conflict Details ===")
ok, output = send_cmd("swarm:conflicts")
if ok:
try:
conflicts = json.loads(output)
if len(conflicts) > 0:
sample = conflicts[0]
if 'accesses' in sample:
print(" ✓ swarm:conflicts includes full access history")
return 1, 0
else:
print(f" ✗ swarm:conflicts missing accesses: {sample.keys()}")
return 0, 1
else:
print(" ⚠ No conflicts to test (no multi-session touches)")
return 1, 0
except json.JSONDecodeError:
print(f" ✗ swarm:conflicts invalid JSON: {output[:100]}")
return 0, 1
else:
print(f" ✗ swarm:conflicts failed: {output[:100]}")
return 0, 1
def test_swarm_id_provenance():
"""Test swarm:id command for path provenance."""
print("\n=== Testing Swarm ID Provenance ===")
ok, output = send_cmd(f"swarm:id:{REPO_ROOT}")
if ok:
try:
data = json.loads(output)
required = ['path', 'swarm_id', 'git_root', 'is_git_repo']
missing = [f for f in required if f not in data]
if not missing:
print(" ✓ swarm:id includes all provenance fields")
return 1, 0
else:
print(f" ✗ swarm:id missing fields: {missing}")
return 0, 1
except json.JSONDecodeError:
print(f" ✗ swarm:id invalid JSON: {output[:100]}")
return 0, 1
else:
print(f" ✗ swarm:id failed: {output[:100]}")
return 0, 1
def test_swarm_help():
"""Test that help includes new commands."""
print("\n=== Testing Help Documentation ===")
ok, output = send_cmd("swarm:help")
if not ok:
print(f" ✗ swarm:help failed: {output[:100]}")
return 0, 1
passed = 0
failed = 0
# Check for documented commands
commands_to_check = [
"swarm:proposals",
"swarm:touches:swarm:",
"timestamp",
"swarm:session",
"events:recent",
"events:since",
]
for cmd in commands_to_check:
if cmd.lower() in output.lower():
print(f" ✓ Help documents {cmd}")
passed += 1
else:
print(f" ✗ Help missing {cmd}")
failed += 1
return passed, failed
def test_event_commands():
"""Test real-time event subscription commands."""
print("\n=== Testing Event Commands ===")
passed = 0
failed = 0
# Test events:count
ok, output = send_cmd("events:count")
if ok:
data = json.loads(output)
if 'count' in data and 'latest_id' in data and 'max_history' in data:
print(" ✓ events:count returns expected fields")
passed += 1
else:
print(f" ✗ events:count missing fields: {list(data.keys())}")
failed += 1
else:
print(f" ✗ events:count failed: {output[:80]}")
failed += 1
# Test events:types
ok, output = send_cmd("events:types")
if ok:
data = json.loads(output)
if 'types' in data and len(data['types']) > 0:
print(f" ✓ events:types returns {len(data['types'])} event types")
passed += 1
else:
print(f" ✗ events:types missing types")
failed += 1
else:
print(f" ✗ events:types failed: {output[:80]}")
failed += 1
# Test events:recent
ok, output = send_cmd("events:recent")
if ok:
events = json.loads(output)
print(f" ✓ events:recent returns valid JSON ({len(events)} events)")
passed += 1
else:
print(f" ✗ events:recent failed: {output[:80]}")
failed += 1
# Test events:recent:10
ok, output = send_cmd("events:recent:10")
if ok:
events = json.loads(output)
print(f" ✓ events:recent:10 returns valid JSON")
passed += 1
else:
print(f" ✗ events:recent:10 failed: {output[:80]}")
failed += 1
# Test events:since:0
ok, output = send_cmd("events:since:0")
if ok:
events = json.loads(output)
print(f" ✓ events:since:0 returns valid JSON ({len(events)} events)")
passed += 1
else:
print(f" ✗ events:since:0 failed: {output[:80]}")
failed += 1
return passed, failed
def main():
print("=" * 60)
print("Swarm Debug Socket Test Suite")
print("=" * 60)
# Check socket exists
if not os.path.exists(SOCKET_PATH):
print(f"\n✗ Debug socket not found at {SOCKET_PATH}")
print("Make sure jcode server is running with debug control enabled.")
print("Enable with: touch ~/.jcode/debug_control")
sys.exit(1)
total_passed = 0
total_failed = 0
test_funcs = [
test_basic_swarm_commands,
test_swarm_member_timestamps,
test_swarm_touches_timestamps,
test_swarm_session_details,
test_swarm_context_timestamps,
test_swarm_proposals,
test_swarm_touches_filtering,
test_swarm_conflicts_details,
test_swarm_id_provenance,
test_swarm_help,
test_event_commands,
]
for test_func in test_funcs:
try:
passed, failed = test_func()
total_passed += passed
total_failed += failed
except Exception as e:
print(f" ✗ Test crashed: {e}")
total_failed += 1
print("\n" + "=" * 60)
print(f"Results: {total_passed} passed, {total_failed} failed")
print("=" * 60)
sys.exit(0 if total_failed == 0 else 1)
if __name__ == "__main__":
main()