e1f9fa8096
ci / verify (push) Successful in 45s
A failed connection left the connect buttons disabled forever. _show_menu() returned early when a menu already existed, so the set_busy(false) that re-enables them never ran on the way back from _abort_connect -- the player was locked out of both joining and hosting with no way out but a restart. Also guards against a second attempt stacking on an in-flight one. Contact damage is gone as a concept: nothing in this game hurts you by touching it, because every threat should be a bullet you can see and dodge. The Stalker walked at you and dealt contact damage and nothing else, so it now carries a point-blank shotgun instead -- five pellets, 62 degrees, 18-tick lifetime for about 78px of reach, so it still has to close and leaves nothing lingering. tests/unit/test_content.gd asserts every hostile enemy has an emitter, so a new enemy cannot quietly reintroduce the mechanic. The menu's "Disconnect" DOES get the anti-cheese protection -- verified, not assumed. It calls Net.shutdown(), the socket closes, and the server takes the same linkdead path as a SIGKILL, because the channel is keyed on the socket closing rather than on how it closed. Made permanent: a --leave-after bot flag and two smoke assertions covering the polite exit alongside the hard kill, so a future "clean leave" message that skipped the channel would fail a test. The menu now says so out loud in a dungeon -- the mechanic only reads as fair if the cost is known before clicking. 103 tests, 12 smoke assertions; check.sh, test.sh and smoke.sh all pass.
133 lines
5.0 KiB
Bash
Executable File
133 lines
5.0 KiB
Bash
Executable File
#!/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"
|
|
"$GODOT" --headless --path . -- --server --port "$PORT" --autoquit "$SERVER_TICKS" \
|
|
> "$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" --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" --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" --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 <label> <file> <pattern>
|
|
if grep -qE "$3" "$2"; then
|
|
echo " ok $1"
|
|
else
|
|
echo " FAIL $1 (no match for /$3/ in $(basename "$2"))"
|
|
fails=$((fails + 1))
|
|
fi
|
|
}
|
|
refute() { # refute <label> <file> <pattern>
|
|
local hits
|
|
hits=$(grep -cE "$3" "$2" || true)
|
|
if [[ "$hits" == "0" ]]; then
|
|
echo " ok $1"
|
|
else
|
|
echo " FAIL $1 ($hits line(s) matched /$3/ in $(basename "$2"))"
|
|
grep -E "$3" "$2" | head -5 | sed 's/^/ /'
|
|
fails=$((fails + 1))
|
|
fi
|
|
}
|
|
|
|
echo "assertions:"
|
|
check "server accepted bot1" "$OUT/server.log" "joined as 'bot1'"
|
|
check "server accepted bot2" "$OUT/server.log" "joined as 'bot2'"
|
|
check "a dungeon instance opened" "$OUT/server.log" "opened dungeon instance"
|
|
check "emergency escape completed" "$OUT/server.log" "escaped to lobby"
|
|
check "bot1 reached a dungeon" "$OUT/bot1.log" "entered instance .*DUNGEON"
|
|
check "bot1 returned to the lobby" "$OUT/bot1.log" "entered instance .*LOBBY"
|
|
check "a hard drop is channelled, not instant" \
|
|
"$OUT/server.log" "dropped in instance [0-9]+, channelling out"
|
|
check "the dropped body is released" "$OUT/server.log" "released from instance [0-9]+ after drop"
|
|
check "a polite disconnect leaves too" "$OUT/leavebot.log" "BOT_GRACEFUL_LEAVE"
|
|
check "a polite disconnect is also channelled" \
|
|
"$OUT/server.log" "'leavebot' dropped in instance [0-9]+, channelling out"
|
|
refute "no server script errors" "$OUT/server.log" "SCRIPT ERROR|Parse Error|USER ERROR"
|
|
refute "no client script errors" "$OUT/bot1.log" "SCRIPT ERROR|Parse Error|USER ERROR"
|
|
|
|
echo
|
|
if [[ $fails -eq 0 ]]; then
|
|
echo "SMOKE PASS"
|
|
else
|
|
echo "SMOKE FAIL ($fails)"
|
|
echo "--- server.log (tail) ---"; tail -40 "$OUT/server.log"
|
|
echo "--- bot1.log (tail) ---"; tail -40 "$OUT/bot1.log"
|
|
fi
|
|
exit $fails
|