magicciv/scripts/run/test.sh
Natalie 3ee2b60b11 feat(@projects/@magic-civilization): add gpu rollout parity tests & performance benchmarks
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-04-17 12:30:50 -07:00

159 lines
5.8 KiB
Bash

#!/usr/bin/env bash
# Test subcommands: data validation, full test pipeline, cross-language
# golden-vector parity, coverage reports.
#
# Split out of dev.sh. `./run test` runs GUT + cargo + vitest + stability;
# `./run test:golden` is the 3-consumer parity gate; `./run validate`
# runs the JSON schema validator standalone.
cmd_validate() {
echo -e "${BLUE}Validating game data JSON schemas...${NC}"
python3 "$REPO_ROOT/tools/validate-game-data.py" "$@"
}
# Run Rust workspace tests, preferring nextest when available.
_cargo_test_workspace() {
if _have_tool cargo-nextest "cargo install cargo-nextest --locked"; then
(cd "$SIMULATOR_DIR" && cargo nextest run --workspace)
else
(cd "$SIMULATOR_DIR" && cargo test --workspace)
fi
}
cmd_test() {
local exit_code=0
echo -e "${BLUE}Running GUT tests (GDScript)...${NC}"
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" \
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
$GODOT_BIN --path "$GAME_DIR" --headless --script res://addons/gut/gut_cmdln.gd \
-gexit "$@" || exit_code=$?
echo ""
echo -e "${BLUE}Running Rust tests (simulator)...${NC}"
_cargo_test_workspace || exit_code=$?
echo ""
echo -e "${BLUE}Running vitest (guide)...${NC}"
pnpm --prefix "$GUIDE_DIR" test || exit_code=$?
echo ""
echo -e "${BLUE}Running stability test (20s game boot)...${NC}"
_run_stability_test || exit_code=$?
return $exit_code
}
_run_stability_test() {
# Boots the game → world_map, waits 20s, captures screenshot.
# If the game crashes before capture, exit code is non-zero.
local LOG="/tmp/stability_test_$$.log"
cmd_screenshot "stability_test" "world_map" "20" > "$LOG" 2>&1
if [ $? -ne 0 ]; then
echo -e "${RED}FAIL: Game crashed during stability test${NC}"
grep -E "SCRIPT ERROR|ERROR:" "$LOG" | head -5
return 1
fi
if grep -q "Captured:" "$LOG"; then
echo -e "${GREEN}PASS: Game stable for 20s, screenshot captured${NC}"
return 0
else
echo -e "${RED}FAIL: Game ran but no screenshot captured${NC}"
cat "$LOG" | tail -5
return 1
fi
}
cmd_coverage() {
# Generate coverage reports for Rust + TypeScript.
# Graceful degradation: each tool warn-skips if not installed.
local exit_code=0
echo -e "${BLUE}[1/2] Rust coverage (cargo llvm-cov)...${NC}"
if _have_tool cargo-llvm-cov "cargo install cargo-llvm-cov --locked"; then
(cd "$SIMULATOR_DIR" && cargo llvm-cov --workspace --html) || exit_code=$?
echo -e "${BLUE}HTML report: $SIMULATOR_DIR/target/llvm-cov/html/index.html${NC}"
fi
echo ""
echo -e "${BLUE}[2/2] TypeScript coverage (pnpm -r test:coverage)...${NC}"
# --if-present: pnpm exits 0 when no package defines the script, which is
# the graceful-degrade behavior we want. Without it, pnpm exits 1 with
# ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT, which would falsely fail verify.
pnpm -r --if-present run test:coverage || exit_code=$?
return $exit_code
}
cmd_test_golden() {
# Cross-language golden-vector parity gate.
#
# Each fixture in src/simulator/tests/golden/vectors/*.json is consumed by
# three runners that MUST produce bitwise-identical output. Divergence =
# release blocker (FFI marshaling / non-determinism / SOT violation).
#
# See src/simulator/tests/golden/README.md for the fixture shape and
# ~/.claude/instructions/rust-code-standards.md §"Testing Strategy" for rationale.
local vectors_dir="$SIMULATOR_DIR/tests/golden/vectors"
local exit_code=0
if [ ! -d "$vectors_dir" ]; then
echo -e "${RED}Golden vectors directory missing: $vectors_dir${NC}"
return 1
fi
local vectors
vectors=$(find "$vectors_dir" -maxdepth 1 -name '*.json' -type f | sort)
if [ -z "$vectors" ]; then
echo -e "${YELLOW}No golden vectors yet — add JSON fixtures to:${NC}"
echo -e " $vectors_dir"
echo -e "${YELLOW}See $SIMULATOR_DIR/tests/golden/README.md for the fixture shape.${NC}"
return 0
fi
local count
count=$(echo "$vectors" | wc -l | tr -d ' ')
echo -e "${BLUE}Found $count golden vector(s) — running 3-consumer parity check${NC}"
echo ""
# Consumer 1: Rust native
echo -e "${BLUE}[1/3] Rust native consumer (cargo test --test golden)${NC}"
if ! (cd "$SIMULATOR_DIR" && cargo test --workspace --test golden 2>&1); then
echo -e "${RED}FAIL: Rust golden tests${NC}"
exit_code=1
fi
echo ""
# Consumer 2: WASM via Vitest (guide simulation worker)
echo -e "${BLUE}[2/3] WASM consumer (pnpm test — golden suite)${NC}"
if ! pnpm --prefix "$GUIDE_DIR" test -- --run golden 2>&1; then
echo -e "${RED}FAIL: WASM golden tests${NC}"
exit_code=1
fi
echo ""
# Consumer 3: GDExtension via headless Godot + GUT
echo -e "${BLUE}[3/3] GDExtension consumer (headless Godot + GUT ffi/)${NC}"
local ffi_dir="$GAME_DIR/engine/tests/ffi"
if [ -d "$ffi_dir" ] && [ -n "$(find "$ffi_dir" -maxdepth 1 -name 'test_golden_*.gd' -print -quit 2>/dev/null)" ]; then
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" \
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
$GODOT_BIN --path "$GAME_DIR" --headless \
--script res://addons/gut/gut_cmdln.gd \
-gdir=res://engine/tests/ffi -gprefix=test_golden_ -gexit 2>&1 \
|| exit_code=$?
else
echo -e "${YELLOW}SKIP: No GDExt golden tests yet at $ffi_dir/test_golden_*.gd${NC}"
fi
echo ""
if [ $exit_code -eq 0 ]; then
echo -e "${GREEN}All 3 consumers agree on $count vector(s)${NC}"
else
echo -e "${RED}Divergence detected — release blocker${NC}"
echo -e "${RED}See src/simulator/tests/golden/README.md for triage guidance${NC}"
fi
return $exit_code
}