Files
wehub-resource-sync a789495a98
CI / Quality Guardrails (push) Waiting to run
CI / Build & Test (macos-latest) (push) Waiting to run
CI / Build & Test (ubuntu-latest) (push) Waiting to run
CI / Build & Test (windows-latest) (push) Waiting to run
CI / Format (push) Waiting to run
CI / PowerShell Syntax (push) Waiting to run
CI / Windows Cross-Target Check (Linux) (push) Waiting to run
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

474 lines
15 KiB
Python
Executable File

#!/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()