chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
This commit is contained in:
Executable
+282
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Comprehensive memory system test for jcode.
|
||||
|
||||
Tests all memory features with both Claude and OpenAI providers via the debug socket.
|
||||
|
||||
Usage:
|
||||
# With existing server (uses /run/user/1000/jcode-debug.sock)
|
||||
./scripts/test_memory.py
|
||||
|
||||
# Start fresh server for testing
|
||||
./scripts/test_memory.py --fresh
|
||||
|
||||
# Test specific provider only
|
||||
./scripts/test_memory.py --provider claude
|
||||
"""
|
||||
|
||||
import socket
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import re
|
||||
import argparse
|
||||
|
||||
# Colors
|
||||
GREEN = '\033[92m'
|
||||
RED = '\033[91m'
|
||||
YELLOW = '\033[93m'
|
||||
RESET = '\033[0m'
|
||||
|
||||
def log(msg, color=None):
|
||||
if color:
|
||||
print(f"{color}{msg}{RESET}")
|
||||
else:
|
||||
print(msg)
|
||||
|
||||
def log_pass(msg): log(f" ✓ {msg}", GREEN)
|
||||
def log_fail(msg): log(f" ✗ {msg}", RED)
|
||||
def log_section(msg): log(f"\n{'='*60}\n{msg}\n{'='*60}", YELLOW)
|
||||
|
||||
class DebugSocketClient:
|
||||
def __init__(self, socket_path):
|
||||
self.socket_path = socket_path
|
||||
self.sock = None
|
||||
|
||||
def connect(self):
|
||||
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.sock.connect(self.socket_path)
|
||||
self.sock.settimeout(120)
|
||||
|
||||
def close(self):
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
|
||||
def send(self, cmd, session_id=None):
|
||||
req = {"type": "debug_command", "id": 1, "command": cmd}
|
||||
if session_id:
|
||||
req["session_id"] = session_id
|
||||
self.sock.send((json.dumps(req) + '\n').encode())
|
||||
data = self.sock.recv(65536).decode()
|
||||
resp = json.loads(data)
|
||||
return resp.get('ok'), resp.get('output', '')
|
||||
|
||||
def run_tests(client, providers):
|
||||
results = {"passed": 0, "failed": 0}
|
||||
|
||||
def check(condition, msg):
|
||||
if condition:
|
||||
log_pass(msg)
|
||||
results["passed"] += 1
|
||||
else:
|
||||
log_fail(msg)
|
||||
results["failed"] += 1
|
||||
return condition
|
||||
|
||||
for provider in providers:
|
||||
log_section(f"Testing with {provider.upper()}")
|
||||
|
||||
# Create session
|
||||
ok, output = client.send(f"create_session:/tmp/memory-test-{provider}")
|
||||
if not ok:
|
||||
log_fail(f"Failed to create session: {output}")
|
||||
continue
|
||||
session_id = json.loads(output)['session_id']
|
||||
check(ok, f"Created session: {session_id}")
|
||||
|
||||
# Switch provider
|
||||
ok, output = client.send(f"set_provider:{provider}", session_id)
|
||||
check(ok, f"Switched to {provider}")
|
||||
if ok:
|
||||
state = json.loads(output)
|
||||
log(f" Provider: {state['provider']}, Model: {state['model']}")
|
||||
|
||||
# Test 1: Memory remember with tags
|
||||
log("\n --- Memory Remember ---")
|
||||
ok, output = client.send(
|
||||
f'tool:memory {{"action":"remember","content":"User prefers {provider} for testing","tags":["test","provider-pref"]}}',
|
||||
session_id)
|
||||
check(ok and "Remembered" in output, "Remember with tags")
|
||||
|
||||
# Extract memory ID
|
||||
result = json.loads(output) if output.startswith('{') else {'output': output}
|
||||
match = re.search(r'id: (mem_\d+_\d+)', result.get('output', output))
|
||||
mem_id = match.group(1) if match else None
|
||||
if mem_id:
|
||||
log(f" Memory ID: {mem_id}")
|
||||
|
||||
# Test 2: Memory list
|
||||
log("\n --- Memory List ---")
|
||||
ok, output = client.send('tool:memory {"action":"list"}', session_id)
|
||||
check(ok and provider in output, "List shows our memory")
|
||||
|
||||
# Test 3: Memory search (keyword)
|
||||
log("\n --- Memory Search (keyword) ---")
|
||||
ok, output = client.send(f'tool:memory {{"action":"search","query":"{provider} testing"}}', session_id)
|
||||
check(ok, f"Search for '{provider} testing'")
|
||||
|
||||
# Test 3b: Enhanced recall with query (semantic search)
|
||||
log("\n --- Enhanced Recall (semantic) ---")
|
||||
ok, output = client.send(
|
||||
f'tool:memory {{"action":"recall","query":"testing preferences","mode":"cascade"}}',
|
||||
session_id)
|
||||
result = json.loads(output) if output.startswith('{') else {'output': output}
|
||||
found_semantic = "relevant" in result.get('output', output).lower() or "memories" in result.get('output', output).lower()
|
||||
check(ok and found_semantic, "Semantic recall with cascade")
|
||||
if ok:
|
||||
log(f" {result.get('output', output)[:150]}...")
|
||||
|
||||
# Test 3c: Recall recent (no query)
|
||||
log("\n --- Recall Recent ---")
|
||||
ok, output = client.send('tool:memory {"action":"recall","limit":5}', session_id)
|
||||
result = json.loads(output) if output.startswith('{') else {'output': output}
|
||||
check(ok and "memories" in result.get('output', output).lower(), "Recall recent memories")
|
||||
|
||||
# Test 4: Memory tag (using correct 'id' field)
|
||||
log("\n --- Memory Tag ---")
|
||||
if mem_id:
|
||||
ok, output = client.send(
|
||||
f'tool:memory {{"action":"tag","id":"{mem_id}","tags":["extra-tag-{provider}"]}}',
|
||||
session_id)
|
||||
check(ok and "Tagged" in output, f"Added tag to memory")
|
||||
else:
|
||||
log_fail("No memory ID to tag")
|
||||
results["failed"] += 1
|
||||
|
||||
# Test 5: Create second memory and link
|
||||
log("\n --- Memory Link ---")
|
||||
ok, output = client.send(
|
||||
f'tool:memory {{"action":"remember","content":"Second memory for {provider} link test"}}',
|
||||
session_id)
|
||||
result = json.loads(output) if output.startswith('{') else {'output': output}
|
||||
match2 = re.search(r'id: (mem_\d+_\d+)', result.get('output', output))
|
||||
mem_id2 = match2.group(1) if match2 else None
|
||||
|
||||
if mem_id and mem_id2:
|
||||
ok, output = client.send(
|
||||
f'tool:memory {{"action":"link","from_id":"{mem_id}","to_id":"{mem_id2}","weight":0.75}}',
|
||||
session_id)
|
||||
check(ok and "Linked" in output, "Linked two memories")
|
||||
else:
|
||||
log_fail("Missing memory IDs for link")
|
||||
results["failed"] += 1
|
||||
|
||||
# Test 6: Memory related (using correct 'id' field)
|
||||
log("\n --- Memory Related (Graph Traversal) ---")
|
||||
if mem_id:
|
||||
ok, output = client.send(
|
||||
f'tool:memory {{"action":"related","id":"{mem_id}","depth":2}}',
|
||||
session_id)
|
||||
result = json.loads(output) if output.startswith('{') else {'output': output}
|
||||
found_related = "Found" in result.get('output', output) or "related" in result.get('output', output).lower()
|
||||
check(ok and found_related, "Related memories query via graph")
|
||||
if ok:
|
||||
log(f" {result.get('output', output)[:100]}...")
|
||||
else:
|
||||
log_fail("No memory ID for related query")
|
||||
results["failed"] += 1
|
||||
|
||||
# Test 7: Send messages for extraction test
|
||||
log("\n --- Message Exchange ---")
|
||||
messages = [
|
||||
f"Hello, I'm testing with {provider}",
|
||||
"Remember that my favorite editor is vim",
|
||||
"What text editors do you know about?",
|
||||
"Thanks for the information!"
|
||||
]
|
||||
all_ok = True
|
||||
for i, msg in enumerate(messages):
|
||||
ok, output = client.send(f"message:{msg}", session_id)
|
||||
if not ok:
|
||||
all_ok = False
|
||||
log_fail(f"Message {i+1} failed: {output[:50]}")
|
||||
check(all_ok, f"Sent {len(messages)} messages")
|
||||
|
||||
# Test 8: Trigger extraction
|
||||
log("\n --- Trigger Extraction ---")
|
||||
ok, output = client.send("trigger_extraction", session_id)
|
||||
check(ok, "Trigger extraction")
|
||||
if ok:
|
||||
result = json.loads(output)
|
||||
log(f" Extracted: {result.get('extracted', 0)} memories from {result.get('message_count', 0)} messages")
|
||||
|
||||
# Cleanup
|
||||
client.send(f"destroy_session:{session_id}")
|
||||
log_pass(f"Destroyed session")
|
||||
|
||||
return results
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Test jcode memory system')
|
||||
parser.add_argument('--fresh', action='store_true', help='Start fresh server for testing')
|
||||
parser.add_argument('--provider', choices=['claude', 'openai'], help='Test specific provider only')
|
||||
parser.add_argument('--socket', help='Custom debug socket path')
|
||||
args = parser.parse_args()
|
||||
|
||||
providers = [args.provider] if args.provider else ['claude', 'openai']
|
||||
|
||||
proc = None
|
||||
if args.fresh:
|
||||
log_section("Starting fresh test server...")
|
||||
test_socket = '/tmp/jcode-memory-test.sock'
|
||||
debug_socket = test_socket.replace('.sock', '-debug.sock')
|
||||
|
||||
for s in [test_socket, debug_socket]:
|
||||
if os.path.exists(s):
|
||||
os.remove(s)
|
||||
|
||||
env = os.environ.copy()
|
||||
env['JCODE_DEBUG_CONTROL'] = '1'
|
||||
env['JCODE_SOCKET'] = test_socket
|
||||
|
||||
proc = subprocess.Popen(
|
||||
['jcode', 'serve'],
|
||||
env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
for i in range(100):
|
||||
if os.path.exists(debug_socket):
|
||||
break
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
proc.terminate()
|
||||
log_fail("Server failed to start")
|
||||
sys.exit(1)
|
||||
|
||||
log_pass(f"Server started (PID {proc.pid})")
|
||||
socket_path = debug_socket
|
||||
else:
|
||||
socket_path = args.socket or '/run/user/1000/jcode-debug.sock'
|
||||
if not os.path.exists(socket_path):
|
||||
log_fail(f"Debug socket not found: {socket_path}")
|
||||
log("Use --fresh to start a test server, or ensure jcode is running")
|
||||
sys.exit(1)
|
||||
|
||||
client = DebugSocketClient(socket_path)
|
||||
|
||||
try:
|
||||
client.connect()
|
||||
results = run_tests(client, providers)
|
||||
|
||||
log_section("TEST RESULTS")
|
||||
total = results["passed"] + results["failed"]
|
||||
log(f"Passed: {results['passed']}/{total}", GREEN if results["failed"] == 0 else YELLOW)
|
||||
if results["failed"] > 0:
|
||||
log(f"Failed: {results['failed']}/{total}", RED)
|
||||
|
||||
client.close()
|
||||
|
||||
finally:
|
||||
if proc:
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
log("Server stopped")
|
||||
|
||||
sys.exit(0 if results["failed"] == 0 else 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user