Files

136 lines
5.6 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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-<n>, 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"
process_cleanup_grace_seconds=15
source script/playable_process_cleanup.sh
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[@]}" </dev/null &
child_pid=$!
wait "$child_pid"
code=$?
child_pid=""
set -e
if [ "$code" -eq 124 ]; then timed_out=1; fi
# A run that never produced a sealed report is still listed so the release fails loudly.
runs_json="$(node -e '
const fs = require("node:fs");
const [runs, dir, name, suite] = process.argv.slice(1);
let runId = `${name}-unsealed`;
try { runId = JSON.parse(fs.readFileSync(`${dir}/report.json`, "utf8")).run_id || runId; } catch {}
const list = JSON.parse(runs);
list.push({ run_id: runId, suite, path: name });
process.stdout.write(JSON.stringify(list));
' "$runs_json" "$run_dir" "run-$index" "$suite")"
done
node -e '
const fs = require("node:fs");
const [root, suite, repeat, runs] = process.argv.slice(1);
const candidate = JSON.parse(fs.readFileSync(`${root}/candidate.json`, "utf8"));
fs.writeFileSync(`${root}/release-manifest.json`, JSON.stringify({
schema_version: 1, candidate, required_runs: { [suite]: Number(repeat) }, runs: JSON.parse(runs),
}, null, 2) + "\n");
' "$root" "$suite" "$repeat" "$runs_json"
set +e
node script/validate_playable_report.mjs --release-dir "$root"
release_code=$?
set -e
if [ "$timed_out" -eq 1 ]; then echo "PLAYABLE SUITE: TIMEOUT root=$root" >&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"