Files
opendance-windows/tests/test_about.gd
T
zijunchiang ba988f6ca6 fix(packaging): 发布包 exe 的 PE 版本资源必须等于发布版本(v0.2.2)
缺陷(v0.2.1 实测):把 opendance-windows-v0.2.1.zip 解开、在 Windows 上
「右键 → 属性 → 详细信息」,显示的是

    文件版本    0.1.0
    产品版本    0.1.0

而产物名、Git tag、README 全是 v0.2.1 —— 正是文档 §11.2 要禁止的
「不可核对的发布」。

根因:export_presets.cfg 的 application/file_version /
application/product_version 被硬编码在 0.1.0,而它们**不会**跟随
project.godot 的 application/config/version 自动更新;check-version.sh 当时
只比 project.godot ↔ project.yaml(两处一致),所以静态校验全绿、产物却是错的。
漂移发生在没人看的第三个位置 —— 校验位置少一个,等于没校验。

证据:
  strings -a -el OpenDance.exe  ->  FileVersion 0.1.0 / ProductVersion 0.1.0
  VS_FIXEDFILEINFO @0x67b976c   ->  FileVersion 0.1.0.0 / ProductVersion 0.0.0.0
对照组:官方模板 windows_release_x86_64.exe 的 FileVersion = 4.7.2,
说明本机 rcedit 改写是生效的,问题纯粹在预设的值本身。

修复:
  * build/sync-export-version.sh(新增):导出前把版本号写进导出预设,
    幂等、拒绝非 X.Y.Z;
  * build/check-pe-version.sh(新增):直接读 exe 的 PE 版本资源 ——
    VS_FIXEDFILEINFO(签名 0xFEEF04BD 后 16 字节,grep -abo 取偏移约 40 ms)
    与 StringFileInfo(strings -el,资源管理器属性页显示的就是它)。
    两处都查:实测固定信息里的 ProductVersion 恒为 0.0.0.0;
  * build/check-version.sh:新增导出预设两个键的校验;
  * build/export-windows.sh:导出前同步、导出后直接复核产物;
  * build/package-release.sh:打包前校验源 exe、解包后再校验包内 exe;
  * build/verify.sh:新增步骤 1/5「产物 PE 版本资源」(未导出则跳过);
  * tests/test_about.gd:新增 test_export_preset_version_matches_project_version。

反向验证:把预设 file_version 改回 0.1.0 →
  check-version.sh 退出码 1(报出两处不一致);
  无头测试 753 通过 / 1 失败,失败项正是
  「导出预设的 file_version 与版本号唯一来源一致」。

实测 v0.2.2 产物:PE FileVersion 0.2.2.0 / StringFileInfo 0.2.2,
版本号在 project.godot / project.yaml / export_presets.cfg / exe 元数据 /
产物名 / tag 六处一致。verify.sh 全绿(754 通过 / 0 失败 / 5 跳过)。

同时把 LZMA 压缩的取舍写清楚:deflate 包 38,248,818 B 仍超 IIS
30,000,000 B 上限,故 Release 资产仍是 LZMA 整包(26,628,529 B,不拆分卷);
若上限放开或改由 Web /static 分发,用 ZIP_METHOD=deflate 重打包即可得到
资源管理器可直接双击解压的包。
2026-09-17 04:22:57 +08:00

81 lines
3.7 KiB
GDScript
Raw 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.
extends TestCase
## T-WIN-012:版本号展示(文档 §11)
##
## 硬性要求是「单一来源 + 可核对」:
## 版本号只允许有一处权威定义(project.godot 的 application/config/version),
## 界面从该来源读取,且必须与 project.yaml / 产物文件名 / Git tag 一致。
## 因此这里同时核对 project.godot、project.yaml、README 三处。
const AboutScreen = preload("res://src/ui/about_screen.gd")
const Credits = preload("res://src/ui/credits.gd")
func _read_text(path: String) -> String:
if not FileAccess.file_exists(path):
return ""
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
return ""
var text := file.get_as_text()
file.close()
return text
func test_version_is_semver_and_matches_project_yaml() -> void:
var version := AboutScreen.app_version()
var parts := version.split(".")
eq(parts.size(), 3, "版本号形如 X.Y.Z(实际 %s)" % version)
for part in parts:
ok(part.is_valid_int(), "版本号各段为整数(%s)" % part)
var yaml := _read_text("res://project.yaml")
ne(yaml, "", "可读取 project.yaml")
contains(yaml, "version: %s" % version, "project.yaml 的 version 与 project.godot 一致")
func test_about_manifest_lists_version() -> void:
var manifest := AboutScreen.about_manifest()
var found := {}
for item in manifest:
found[str(item["label"])] = str(item["value"])
ok(found.has("客户端版本"), "关于清单含「客户端版本」一项")
eq(found.get("客户端版本", ""), "v%s" % AboutScreen.app_version(), "清单版本号与唯一来源一致")
ok(found.has("契约版本"), "关于清单含「契约版本」")
for key in Credits.CONTRACT_VERSIONS:
contains(str(found.get("契约版本", "")), "%s v%s" % [key, Credits.CONTRACT_VERSIONS[key]], "清单含 %s 契约版本" % key)
ok(str(found.get("隐私说明", "")).contains("不存储"), "关于清单声明不存储视频画面")
func test_version_not_hardcoded_in_ui_sources() -> void:
# 反例防护:界面代码里不允许再手写一份版本号(文档 §11.2 第 1 条)
var version := AboutScreen.app_version()
for path in ["res://src/ui/about_screen.gd", "res://src/ui/main_menu.gd", "res://src/ui/game_screen.gd"]:
var text := _read_text(path)
ne(text, "", "可读取 %s" % path)
is_false(text.contains("\"%s\"" % version), "%s 未硬编码版本号字面量" % path.get_file())
func test_entry_scene_version_is_exported_into_build() -> void:
# 导出产物名必须能由版本号推导(Documentation §11.2 第 2 条)
var version := AboutScreen.app_version()
contains("opendance-windows-v%s.zip" % version, version, "产物文件名包含版本号")
ok(Credits.entry_count() >= 10, "开源声明条目仍齐备")
func test_export_preset_version_matches_project_version() -> void:
# 缺陷回归:export_presets.cfg 的 application/file_version 与 product_version
# 决定导出后 exe 的 PE VERSIONINFO 资源,也就是 Windows「右键 → 属性 →
# 详细信息」里显示的文件版本/产品版本。这两个键**不会**跟随 project.godot 的
# application/config/version 自动更新,v0.2.1 的发布包因此显示的是 0.1.0
# (产物名与 tag 都是 v0.2.1,二进制元数据却是 0.1.0)。
# 产物侧由 build/check-pe-version.sh 直接读 exe 复核,这里做静态防线。
var version := AboutScreen.app_version()
var preset := _read_text("res://export_presets.cfg")
ne(preset, "", "可读取 export_presets.cfg")
contains(preset, "application/file_version=\"%s\"" % version,
"导出预设的 file_version 与版本号唯一来源一致")
contains(preset, "application/product_version=\"%s\"" % version,
"导出预设的 product_version 与版本号唯一来源一致")