-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_socket_api_full.py
More file actions
349 lines (287 loc) · 10.2 KB
/
test_socket_api_full.py
File metadata and controls
349 lines (287 loc) · 10.2 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
import pytest
import socket
import json
import glob
import time
import base64
import os
# --- Fixtures ---
@pytest.fixture(scope="session")
def socket_path():
"""Find the active Mesen2 socket."""
sockets = glob.glob("/tmp/mesen2-*.sock")
if not sockets:
pytest.skip("No Mesen2 socket found. Is Mesen running?")
# Sort by PID (best effort to find latest)
return sorted(sockets, key=lambda x: -int(x.split('-')[1].split('.')[0]))[0]
@pytest.fixture(scope="session")
def sock(socket_path):
"""Create a connection to the Mesen2 socket."""
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.settimeout(5.0)
try:
s.connect(socket_path)
yield s
finally:
s.close()
def send_command(sock, cmd_type, **params):
"""Helper to send command and return data."""
cmd = {"type": cmd_type}
cmd.update(params)
request = json.dumps(cmd) + "\n"
sock.sendall(request.encode())
response = b""
while True:
chunk = sock.recv(4096)
if not chunk:
break
response += chunk
if b"\n" in response:
break
result = json.loads(response.decode().strip())
# Allow caller to handle success/failure, but return whole object
return result
# --- Core Tests ---
def test_ping(sock):
res = send_command(sock, "PING")
assert res["success"]
assert res["data"] == "PONG"
def test_state(sock):
res = send_command(sock, "STATE")
assert res["success"]
data = res["data"]
assert "running" in data
assert "frame" in data
assert isinstance(data["frame"], int)
assert "lastSave" in data
assert "lastLoad" in data
if data["lastSave"] is not None:
assert "success" in data["lastSave"]
if data["lastLoad"] is not None:
assert "success" in data["lastLoad"]
def test_health(sock):
res = send_command(sock, "HEALTH")
assert res["success"]
data = res["data"]
# Check for enhanced health fields
assert "running" in data
if "diagnostics" in data:
assert "registeredAgents" in data["diagnostics"]
# --- Control Tests ---
def test_pause_resume(sock):
# Pause
res = send_command(sock, "PAUSE")
assert res["success"]
# Wait for state to update
for _ in range(10):
state = send_command(sock, "STATE")
if state["data"]["paused"] is True:
break
time.sleep(0.05)
else:
pytest.fail("State did not update to paused")
# Frame advance while paused
res = send_command(sock, "FRAME")
assert res["success"]
# Resume
res = send_command(sock, "RESUME")
assert res["success"]
# Wait for state to update
for _ in range(10):
state = send_command(sock, "STATE")
if state["data"]["paused"] is False:
break
time.sleep(0.05)
else:
pytest.fail("State did not update to running")
def test_savestate_pause_param(sock):
res = send_command(sock, "SAVESTATE", slot="1", pause="true")
assert res["success"]
res = send_command(sock, "LOADSTATE", slot="1", pause="true")
assert res["success"]
def test_step(sock):
send_command(sock, "PAUSE")
try:
cpu_before = send_command(sock, "CPU")["data"]
cycles_before = cpu_before["cycles"]
# Step 100 instructions to be absolutely sure cycles move
res = send_command(sock, "STEP", count="100")
assert res["success"]
# Wait a bit for emulator state to settle if needed
time.sleep(0.1)
cpu_after = send_command(sock, "CPU")["data"]
cycles_after = cpu_after["cycles"]
assert cycles_after > cycles_before
finally:
send_command(sock, "RESUME")
# ...
def test_search(sock):
# Write a unique pattern
addr = "0x7E0100"
send_command(sock, "WRITE", addr=addr, value="0xDE")
send_command(sock, "WRITE", addr="0x7E0101", value="0xAD")
# Verify write
check = send_command(sock, "READBLOCK", addr=addr, len="2")
val = check["data"].replace('"', '').replace('0x', '')
assert "dead" in val.lower()
# Use SnesMemory to support absolute SNES addresses
res = send_command(sock, "SEARCH", pattern="DE AD", memtype="SnesMemory", start="0x7E0000", end="0x7E0200")
assert res["success"]
matches = res["data"]["matches"]
target = 0x7E0100
found = False
for m in matches:
if isinstance(m, str):
val = int(m.replace('"', '').replace("0x", ""), 16)
else:
val = m
if val == target:
found = True
break
if not found:
print(f"DEBUG: Search matches: {matches}")
assert found
# --- Discovery Tests ---
def test_capabilities(sock):
res = send_command(sock, "CAPABILITIES")
assert res["success"]
data = res["data"]
if isinstance(data, str):
data = json.loads(data)
assert "version" in data
assert "features" in data
def test_help_list(sock):
res = send_command(sock, "HELP")
assert res["success"]
data = res["data"]
if isinstance(data, str):
data = json.loads(data)
assert "commands" in data
assert len(data["commands"]) > 30
# --- ALTTP Specifics ---
def test_gamestate(sock):
# This might fail if not playing Zelda 3, so we check error code or success
res = send_command(sock, "GAMESTATE")
if res["success"]:
data = res["data"]
# If success, must have structure
if "link" in data:
assert "x" in data["link"]
assert "y" in data["link"]
else:
# If failed, acceptable if game specific
pass
def test_stateinspect_includes_gamestate(sock):
res = send_command(sock, "STATEINSPECT", includeGameState="true")
assert res["success"]
data = res["data"]
assert "watchEntries" in data
if "gameState" in data:
assert isinstance(data["gameState"], dict)
def test_sprites(sock):
res = send_command(sock, "SPRITES")
if res["success"]:
data = res["data"]
assert "sprites" in data
assert isinstance(data["sprites"], list)
# --- Agent Features ---
def test_agent_register(sock):
res = send_command(sock, "AGENT_REGISTER", agentId="pytest_runner", agentName="Pytest", version="2.0")
assert res["success"]
assert res["data"]["registered"] is True
def test_metrics(sock):
res = send_command(sock, "METRICS")
assert res["success"]
assert "avgLatencyUs" in res["data"]
def test_batch(sock):
cmds = json.dumps([
{"type": "PING"},
{"type": "CPU"}
])
res = send_command(sock, "BATCH", commands=cmds)
assert res["success"]
# Check if data is already a dict or needs parsing
if isinstance(res["data"], str):
results = json.loads(res["data"])["results"]
else:
results = res["data"]["results"]
assert len(results) == 2
assert results[0]["data"] == "PONG"
assert "pc" in results[1]["data"] # lowercase
# --- Input ---
def test_input_macro(sock):
# Simple input test (don't hold too long)
res = send_command(sock, "INPUT", buttons="A", frames="1")
assert res["success"]
# --- Advanced Agentic Debugging Tests ---
def test_p_register_tracking(sock):
# Start tracking
res = send_command(sock, "P_WATCH", action="start", depth="100")
assert res["success"]
assert res["data"]["enabled"] is True
# Run some frames to generate changes
send_command(sock, "RESUME")
time.sleep(0.1)
send_command(sock, "PAUSE")
# Check log
res = send_command(sock, "P_LOG", count="10")
assert res["success"]
data = res["data"]
# Entries might be empty if P didn't change, but structure should be there
assert "entries" in data
assert "total" in data
# Stop tracking
res = send_command(sock, "P_WATCH", action="stop")
assert res["success"]
def test_memory_write_attribution(sock):
# Watch Link's X position (ALTTP) or similar active address
addr = "0x7E0022"
res = send_command(sock, "MEM_WATCH_WRITES", action="add", addr=addr, size="2", depth="10")
assert res["success"]
watch_id = res["data"]["watch_id"]
# Run some frames to allow Link to move or the game to update state
send_command(sock, "RESUME")
time.sleep(0.2)
send_command(sock, "PAUSE")
# Check blame
res = send_command(sock, "MEM_BLAME", watch_id=str(watch_id))
assert res["success"]
# We don't strictly assert len > 0 because Link might not move, but we check command works
# Cleanup
send_command(sock, "MEM_WATCH_WRITES", action="remove", watch_id=str(watch_id))
def test_trace_execution(sock):
# Trace usually returns recent execution
res = send_command(sock, "TRACE", count="10")
assert res["success"]
assert "entries" in res["data"]
assert len(res["data"]["entries"]) <= 10
def test_symbols_integration(sock):
# Use the discovered oos.mlb
mlb_path = "/Users/scawful/src/hobby/oracle-of-secrets/Roms/oos.mlb"
if not os.path.exists(mlb_path):
pytest.skip(f"Symbol file not found: {mlb_path}")
res = send_command(sock, "SYMBOLS_LOAD", path=mlb_path)
# This might fail if the ROM doesn't match or path is inaccessible to Mesen
if res["success"]:
# Try to resolve a known label if possible, or just check success
res = send_command(sock, "SYMBOLS_RESOLVE", addr="0x008000")
assert res["success"]
# Resolving might return None if no label at address, but command succeeds
else:
# If it fails, check if it's because of path
print(f"DEBUG: SYMBOLS_LOAD failed: {res.get('error')}")
def test_collision_overlay(sock):
res = send_command(sock, "COLLISION_OVERLAY")
assert res["success"]
assert "enabled" in res["data"]
# Toggle (smoke test)
send_command(sock, "COLLISION_OVERLAY", enabled="true", colmap="A")
res = send_command(sock, "COLLISION_OVERLAY")
assert res["data"]["enabled"] is True
send_command(sock, "COLLISION_OVERLAY", enabled="false")
def test_collision_dump(sock):
res = send_command(sock, "COLLISION_DUMP", colmap="A")
# Might fail if colmap not loaded
if res["success"]:
assert "data" in res["data"]
assert "width" in res["data"]