feat(playable): 首个 Mac 联网内测版 STB-01 soak 工具与发布状态文档
- 新增 script/playable_soak.sh:先做 soak 配置/资源校验,无 --allow-gameplay 只校验不启动客户端;N(>=10) 次独立正常退出运行 + 墙钟 soak,失败即停止后续 运行并记 BLOCKED;写本批次 release-manifest.json 后聚合 - run_client_gate.sh:确认的故障代理对所有 suite 启动(共享场景的退出运行也经代理); soak 超时下限只约束 soak 客户端(validate_soak 增加 soak_client 参数) - 新增本地 127.0.0.1 故障代理、RSS 采样/内存判定、窗口/指标/流程模块及其测试 - forest_mob_render_test 输出 PASS/FAIL 标记,供 rendering_batch_test.sh 识别 - 新增 docs/FIRST-MAC-PLAYABLE-STATUS.md:如实记录 PASS/BLOCKED、已知问题与环境需求 - .gitignore 排除本地场景配置、凭据文件与运行输出 离线回归:rendering_batch_test.sh failures=0,playable_gate_test.sh PASS, node 夹具测试 PASS。未联网运行,未重建候选包。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
This commit is contained in:
+101
-27
@@ -1,56 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
# Public entry for the first playable client gate. It delegates process
|
||||
# ownership and post-exit validation to run_client_gate.sh.
|
||||
# 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")/.."
|
||||
app="${MT_PLAYABLE_APP:-$PWD/build/export-native-trees-20260911/mtgodot-poc.app}"
|
||||
repo="$PWD"
|
||||
app="${MT_PLAYABLE_APP:-}"
|
||||
config=""
|
||||
output=""
|
||||
mode="playable"
|
||||
timeout="180"
|
||||
timeout="900"
|
||||
allow=0
|
||||
repeat=1
|
||||
suite="full"
|
||||
assets=""
|
||||
serverlist="${MT_PLAYABLE_SERVERLIST:-}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法: script/playable_test.sh --config CONFIG [选项]
|
||||
--app APP 导出包路径
|
||||
--output DIR 单次运行输出目录(repeat=1)
|
||||
--suite full 当前固定为 full/playable
|
||||
--allow-gameplay 允许发送游戏状态变更请求
|
||||
--repeat N 独立进程运行次数,默认 1
|
||||
--timeout-seconds N 每次墙钟超时,默认 180
|
||||
用法: 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 ;;
|
||||
--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 ;;
|
||||
--repeat) repeat="$2"; shift 2 ;;
|
||||
--timeout-seconds) timeout="$2"; shift 2 ;;
|
||||
--help) usage; exit 0 ;;
|
||||
*) echo "未知选项: $1" >&2; usage >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
if [ -z "$config" ] || [ ! -f "$config" ]; then echo "需要 --config CONFIG" >&2; exit 2; fi
|
||||
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
|
||||
if [ "$repeat" -gt 1 ] && [ -n "$output" ]; then echo "repeat>1 时不能指定单一 --output" >&2; exit 2; fi
|
||||
|
||||
root="$output"
|
||||
if [ -z "$root" ]; then root="${MT_PLAYABLE_OUTPUT:-$PWD/build/playable/suite-$(date +%Y%m%d-%H%M%S)-$$}"; fi
|
||||
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"
|
||||
failed=0
|
||||
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"
|
||||
if [ "$repeat" -gt 1 ]; then run_dir="$root/run-$index"; fi
|
||||
args=(--app "$app" --config "$config" --output "$run_dir" --mode "$mode" --timeout-seconds "$timeout")
|
||||
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
|
||||
if ! bash script/run_client_gate.sh "${args[@]}"; then failed=$((failed + 1)); fi
|
||||
set +e
|
||||
bash script/run_client_gate.sh "${args[@]}" </dev/null
|
||||
code=$?
|
||||
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
|
||||
if [ "$failed" -ne 0 ]; then echo "PLAYABLE SUITE: FAIL runs=$failed/$repeat" >&2; exit 1; fi
|
||||
echo "PLAYABLE SUITE: PASS runs=$repeat root=$root"
|
||||
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user