# python_stdlib_test —— 内嵌解释器标准库的落盘(docs/PORT-PLAN.md 批次 2P step 3c)。 # 桌面上 res:// 就是磁盘目录,CPython 直接打开;Android/iOS 的 res:// 在 PCK 里,必须先复制进 # user:// 沙箱并按随包发的 sha256 校验。force_stage=true 就是在桌面上跑那条移动端路径。 # godot --headless --path project --script python_stdlib_test.gd extends SceneTree const ZIP_USER := "user://python27.zip" const ZIP_PART := "user://python27.zip.part" var _fail := 0 func _ck(ok: bool, message: String) -> void: if not ok: _fail += 1 printerr("FAIL: " + message) func _init() -> void: if not ClassDB.class_exists("Metin2Python"): printerr("FAIL: Metin2Python extension is not registered") quit(1) return if not FileAccess.file_exists("res://python27.zip"): print("SKIP: python_stdlib_test(没有 res://python27.zip,需要 -DMTGODOT_EMBED_PYTHON=ON 的构建)") quit(0) return var want := FileAccess.get_file_as_string("res://python27.zip.sha256").strip_edges() _ck(want.length() == 64, "res://python27.zip.sha256 是一行 sha256") # 桌面:res:// 在磁盘上,原地用,不复制。 if DirAccess.dir_exists_absolute(ProjectSettings.globalize_path("res://")): var in_place := Metin2Python.stdlib_path(false) _ck(in_place == ProjectSettings.globalize_path("res://python27.zip"), "res:// 可直接读时返回原路径,不落盘:" + Metin2Python.last_error()) # 移动端路径:复制进沙箱,校验 sha256,返回真实文件系统路径。 if FileAccess.file_exists(ZIP_USER): DirAccess.remove_absolute(ZIP_USER) var staged := Metin2Python.stdlib_path(true) _ck(staged != "", "落盘成功:" + Metin2Python.last_error()) _ck(staged == ProjectSettings.globalize_path(ZIP_USER), "返回的是 user:// 的真实路径") _ck(FileAccess.file_exists(staged), "返回的路径在文件系统上存在") _ck(FileAccess.get_sha256(ZIP_USER) == want, "落盘的 zip 与随包的 sha256 一致") _ck(not FileAccess.file_exists(ZIP_PART), "不留下半成品 .part") _ck(FileAccess.get_file_as_bytes(ZIP_USER) == FileAccess.get_file_as_bytes("res://python27.zip"), "落盘的字节与 res:// 里的一致") # zipimport 读不了 deflate(没有 zlib 模块),所以内容必须是 STORED。 var reader := ZIPReader.new() var opened := reader.open(staged) _ck(opened == OK, "落盘的 zip 能打开") if opened == OK: _ck(reader.file_exists("encodings/__init__.py"), "zip 里有 encodings 包") _ck(reader.file_exists("os.py"), "zip 里有 os.py") reader.close() # 第二次启动:已经在沙箱里且 sha 对得上,直接复用。 var modified := FileAccess.get_modified_time(ProjectSettings.globalize_path(ZIP_USER)) var again := Metin2Python.stdlib_path(true) _ck(again == staged, "第二次返回同一路径") _ck(FileAccess.get_modified_time(ProjectSettings.globalize_path(ZIP_USER)) == modified, "sha 相符时不重新复制") # 上次启动被杀在半路 / 换了一版构建:沙箱里的副本对不上就重新落盘。 var f := FileAccess.open(ZIP_USER, FileAccess.WRITE) f.store_string("torn") f.close() _ck(FileAccess.get_sha256(ZIP_USER) != want, "已经把沙箱里的副本弄坏") var restaged := Metin2Python.stdlib_path(true) _ck(restaged == staged, "坏副本之后仍返回同一路径:" + Metin2Python.last_error()) _ck(FileAccess.get_sha256(ZIP_USER) == want, "坏副本被重新落盘覆盖") if _fail == 0: print("PASS: python_stdlib_test (res:// -> user:// staging + sha256)") quit(0) else: printerr("%d check(s) failed" % _fail) quit(1)