#!/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"