tests/test_pose_server.gd 用的是**同引擎内的客户端**(Godot WebSocketPeer),而真实
对端是 Android(Kotlin/OkHttp)——同引擎自测会共享同样的假设(超时判定、分包、
文本/二进制边界、关闭语义、子协议协商),于是「测试全绿」不等于「Android 连得上」。
契约 §7 也写明:静默超时要用保活避免,而不是靠重连掩盖;这句话只有用独立实现的
客户端实测才算验过。
新增三个文件:
* build/integration/probe-ws.gd —— 把**包内** PoseServer 跑起来(--main-pack +
只加载包内代码),把连接/控制帧/帧/断开事件汇总成 JSON 报告;自身不做判定
(避免「用被测实现判定被测实现」)。支持 --stop-file 提前收尾。
* build/integration/ws-client.mjs —— Node 实现的契约一致性客户端(用内置
WebSocket,零依赖),逐条跑 §1 §1.1 §2 §3.1-§3.6 §5 §6 §7 的场景。
* build/accept-ws.sh —— 编排:解包 → 起探针 → 等监听 → 跑客户端 → 收报告 →
核对客户端看不到的那一半(frames_dropped / malformed_frames / 控制帧计数 /
device_replaced / 关闭原因 / 静默超时消息带毫秒数与阈值)。
实测(**已发布**的 v0.2.3 资产重新下载后跑):客户端 30 通过 / 0 失败 + 服务端侧
16 项事实全过。关键两条:
* 保活对照:只发应用层 ping 持续 19 秒(> 静默超时 15 秒)连接**仍保持打开**;
* 静默超时:完全不发数据 → close(1001,"timeout"),实测 15011 ms(阈值 15000 ms);
另有:子协议协商、welcome 声明保活参数且 heartbeat(5000) < silentTimeout(15000)/2、
pong 原样回传 sentAtMs、同 deviceId 替换旧连接(1000 "replaced by new connection")、
bye → 1000、协议不匹配 → bye + 1002、72 帧含故意跳号(服务端 frames_dropped=3)
与故意 100 字节非法帧(malformed_frames=1)。
反向验证:新增**故意不合规的服务端** build/integration/probe-ws-bad.gd
(welcome 不声明保活字段、不带 sessionId、收到 ping 不回 pong、不做 deviceId 去重),
对同一客户端 → 14 项 ✗、退出码 1,证明断言不是空转。
踩坑记录(已写入任务文档):
1. --script 模式下从无类型数组取 peer,peer.get_packet() 返回类型推不出来,
var x := … 直接 Parse Error;
2. 复核静默超时消息时写了否定式断言 !includes("静默超时 1"),把正确行为判成失败 ——
断言应写成正向模式匹配;
3. 探针原本只能按固定时长跑,编排脚本得猜时间;改成客户端跑完 touch stop 文件。
顺带:package-release.sh 结尾把三条发布前验收(产物自证 / WS 契约一致性 / 跨端集成)
一次列全。不影响已发布的 v0.2.3 产物,故不新发版本。
186 lines
8.8 KiB
Bash
Executable File
186 lines
8.8 KiB
Bash
Executable File
#!/bin/bash
|
||
# ============================================================
|
||
# OpenDance Studio —— Windows 客户端发布打包(T-WIN-014)
|
||
#
|
||
# 产物(文档 §5.3 / §5.7):
|
||
# opendance-windows-vX.Y.Z.zip ← **单个完整包,禁止 .partN 分卷**
|
||
# SHA256SUMS ← 裸文件名,下载目录内 sha256sum -c 直接可用
|
||
#
|
||
# 包内结构:
|
||
# opendance-windows-vX.Y.Z/
|
||
# OpenDance.exe
|
||
# OpenDance.pck
|
||
# THIRD_PARTY.md
|
||
# README.txt README.md
|
||
# contracts/
|
||
#
|
||
# 为什么强调"单个完整包":本项目的 Gitea 前置 IIS 默认
|
||
# maxAllowedContentLength = 30,000,000 字节(≈28.6 MiB),
|
||
# 30.5 MB / 40 MB 的上传实测直接 HTTP 413。当时的应对是把整包拆成
|
||
# zip.part0/1/2(每卷 13 MiB)——这正是"分卷有问题"的根因:
|
||
# 用户拿到的是无法直接解压的碎片,还要自己拼接。
|
||
# 现在改为:整包上传;若超过上限则**直接报错退出**,并提示运维放宽
|
||
# IIS/ARR 上限(做法见 opendance-web/deploy/proxy/README.md),
|
||
# 绝不静默退回分卷。
|
||
#
|
||
# 用法:bash build/package-release.sh [版本号]
|
||
# ============================================================
|
||
set -euo pipefail
|
||
|
||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||
cd "$PROJECT_DIR"
|
||
|
||
VERSION="${1:-$(sed -n 's/^version:[[:space:]]*//p' project.yaml | head -1)}"
|
||
VERSION="${VERSION:-0.2.0}"
|
||
NAME="opendance-windows-v${VERSION}"
|
||
OUT_DIR="$PROJECT_DIR/dist-release"
|
||
STAGE="$OUT_DIR/$NAME"
|
||
|
||
# Gitea 前置 IIS 的实测上传上限(文档 §5.7 实测结论)
|
||
UPLOAD_LIMIT_BYTES=30000000
|
||
|
||
log() { printf '\033[1;34m[package]\033[0m %s\n' "$*"; }
|
||
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; }
|
||
die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
|
||
|
||
# ---- 0. 版本号一致性(文档 §11.2:产物名 / tag / 界面展示必须一致)----
|
||
bash "$PROJECT_DIR/build/check-version.sh" "$VERSION" || die "版本一致性校验失败,先统一 project.godot / project.yaml / 发布版本号"
|
||
|
||
EXE="$PROJECT_DIR/build/windows/OpenDance.exe"
|
||
PCK="$PROJECT_DIR/build/windows/OpenDance.pck"
|
||
|
||
[ -f "$EXE" ] || die "未找到 $EXE,请先执行 bash build/export-windows.sh release"
|
||
[ -f "$PCK" ] || die "未找到 $PCK,请先执行 bash build/export-windows.sh release"
|
||
|
||
# ---- 1. 源产物自检:PE 头 + pck 魔数 ----
|
||
MAGIC=$(head -c 2 "$EXE" | od -An -tx1 | tr -d ' \n')
|
||
[ "$MAGIC" = "4d5a" ] || die "$EXE 不是合法的 Windows PE 文件(头 $MAGIC)"
|
||
PCK_MAGIC=$(head -c 4 "$PCK" | tr -d '\0')
|
||
[ "$PCK_MAGIC" = "GDPC" ] || die "$PCK 不是合法的 Godot 资源包(头 '$PCK_MAGIC')"
|
||
log "源产物校验通过:exe PE 头 MZ | pck 头 GDPC"
|
||
|
||
# 产物 exe 的 PE 版本资源必须是本次发布版本 —— Windows「属性 → 详细信息」显示的就是它。
|
||
# v0.2.1 曾带着 0.1.0 发出去(导出预设里是硬编码的旧值,且没有任何环节复核产物)。
|
||
bash "$PROJECT_DIR/build/check-pe-version.sh" "$EXE" "$VERSION" \
|
||
|| die "$EXE 的 PE 版本资源不是 v$VERSION;先跑 bash build/sync-export-version.sh $VERSION 再重新导出"
|
||
log "产物 PE 版本资源校验通过(=$VERSION)"
|
||
|
||
# ---- 2. 整理产物 ----
|
||
log "整理产物到 $STAGE …"
|
||
rm -rf "$STAGE"
|
||
mkdir -p "$STAGE"
|
||
|
||
cp "$EXE" "$STAGE/OpenDance.exe"
|
||
cp "$PCK" "$STAGE/OpenDance.pck"
|
||
cp THIRD_PARTY.md README.txt "$STAGE/"
|
||
if [ -f README.md ]; then cp README.md "$STAGE/"; fi
|
||
cp -r contracts "$STAGE/contracts"
|
||
|
||
# ---- 3. 打包(单个完整包) ----
|
||
ZIP="$OUT_DIR/$NAME.zip"
|
||
# 清理历史分卷,避免 old .partN 被误当成本次产物上传
|
||
rm -f "$OUT_DIR/$NAME.zip".part* "$OUT_DIR/$NAME.zip" "$OUT_DIR/SHA256SUMS"*
|
||
|
||
# 压缩方式:默认 deflate(Windows 资源管理器可直接解压)。
|
||
# ZIP_METHOD=lzma 时改用 LZMA —— 包体从约 38 MB 降到约 26 MB,可绕过
|
||
# 「Gitea 站点 IIS 的 maxAllowedContentLength = 30,000,000 字节」这道闸
|
||
# (见 opendance-web/deploy/proxy/README.md 第九节)。
|
||
# **代价**:Windows 10 的资源管理器不支持 LZMA(method 14) 的 zip,用户必须用
|
||
# 7-Zip / WinRAR / PeaZip 解压(Win11 24H2+ 起资源管理器原生支持 7z/zip 变体,
|
||
# 但本项目未在 Windows 上实测)。因此只有在 IIS 上限确实无法放开时才用 LZMA。
|
||
ZIP_METHOD="${ZIP_METHOD:-deflate}"
|
||
log "生成单个完整包 $NAME.zip(压缩方式:$ZIP_METHOD)…"
|
||
case "$ZIP_METHOD" in
|
||
deflate)
|
||
( cd "$OUT_DIR" && zip -qr "$NAME.zip" "$NAME" )
|
||
;;
|
||
lzma)
|
||
command -v 7z >/dev/null 2>&1 || die "ZIP_METHOD=lzma 需要 7z(Debian/Ubuntu: apt install p7zip-full)"
|
||
( cd "$OUT_DIR" && rm -f "$NAME.zip" && 7z a -tzip -mm=LZMA -mx=9 "$NAME.zip" "$NAME" >/dev/null )
|
||
;;
|
||
*)
|
||
die "未知 ZIP_METHOD=$ZIP_METHOD(可选:deflate / lzma)"
|
||
;;
|
||
esac
|
||
# LZMA 包额外做一次完整性测试与解压自检(zip/unzip 对 method 14 支持不一致)
|
||
if [ "$ZIP_METHOD" = "lzma" ]; then
|
||
7z t "$ZIP" >/dev/null || die "LZMA 包完整性测试失败(7z t)"
|
||
log "LZMA 包完整性测试通过(7z t)"
|
||
fi
|
||
|
||
# ---- 4. 完整性自检:解压后必须含 exe + pck(同目录)且 exe 仍是合法 PE ----
|
||
CHECK_DIR=$(mktemp -d)
|
||
trap 'rm -rf "$CHECK_DIR"' EXIT
|
||
if [ "$ZIP_METHOD" = "lzma" ]; then
|
||
7z x -y -o"$CHECK_DIR" "$ZIP" >/dev/null
|
||
else
|
||
unzip -q "$ZIP" -d "$CHECK_DIR"
|
||
fi
|
||
|
||
[ -f "$CHECK_DIR/$NAME/OpenDance.exe" ] || die "包内缺少 OpenDance.exe"
|
||
[ -f "$CHECK_DIR/$NAME/OpenDance.pck" ] || die "包内缺少 OpenDance.pck(必须与 exe 同目录)"
|
||
[ -f "$CHECK_DIR/$NAME/README.txt" ] || die "包内缺少 README.txt"
|
||
[ -f "$CHECK_DIR/$NAME/THIRD_PARTY.md" ] || die "包内缺少 THIRD_PARTY.md"
|
||
[ -d "$CHECK_DIR/$NAME/contracts" ] || die "包内缺少 contracts/ 契约快照"
|
||
|
||
CHECK_MAGIC=$(head -c 2 "$CHECK_DIR/$NAME/OpenDance.exe" | od -An -tx1 | tr -d ' \n')
|
||
[ "$CHECK_MAGIC" = "4d5a" ] || die "解压后的 OpenDance.exe 不是合法 PE 文件(头 $CHECK_MAGIC)"
|
||
CHECK_PCK=$(head -c 4 "$CHECK_DIR/$NAME/OpenDance.pck" | tr -d '\0')
|
||
[ "$CHECK_PCK" = "GDPC" ] || die "解压后的 OpenDance.pck 不是合法 Godot 资源包"
|
||
log "整包完整性自检通过(解压后 exe + pck 同目录,PE/GDPC 头均合法)"
|
||
|
||
# 再核一次**解压后**的 exe 版本资源:确认打包/解包环节没有换错文件
|
||
bash "$PROJECT_DIR/build/check-pe-version.sh" "$CHECK_DIR/$NAME/OpenDance.exe" "$VERSION" \
|
||
|| die "包内 exe 的 PE 版本资源不是 v$VERSION"
|
||
log "包内 exe 的 PE 版本资源 = v$VERSION"
|
||
|
||
# pck 与 exe 必须同名同目录,否则 Godot 找不到资源
|
||
[ "$(basename "$CHECK_DIR/$NAME/OpenDance.exe" .exe)" = "$(basename "$CHECK_DIR/$NAME/OpenDance.pck" .pck)" ] \
|
||
|| die "exe 与 pck 主文件名不一致,导出产物不可运行"
|
||
|
||
# ---- 5. SHA256SUMS(裸文件名,下载目录内可直接校验) ----
|
||
( cd "$OUT_DIR" && sha256sum "$NAME.zip" > SHA256SUMS )
|
||
log "已生成 $OUT_DIR/SHA256SUMS"
|
||
|
||
# ---- 6. 体积与上传上限对比 ----
|
||
SIZE_BYTES=$(stat -c %s "$ZIP")
|
||
SIZE_HUMAN=$(du -h "$ZIP" | cut -f1)
|
||
log "包体 $SIZE_HUMAN($SIZE_BYTES 字节)| Gitea/IIS 实测上限 $UPLOAD_LIMIT_BYTES 字节"
|
||
if [ "$SIZE_BYTES" -gt "$UPLOAD_LIMIT_BYTES" ]; then
|
||
warn "整包超过当前 Gitea 前置 IIS 的上传上限,上传会返回 HTTP 413。"
|
||
warn "请让运维放宽上限后重传(做法见 opendance-web/deploy/proxy/README.md):"
|
||
warn " IIS requestFiltering/requestLimits@maxAllowedContentLength = 268435456"
|
||
warn " ARR maxRequestBytes >= 268435456"
|
||
warn " Gitea app.ini [attachment] MAX_SIZE >= 256MB"
|
||
warn " nginx(若前置)client_max_body_size 256m;"
|
||
warn "本脚本**不会**回退为 .partN 分卷——分卷包无法直接解压,是历史缺陷。"
|
||
warn "若暂时无法放开 IIS 上限,可用 ZIP_METHOD=lzma 重跑本脚本:包体约 26 MB 可绕过 30 MB 闸,"
|
||
warn "但 Windows 10 资源管理器不能直接解压 LZMA zip,必须用 7-Zip / WinRAR(README 中已注明)。"
|
||
else
|
||
log "低于上限,可直接作为 Release 资产上传。"
|
||
fi
|
||
|
||
echo ""
|
||
echo "产物:"
|
||
echo " $ZIP"
|
||
echo " $OUT_DIR/SHA256SUMS"
|
||
if ( cd "$OUT_DIR" && sha256sum -c SHA256SUMS --status ); then
|
||
echo " sha256 校验:OK"
|
||
else
|
||
die "sha256 自校验失败"
|
||
fi
|
||
echo ""
|
||
if [ "$ZIP_METHOD" = "lzma" ]; then
|
||
7z l "$ZIP" | tail -14
|
||
else
|
||
unzip -l "$ZIP" | sed -n '3,14p'
|
||
fi
|
||
echo ""
|
||
echo "分发:上传 $NAME.zip 与 SHA256SUMS 两个资产;用户下载后"
|
||
echo " sha256sum -c SHA256SUMS && unzip $NAME.zip 即可直接运行 OpenDance.exe。"
|
||
echo ""
|
||
echo "下一步(文档 §7.5 / §7.6:发布前必须做的三条产物级验收):"
|
||
echo " GODOT_BIN=/path/to/godot bash build/accept-release.sh $ZIP # 产物自证"
|
||
echo " GODOT_BIN=/path/to/godot bash build/accept-ws.sh $ZIP # WebSocket 契约一致性"
|
||
echo " API_BASE=http://127.0.0.1:3000 GODOT_BIN=... bash build/accept-integration.sh $ZIP # 跨端(需已部署服务端)"
|