#!/usr/bin/env bash # End-to-end integration test: boot a dedicated server, connect two scripted # bot clients over a real ENet socket, and assert that the whole loop happened -- # handshake, lobby, portal into a dungeon, and the emergency escape back. # # This is the test that catches everything unit tests structurally cannot: RPC # wiring, codec round-trips over the wire, instance transfers, and the client's # reconciliation loop. Run it before calling any networking change done. # # tools/smoke.sh # ~35s # KEEP=1 tools/smoke.sh # keep the logs and print where they are set -uo pipefail cd "$(dirname "$0")/.." GODOT="${GODOT:-godot}" PORT="${PORT:-27099}" SERVER_TICKS="${SERVER_TICKS:-2000}" CLIENT_TICKS="${CLIENT_TICKS:-1700}" OUT="$(mktemp -d -t transcience-smoke-XXXXXX)" PIDS=() cleanup() { for pid in "${PIDS[@]:-}"; do kill "$pid" 2>/dev/null done wait 2>/dev/null if [[ "${KEEP:-0}" == "1" ]]; then echo "logs kept in $OUT" else rm -rf "$OUT" fi } trap cleanup EXIT echo "smoke: server on port $PORT, logs in $OUT" # A scratch character store: without it a rerun resumes the characters the # previous run created, and "a character was created" stops being true. "$GODOT" --headless --path . -- --server --port "$PORT" --autoquit "$SERVER_TICKS" \ --store "$OUT/characters.json" \ > "$OUT/server.log" 2>&1 & PIDS+=($!) for _ in $(seq 1 60); do grep -q "SERVER_READY" "$OUT/server.log" 2>/dev/null && break sleep 0.25 done if ! grep -q "SERVER_READY" "$OUT/server.log" 2>/dev/null; then echo "FAIL: server never became ready"; cat "$OUT/server.log"; exit 1 fi for n in 1 2; do "$GODOT" --headless --path . -- --bot --host 127.0.0.1 --port "$PORT" \ --name "bot$n" --account "$((7000 + n))" \ --autoquit "$CLIENT_TICKS" > "$OUT/bot$n.log" 2>&1 & PIDS+=($!) sleep 0.4 done # A fourth bot that leaves *politely* -- the same Net.shutdown() the in-game # menu's "Disconnect" button calls. The escape channel is keyed on the socket # closing, not on how it closed, so a clean exit must be caught exactly like the # SIGKILL below. If someone ever adds a "clean leave" message that bypasses the # channel, this is what catches it. "$GODOT" --headless --path . -- --bot --host 127.0.0.1 --port "$PORT" \ --name "leavebot" --account 7101 --leave-after 120 --autoquit "$CLIENT_TICKS" \ > "$OUT/leavebot.log" 2>&1 & PIDS+=($!) sleep 0.4 # A third bot that gets SIGKILLed the moment it is inside a dungeon. This is the # anti-disconnect-cheese path: the server must keep its body in the world, # channel it out over the same one second the escape button costs, and only then # forget the peer -- never delete it instantly on socket close. "$GODOT" --headless --path . -- --bot --host 127.0.0.1 --port "$PORT" \ --name "dropbot" --account 7102 --autoquit "$CLIENT_TICKS" > "$OUT/dropbot.log" 2>&1 & DROP_PID=$! for _ in $(seq 1 80); do grep -q "entered instance .*DUNGEON" "$OUT/dropbot.log" 2>/dev/null && break sleep 0.25 done if grep -q "entered instance .*DUNGEON" "$OUT/dropbot.log" 2>/dev/null; then # Reaped here so bash does not print its own "Killed" job-control line; the # kill is the point of the test, not a failure. { kill -9 "$DROP_PID"; wait "$DROP_PID"; } 2>/dev/null || true else echo " WARN dropbot never reached a dungeon; drop assertions will fail" PIDS+=("$DROP_PID") fi wait 2>/dev/null fails=0 check() { # check