#!/usr/bin/env bash # Public entry for the first playable client gate (INF-02 / REL-01). # # Runs N independent client processes through run_client_gate.sh into # ROOT/run-, writes ROOT/release-manifest.json listing exactly those runs and # the candidate package hashes, then aggregates ONLY the listed runs. Historical # PASS reports elsewhere are never scanned. Credentials are asked once (hidden # TTY prompt) or taken from MT_ACCOUNT/MT_PASSWORD; never from arguments/files. # Do not add `set -x` to this script. # # exit: 0 PASS, 1 FAIL, 2 BLOCKED, 124 a run hit the wall-clock timeout. set -euo pipefail set +x cd "$(dirname "$0")/.." repo="$PWD" app="${MT_PLAYABLE_APP:-}" config="" output="" mode="playable" timeout="900" allow=0 repeat=1 suite="full" assets="" serverlist="${MT_PLAYABLE_SERVERLIST:-}" usage() { cat <<'EOF' 用法: script/playable_test.sh --app APP --config CONFIG [选项] --app APP 导出包路径(候选包;所有 run 必须是同一哈希) --output DIR 本批次根目录,必须不存在或为空(默认 build/playable/suite-<时间>) --suite playable|full 必测用例集合,默认 full --repeat N 独立进程运行次数,默认 1(发布要求见 docs/FIRST-MAC-PLAYABLE-IMPLEMENTATION.md) --timeout-seconds N 每次墙钟超时,默认 900 --assets DIR 资源预检根目录(透传 run_client_gate.sh) --serverlist FILE 测试模式 serverlist 覆盖(透传) --allow-gameplay 允许专用测试账号发送移动/战斗/拾取/施法请求 退出码: 0 PASS / 1 FAIL / 2 BLOCKED / 124 超时 EOF } while [ "$#" -gt 0 ]; do case "$1" in --app) app="${2:-}"; shift 2 ;; --config) config="${2:-}"; shift 2 ;; --output) output="${2:-}"; shift 2 ;; --suite) suite="${2:-}"; shift 2 ;; --repeat) repeat="${2:-}"; shift 2 ;; --timeout-seconds) timeout="${2:-}"; shift 2 ;; --assets) assets="${2:-}"; shift 2 ;; --serverlist) serverlist="${2:-}"; shift 2 ;; --allow-gameplay) allow=1; shift ;; --help) usage; exit 0 ;; *) echo "未知选项: $1" >&2; usage >&2; exit 2 ;; esac done if [ -z "$app" ] || [ ! -d "$app" ]; then echo "PLAYABLE SUITE: BLOCKED 需要 --app 指向候选导出包" >&2; exit 2; fi if [ -z "$config" ] || [ ! -f "$config" ]; then echo "PLAYABLE SUITE: BLOCKED 需要 --config CONFIG" >&2; exit 2; fi if [ "$suite" != "full" ] && [ "$suite" != "playable" ]; then echo "--suite 只支持 full/playable" >&2; exit 2; fi if ! [[ "$repeat" =~ ^[1-9][0-9]*$ ]]; then echo "--repeat 必须是正整数" >&2; exit 2; fi root="$output" if [ -z "$root" ]; then root="$repo/build/playable/suite-$(date +%Y%m%d-%H%M%S)-$$"; fi if [ -e "$root" ] && [ -n "$(find "$root" -mindepth 1 -maxdepth 1 -print -quit)" ]; then echo "PLAYABLE SUITE: BLOCKED 输出目录已有内容,拒绝混入历史运行:$root" >&2 exit 2 fi mkdir -p "$root" root="$(cd "$root" && pwd)" # Ask once for the whole batch; child runners inherit the environment only. if [ -z "${MT_ACCOUNT:-}" ] || [ -z "${MT_PASSWORD:-}" ]; then if [ -t 0 ]; then if [ -z "${MT_ACCOUNT:-}" ]; then read -r -s -p "测试账号: " MT_ACCOUNT; echo >&2; fi if [ -z "${MT_PASSWORD:-}" ]; then read -r -s -p "测试密码: " MT_PASSWORD; echo >&2; fi fi fi if [ -z "${MT_ACCOUNT:-}" ] || [ -z "${MT_PASSWORD:-}" ]; then echo "PLAYABLE SUITE: BLOCKED 缺少 MT_ACCOUNT/MT_PASSWORD,且不是交互终端" >&2 exit 2 fi export MT_ACCOUNT MT_PASSWORD # The candidate identity is computed once, before any run, from the same package. bash script/playable_build_info.sh "$app" >"$root/candidate.json" runs_json="[]" timed_out=0 for index in $(seq 1 "$repeat"); do run_dir="$root/run-$index" args=(--app "$app" --config "$config" --output "$run_dir" --suite "$suite" --mode "$mode" --timeout-seconds "$timeout") if [ -n "$assets" ]; then args+=(--assets "$assets"); fi if [ -n "$serverlist" ]; then args+=(--serverlist "$serverlist"); fi if [ "$allow" -eq 1 ]; then args+=(--allow-gameplay); fi set +e bash script/run_client_gate.sh "${args[@]}" &2; exit 124; fi case "$release_code" in 0) echo "PLAYABLE SUITE: PASS runs=$repeat root=$root" ;; 2) echo "PLAYABLE SUITE: BLOCKED root=$root" >&2 ;; *) echo "PLAYABLE SUITE: FAIL root=$root" >&2; release_code=1 ;; esac exit "$release_code"