Files
EVEngine/scripts/test_devlab.sh
T

179 lines
4.7 KiB
Bash

#!/usr/bin/env bash
# DevLab developer-experience behavior test.
#
# Verifies the two claims examples/devlab is built around, against a real
# running engine:
# 1. `--debug` attaches DevTools (MCP attached, script state reachable);
# 2. editing a script function triggers soft hot-reload without restart.
#
# With `--debug` the engine captures Squirrel print() into the DevTools console,
# so this test verifies through the embedded MCP server instead of grepping
# stdout: "MCP listening" (stderr) proves DevTools attached, then a tiny Python
# client evaluates lab.reloads before/after editing the script.
#
# The example is copied to a temp dir so the repo stays clean.
#
# Usage:
# scripts/test_devlab.sh [path-to-eve] # e.g. build/linux-debug/src/engine/eve
# EVE=... MCP_PORT=7530 scripts/test_devlab.sh
set -euo pipefail
EVE_BIN="${1:-${EVE:-build/linux-debug/src/engine/eve}}"
MCP_PORT="${MCP_PORT:-7530}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
cp -R "$ROOT/examples/devlab" "$WORK/devlab"
log="$WORK/devlab.log"
(
cd "$WORK/devlab"
"$EVE_BIN" run --debug --mcp-port="$MCP_PORT" >"$log" 2>&1
) &
pid=$!
wait_for() {
local needle="$1" timeout="${2:-15}" waited=0
while [ "$waited" -lt "$timeout" ]; do
if grep -qF "$needle" "$log" 2>/dev/null; then
return 0
fi
if ! kill -0 "$pid" 2>/dev/null; then
return 1
fi
sleep 1
waited=$((waited + 1))
done
return 1
}
fail() {
echo "FAIL: $1"
tail -n 15 "$log" 2>/dev/null | sed 's/^/ | /'
kill "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
exit 1
}
wait_for "MCP listening on 127.0.0.1:$MCP_PORT" 30 \
|| fail "DevTools/MCP did not start (did the build include DevTools?)"
wait_for "hot-reload: watching" 30 \
|| fail "hot-reload watcher did not start"
# Hot-edit the bounce coefficient in the copied game: 0.85 -> 0.5.
sed -i 's/return (v < 0.0 || v > maxV) ? -0.85 : 1.0;/return (v < 0.0 || v > maxV) ? -0.5 : 1.0;/' \
"$WORK/devlab/main.nut"
python3 - "$MCP_PORT" "$log" <<'PY'
import json
import socket
import sys
import time
port = int(sys.argv[1])
eve_log = sys.argv[2]
s = socket.create_connection(("127.0.0.1", port), timeout=30)
buf = b""
seq = 0
def dump_log():
try:
with open(eve_log, encoding="utf-8", errors="replace") as f:
lines = f.readlines()
print("--- eve log tail ---")
print("".join(lines[-30:]))
except OSError:
pass
def send(obj):
s.sendall((json.dumps(obj) + "\n").encode())
def recv(req_id):
global buf
while True:
while b"\n" in buf:
line, buf = buf.split(b"\n", 1)
if not line.strip():
continue
msg = json.loads(line)
if msg.get("id") == req_id:
return msg
chunk = s.recv(65536)
if not chunk:
raise RuntimeError("MCP connection closed")
buf += chunk
def call(method, params=None):
global seq
seq += 1
req = {"jsonrpc": "2.0", "id": seq, "method": method}
if params is not None:
req["params"] = params
send(req)
return recv(seq)
def tool(name, args=None):
r = call("tools/call", {"name": name, "arguments": args or {}})
return "\n".join(
c.get("text", "")
for c in r.get("result", {}).get("content", [])
if c.get("type") == "text"
)
call(
"initialize",
{"protocolVersion": "2025-06-18", "clientInfo": {"name": "devlab-test", "version": "1"}},
)
send({"jsonrpc": "2.0", "method": "notifications/initialized"})
time.sleep(0.2)
status = ""
for attempt in range(5):
try:
status = tool("eve_status")
if '"attached":true' in status:
break
except Exception as exc: # noqa: BLE001 - report + retry
time.sleep(2)
continue
if '"attached":true' not in status:
dump_log()
print(f"FAIL: DevTools not attached: {status[:200]}")
sys.exit(1)
# The script was edited before this client connected; wait for the reload.
deadline = time.time() + 30
while time.time() < deadline:
try:
r = tool("eve_eval", {"expression": "lab.reloads"})
except Exception as exc: # noqa: BLE001 - report + retry
r = f"error: {exc}"
time.sleep(1)
if '"value":"1"' in r or '"value":1' in r:
print("PASS: DevLab — MCP attached and script hot-reload verified on a live game.")
sys.exit(0)
time.sleep(0.5)
dump_log()
print(f"FAIL: hot reload counter never reached 1 (last eval: {r})")
sys.exit(1)
PY
rc=$?
if [ "$rc" -ne 0 ]; then
echo " --- eve log tail ---"
tail -n 30 "$log" 2>/dev/null | sed 's/^/ | /'
fi
kill "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
exit "$rc"