"""M10b 加载器服务端判据:servers.py。 ## 判据要点 1. **隔离**:mods 落**实例层**,内容根**零写入** (这是 `test_version_isolation` 守的同一条不变量,加载器不能破坏它) 2. **未接的加载器要明说**,不能瞎猜或静默降级成 vanilla 3. **缺文件要指名道姓**,不能吞掉 4. 全程用**临时目录**,不碰真实 mcview ## 不测什么 下载安装器、起 JVM —— 那些需要网络/环境,放真实使用时验。 """ import sys, os, json, tempfile, stat from pathlib import Path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import mcfold.servers as S def _tmp(): """把模块的路径常量指到临时目录(隔离真实 mcview)。""" tmp = Path(tempfile.mkdtemp(prefix="mcfold-srv-test-")) S.BASE = tmp S.STORE = tmp / "store" S.INSTANCES = tmp / "instances" S.RUNTIME = tmp / "runtime" S.LOADER_CACHE = tmp / "loaders" return tmp def _fake_jar(tmp: Path, name: str = "server.jar", size: int = 5000) -> Path: p = tmp / name p.write_bytes(b"PK\x03\x04" + b"0" * size) return p # ---------- ① 隔离:mods 在实例层,内容根零写入 ---------- def test_mods_land_in_instance_not_content_root(): """★★ mods 必须落**实例层**;内容根保持只读共享。 为什么这条是硬要求:一个 vanilla 内容根旁边要能起多个「装了不同 mod 的服」。 mods 若写进内容根 ⇒ 那些服会互相看到对方的 mod(这正是版本隔离要防的崩因)。 """ tmp = _tmp() jar = _fake_jar(tmp) mod = _fake_jar(tmp, "coolmod.jar") r = S.create_modded_server("inst1", version="1.21.1", loader="vanilla", mod=[str(mod)], server_jar=str(jar)) assert r["ok"] is True, r inst = Path(r["instance"]) assert (inst / "mods" / "coolmod.jar").exists(), "mod 应在实例的 mods/ 里" # ★ 内容根零写入 assert not S.STORE.exists() or not any(S.STORE.iterdir()), \ f"内容根被写入了:{list(S.STORE.iterdir()) if S.STORE.exists() else []}" print(" ★★ mods 落实例层,内容根零写入") def test_two_instances_share_content_root_different_mods(): """★ 同一版本的两个实例可装**不同** mod(多实例的意义)。""" tmp = _tmp() jar = _fake_jar(tmp) a = _fake_jar(tmp, "modA.jar") b = _fake_jar(tmp, "modB.jar") r1 = S.create_modded_server("instA", version="1.21.1", loader="vanilla", mod=[str(a)], server_jar=str(jar)) r2 = S.create_modded_server("instB", version="1.21.1", loader="vanilla", mod=[str(b)], server_jar=str(jar)) assert r1["ok"] and r2["ok"] ma = sorted(p.name for p in (Path(r1["instance"]) / "mods").iterdir()) mb = sorted(p.name for p in (Path(r2["instance"]) / "mods").iterdir()) assert ma == ["modA.jar"] and mb == ["modB.jar"], (ma, mb) print(" ★ 同版本两实例各装各的 mod(互不看见)") def test_instance_json_records_version_and_loader(monkey=None): """★ 实例元数据要记 version/loader —— 隔离的**依据**。 (之前 7 个实例里有 2 个缺 instance.json,list_instances 只能报 has_meta: False ⇒ 隔离没有可核查的依据。) ★ fabric 路径会去**下载** server-launch ⇒ 判据里替换掉下载函数, 避免判据依赖网络(网络一挂就假红)。 """ tmp = _tmp() jar = _fake_jar(tmp) # ★ fabric 现在走 classpath 启动(实测 maven 上没有 server-launch 产物) # ⇒ 判据替换「库解析」,不依赖网络与本地资产 libs = tmp / "fakelibs" libs.mkdir() for n in ("fabric-loader.jar", "intermediary.jar"): (libs / n).write_bytes(b"PK\x03\x04" + b"0" * 5000) orig = S.fabric_libraries S.fabric_libraries = lambda v, loader_version="": { "ok": True, "jars": [str(p) for p in sorted(libs.glob("*.jar"))], "source": str(libs), "cached": True, "_stub": True} try: r = S.create_modded_server("inst1", version="1.20.1", loader="fabric") finally: S.fabric_libraries = orig assert r["ok"] is True, r meta = json.loads((Path(r["instance"]) / "instance.json").read_text()) assert meta["version"] == "1.20.1", meta assert meta["loader"] == "fabric", meta assert meta["role"] == "server", meta print(" ★ instance.json 记全 version/loader/role") def test_runtime_dir_is_third_layer(): """★ 运行时目录(world/logs/pid)在第三层,不在实例或内容根里。""" tmp = _tmp() jar = _fake_jar(tmp) r = S.create_modded_server("inst1", version="1.21.1", loader="vanilla", server_jar=str(jar)) rt = Path(r["runtime"]) assert rt.parent == S.RUNTIME, rt assert rt.name == "inst1", rt assert "/store/" not in str(rt) and "/instances/" not in str(rt) print(f" ★ 运行时目录在第三层({rt})") # ---------- ② 未接的加载器必须明说 ---------- def test_unimplemented_loader_says_so(): """★★ 没接的加载器(Forge/Quilt)必须**明说**,不能静默降级。 静默降级成 vanilla 的后果很严重:服起来了但**mod 全部没加载**, 而使用者以为装上了 ⇒ 后面所有观察都基于错误前提。 """ tmp = _tmp() jar = _fake_jar(tmp) for loader in ("forge", "quilt"): r = S.create_modded_server(f"inst-{loader}", version="1.21.1", loader=loader, server_jar=str(jar)) assert r["ok"] is False, f"{loader} 不该成功" assert loader in r["why"], r assert r.get("how_to"), r # ★ 不能建出实例目录(否则会留个「看着存在但没配好」的壳) assert not (S.INSTANCES / f"inst-{loader}").exists(), \ f"{loader} 失败时不该留下实例目录" print(" ★★ NeoForge/Forge 明确说未接(不静默降级成 vanilla)") # ---------- ③ 缺文件要指名道姓 ---------- def test_missing_mod_files_are_named(): """★ 缺的 mod 要**逐个列出来**(不能吞掉让人以为全装上了)。""" tmp = _tmp() jar = _fake_jar(tmp) r = S.create_modded_server("inst1", version="1.21.1", loader="vanilla", mod=["/no/such1.jar", "/no/such2.jar"], server_jar=str(jar)) assert r["ok"] is False, r assert "such1" in r["why"] and "such2" in r["why"], r["why"] assert r.get("installed") == [], r.get("installed") print(" ★ 缺 mod 逐个指名(不静默吞)") def test_neoforge_requires_install_flag(): """★ NeoForge 没给 install=True 要**明说**(不能假装装好了)。 NeoForge 服务端必须由官方 installer(--installServer)生成, 没有它就只有一堆 mod jar 而没有可运行的服。 """ tmp = _tmp() jar = _fake_jar(tmp) r = S.create_modded_server("inst-nf", version="1.21.1", loader="neoforge", mod=[str(jar)], install=False) assert r["ok"] is False, r assert "installer" in r["why"], r assert r.get("how_to"), r assert not (S.INSTANCES / "inst-nf").exists(), "失败不该留实例目录" print(" ★ NeoForge 未 install:明说要跑 installer") def test_missing_server_jar_for_vanilla_is_reported(): """★ vanilla 没给 server.jar 要明说(不能起个残缺服)。""" tmp = _tmp() r = S.create_modded_server("inst1", version="1.21.1", loader="vanilla") assert r["ok"] is False, r assert "server_jar" in r["why"], r assert r.get("how_to"), r print(" ★ vanilla 缺 server.jar 明说") def test_bad_instance_name_rejected(): """★ 实例名要校验(它进路径,含分隔符会越界)。""" tmp = _tmp() for bad in ("../escape", "a/b", "", "x" * 100, "has space"): r = S.create_modded_server(bad, version="1.21.1", loader="vanilla", server_jar=str(_fake_jar(tmp))) assert r["ok"] is False, f"{bad!r} 应被拒" print(" ★ 非法实例名被拒(含路径分隔符)") # ---------- ④ 启动脚本 ---------- def test_start_script_is_executable_and_self_contained(): """★ 脚本要可执行且**自带 cd**(内容根零写入 ⇒ 不能依赖调用者 cwd)。""" tmp = _tmp() jar = _fake_jar(tmp) r = S.create_modded_server("inst1", version="1.21.1", loader="vanilla", server_jar=str(jar)) script = Path(r["start_script"]) assert script.exists(), script assert script.stat().st_mode & stat.S_IXUSR, "应可执行" body = script.read_text(encoding="utf-8") assert 'cd "$(dirname "$0")"' in body, "应自带 cd 到实例目录" assert "server.jar" in body print(" ★ 启动脚本可执行且自带 cd") def test_repeated_creation_is_idempotent_report(): """★ 重复创建同一实例要**如实报告**(不假装全新,也不默默覆盖 mod)。""" tmp = _tmp() jar = _fake_jar(tmp) a = _fake_jar(tmp, "modA.jar") r1 = S.create_modded_server("inst1", version="1.21.1", loader="vanilla", mod=[str(a)], server_jar=str(jar)) r2 = S.create_modded_server("inst1", version="1.21.1", loader="vanilla", mod=[str(a)], server_jar=str(jar)) assert r1["ok"] and r2["ok"] # 两次都成功(幂等),且 mod 没被重复堆积 mods = sorted(p.name for p in (Path(r2["instance"]) / "mods").iterdir()) assert mods == ["modA.jar"], mods print(" ★ 重复创建幂等(mod 不堆积)") if __name__ == "__main__": for fn in [test_mods_land_in_instance_not_content_root, test_two_instances_share_content_root_different_mods, test_instance_json_records_version_and_loader, test_runtime_dir_is_third_layer, test_unimplemented_loader_says_so, test_missing_mod_files_are_named, test_neoforge_requires_install_flag, test_missing_server_jar_for_vanilla_is_reported, test_bad_instance_name_rejected, test_start_script_is_executable_and_self_contained, test_repeated_creation_is_idempotent_report]: fn() print("\n加载器服务端:全部通过")