diff --git a/.gitea/workflows/smoke-tests.yml b/.gitea/workflows/smoke-tests.yml index 23c43b9..e048653 100644 --- a/.gitea/workflows/smoke-tests.yml +++ b/.gitea/workflows/smoke-tests.yml @@ -2,19 +2,27 @@ name: Smoke tests on: workflow_dispatch: push: + pull_request: jobs: smoke: name: Build and smoke test runs-on: ubuntu-latest + env: + TEST_DB: ./mudserver.db.test steps: - name: Checkout Code uses: actions/checkout@v4 - - name: Install Rust + - name: Install needed tools + env: + DEBIAN_FRONTEND: noninteractive run: | + set -e + sudo apt-get update + sudo apt-get install -y --no-install-recommends openssh-client netcat-openbsd ca-certificates curl curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - echo "$HOME/.cargo/bin" >> $GITHUB_PATH + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: Build run: cargo build @@ -22,5 +30,143 @@ jobs: - name: Validate world data run: ./target/debug/mudtool validate -w ./world - - name: Run smoke tests - run: ./run-tests.sh + - name: Reset smoke database + run: rm -f "$TEST_DB" + + - name: Smoke - new player and basics + run: | + set -euo pipefail + RUST_LOG=info ./target/debug/mudserver -d "$TEST_DB" & + MUD_PID=$! + trap 'kill $MUD_PID 2>/dev/null || true' EXIT + bash scripts/ci/wait-for-tcp.sh 127.0.0.1 2222 + set +e + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' + 1 + 1 + look + stats + go south + go down + go north + talk barkeep + go south + go south + examine thief + attack thief + flee + quit + EOF + r=$? + set -e + [[ $r -eq 0 || $r -eq 255 ]] + + - name: Smoke - persistence (reconnect) + run: | + set -euo pipefail + RUST_LOG=info ./target/debug/mudserver -d "$TEST_DB" & + MUD_PID=$! + trap 'kill $MUD_PID 2>/dev/null || true' EXIT + bash scripts/ci/wait-for-tcp.sh 127.0.0.1 2222 + set +e + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' + look + stats + quit + EOF + r=$? + set -e + [[ $r -eq 0 || $r -eq 255 ]] + + - name: Smoke - mudtool admin + run: | + set -euo pipefail + ./target/debug/mudtool -d "$TEST_DB" players list + ./target/debug/mudtool -d "$TEST_DB" players set-admin smoketest true + ./target/debug/mudtool -d "$TEST_DB" players show smoketest + ./target/debug/mudtool -d "$TEST_DB" settings set registration_open false + ./target/debug/mudtool -d "$TEST_DB" settings list + + - name: Smoke - in-game admin + run: | + set -euo pipefail + RUST_LOG=info ./target/debug/mudserver -d "$TEST_DB" & + MUD_PID=$! + trap 'kill $MUD_PID 2>/dev/null || true' EXIT + bash scripts/ci/wait-for-tcp.sh 127.0.0.1 2222 + set +e + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' + admin help + admin list + admin registration on + admin info smoketest + quit + EOF + r=$? + set -e + [[ $r -eq 0 || $r -eq 255 ]] + + - name: Smoke - registration gate + run: | + set -euo pipefail + ./target/debug/mudtool -d "$TEST_DB" settings set registration_open false + RUST_LOG=info ./target/debug/mudserver -d "$TEST_DB" & + MUD_PID=$! + trap 'kill $MUD_PID 2>/dev/null || true' EXIT + bash scripts/ci/wait-for-tcp.sh 127.0.0.1 2222 + set +e + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 newplayer@localhost <<'EOF' + quit + EOF + r=$? + set -e + [[ $r -eq 0 || $r -eq 255 ]] + + - name: Smoke - tick-based combat + run: | + set -euo pipefail + ./target/debug/mudtool -d "$TEST_DB" settings set registration_open true + ./target/debug/mudtool -d "$TEST_DB" players delete smoketest + RUST_LOG=info ./target/debug/mudserver -d "$TEST_DB" & + MUD_PID=$! + trap 'kill $MUD_PID 2>/dev/null || true' EXIT + bash scripts/ci/wait-for-tcp.sh 127.0.0.1 2222 + set +e + ( + echo "1" + echo "1" + echo "go south" + echo "go down" + echo "go south" + echo "attack thief" + sleep 8 + echo "stats" + echo "quit" + ) | ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost + r=$? + set -e + [[ $r -eq 0 || $r -eq 255 ]] + ./target/debug/mudtool -d "$TEST_DB" settings set registration_open true + ./target/debug/mudtool -d "$TEST_DB" players delete smoketest + + - name: Smoke - JSON-RPC list_commands + run: | + set -euo pipefail + RUST_LOG=info ./target/debug/mudserver -d "$TEST_DB" & + MUD_PID=$! + trap 'kill $MUD_PID 2>/dev/null || true' EXIT + bash scripts/ci/wait-for-tcp.sh 127.0.0.1 2222 + set +e + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 rpctest@localhost <<'EOF' + 1 + 1 + quit + EOF + r=$? + set -e + [[ $r -eq 0 || $r -eq 255 ]] + echo '{"_jsonrpc": "2.0", "method": "login", "params": {"username": "rpctest"}, "id": 1}' | nc -w 2 localhost 2223 > rpc_resp.json + echo '{"_jsonrpc": "2.0", "method": "list_commands", "params": {}, "id": 2}' | nc -w 2 localhost 2223 >> rpc_resp.json + grep -q '"shop"' rpc_resp.json + rm rpc_resp.json + ./target/debug/mudtool -d "$TEST_DB" players delete rpctest diff --git a/TESTING.md b/TESTING.md index e28ad1d..643117b 100644 --- a/TESTING.md +++ b/TESTING.md @@ -6,9 +6,9 @@ ./run-tests.sh ``` -This builds the server and mudtool, starts the server with a temporary DB, runs the smoke test below (new player, persistence, admin, registration gate, combat), then cleans up. Use it locally to match what Gitea Actions run on push/pull_request. The script uses `MUD_TEST_DB` (default `./mudserver.db.test`) so it does not overwrite your normal `mudserver.db`. +This builds the server and mudtool, starts the server with a temporary DB, and runs the same sequence as the smoke steps in [`.gitea/workflows/smoke-tests.yml`](.gitea/workflows/smoke-tests.yml) (new player, persistence, mudtool admin, in-game admin, registration gate, tick combat), then cleans up. Use `MUD_TEST_DB` (default `./mudserver.db.test`) so you do not overwrite your normal `mudserver.db`. -Prerequisites: Rust toolchain (cargo), ssh client. In CI, Rust is installed by the workflow. +Prerequisites: Rust toolchain (cargo), OpenSSH client, and OpenBSD `nc` (`netcat-openbsd` on Debian/Ubuntu) for [`scripts/ci/wait-for-tcp.sh`](scripts/ci/wait-for-tcp.sh). CI installs these explicitly before the smoke steps. --- @@ -247,90 +247,4 @@ Run through the checks below before every commit to ensure consistent feature co ## Quick Smoke Test Script -The canonical implementation is **`./run-tests.sh`** (see top of this file). The following is the same sequence for reference; when writing or extending tests, keep `run-tests.sh` and this section in sync. - -```bash -# Start server in background (use -d for test DB so you don't overwrite mudserver.db) -TEST_DB=./mudserver.db.test -RUST_LOG=info ./target/debug/mudserver -d "$TEST_DB" & -SERVER_PID=$! -sleep 2 - -# Test 1: New player creation + basic commands -ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' -1 -1 -look -stats -go north -talk barkeep -go south -go south -examine thief -attack thief -flee -quit -EOF - -# Test 2: Persistence - reconnect -ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' -look -stats -quit -EOF - -# Test 3: Admin via mudtool (use same test DB) -./target/debug/mudtool -d "$TEST_DB" players list -./target/debug/mudtool -d "$TEST_DB" players set-admin smoketest true -./target/debug/mudtool -d "$TEST_DB" players show smoketest -./target/debug/mudtool -d "$TEST_DB" settings set registration_open false -./target/debug/mudtool -d "$TEST_DB" settings list - -# Test 4: Admin commands in-game -ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' -admin help -admin list -admin registration on -admin info smoketest -quit -EOF - -# Test 5: Registration gate -./target/debug/mudtool -d "$TEST_DB" settings set registration_open false -ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 newplayer@localhost <<'EOF' -quit -EOF - -# Test 6: Tick-based combat (connect and wait for ticks) -./target/debug/mudtool -d "$TEST_DB" settings set registration_open true -./target/debug/mudtool -d "$TEST_DB" players delete smoketest -ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' -1 -1 -go south -go south -attack thief -EOF -# Wait for several combat ticks to resolve -sleep 8 -ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' -stats -quit -EOF -# Verify XP changed (combat happened via ticks) - -# Test 7: JSON-RPC interface and dynamic command list -ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 rpctest@localhost <<'EOF' -1 -1 -quit -EOF -echo '{"_jsonrpc": "2.0", "method": "login", "params": {"username": "rpctest"}, "id": 1}' | nc -w 2 localhost 2223 -echo '{"_jsonrpc": "2.0", "method": "list_commands", "params": {}, "id": 2}' | nc -w 2 localhost 2223 - -# Cleanup -./target/debug/mudtool -d "$TEST_DB" settings set registration_open true -./target/debug/mudtool -d "$TEST_DB" players delete smoketest -./target/debug/mudtool -d "$TEST_DB" players delete rpctest -kill $SERVER_PID -``` +**CI:** each scenario is a separate step in [`.gitea/workflows/smoke-tests.yml`](.gitea/workflows/smoke-tests.yml) (each SSH step starts `mudserver`, runs the block, stops it; the same `TEST_DB` file carries state between steps). The last step exercises JSON-RPC `login` / `list_commands` on port 2223 (expects `shop` in the command list). **Local:** **`./run-tests.sh`** runs the full sequence in one process. When you add or change coverage, update the workflow steps and `run-tests.sh` together, and keep the checklist sections above aligned. diff --git a/run-tests.sh b/run-tests.sh index becd4a8..0832e86 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash set -ex +ROOT="$(cd "$(dirname "$0")" && pwd)" +cd "$ROOT" + TEST_DB=${MUD_TEST_DB:-./mudserver.db.test} SERVER_PID= -# SSH returns 255 when MUD closes connection after quit — treat as success ssh_mud() { set +e ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 "$@" @@ -23,9 +25,8 @@ trap cleanup EXIT cargo build RUST_LOG=info ./target/debug/mudserver -d "$TEST_DB" & SERVER_PID=$! -sleep 2 +bash "$ROOT/scripts/ci/wait-for-tcp.sh" 127.0.0.1 2222 -# Test 1: New player creation + basic commands ssh_mud smoketest@localhost <<'EOF' 1 1 @@ -43,21 +44,18 @@ flee quit EOF -# Test 2: Persistence - reconnect ssh_mud smoketest@localhost <<'EOF' look stats quit EOF -# Test 3: Admin via mudtool (use same test DB) ./target/debug/mudtool -d "$TEST_DB" players list ./target/debug/mudtool -d "$TEST_DB" players set-admin smoketest true ./target/debug/mudtool -d "$TEST_DB" players show smoketest ./target/debug/mudtool -d "$TEST_DB" settings set registration_open false ./target/debug/mudtool -d "$TEST_DB" settings list -# Test 4: Admin commands in-game ssh_mud smoketest@localhost <<'EOF' admin help admin list @@ -66,16 +64,13 @@ admin info smoketest quit EOF -# Test 5: Registration gate ./target/debug/mudtool -d "$TEST_DB" settings set registration_open false ssh_mud newplayer@localhost <<'EOF' quit EOF -# Test 6: Tick-based combat (connect and wait for ticks) ./target/debug/mudtool -d "$TEST_DB" settings set registration_open true ./target/debug/mudtool -d "$TEST_DB" players delete smoketest -# Use subshell to pipe commands with a delay between them while staying connected ( echo "1" echo "1" @@ -88,9 +83,6 @@ EOF echo "quit" ) | ssh_mud smoketest@localhost -# Test 7: JSON-RPC interface and dynamic command list -# We need an active player for the login to succeed in mud-mcp style -# (though list_commands doesn't require session, the MCP does login on startup) ssh_mud rpctest@localhost <<'EOF' 1 1 @@ -107,7 +99,6 @@ if ! grep -q '"shop"' rpc_resp.json; then fi rm rpc_resp.json -# Cleanup (trap handles server kill) ./target/debug/mudtool -d "$TEST_DB" settings set registration_open true ./target/debug/mudtool -d "$TEST_DB" players delete smoketest ./target/debug/mudtool -d "$TEST_DB" players delete rpctest diff --git a/scripts/ci/wait-for-tcp.sh b/scripts/ci/wait-for-tcp.sh new file mode 100755 index 0000000..e66f9d3 --- /dev/null +++ b/scripts/ci/wait-for-tcp.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e +host=${1:-127.0.0.1} +port=${2:-2222} +max_attempts=${3:-30} +for _ in $(seq 1 "$max_attempts"); do + if nc -z -w 1 "$host" "$port" 2>/dev/null; then + exit 0 + fi + sleep 1 +done +echo "timeout waiting for $host:$port" >&2 +exit 1