Files
agentmc/tests/test_modjar.py

127 lines
5.8 KiB
Python
Raw Permalink 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.

"""mod jar 解析回归 —— 用**真实 mod jar** 验证(三种加载器 + 边界)。"""
import sys, os, json, zipfile, io, tempfile
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from mcfold.modjar import parse_mod, scan_mods, summarize, _parse_toml, detect_loaders
FORGE = "/home/mc-test/mods/curtain-mc1.20.1-1.3.2.jar"
NEOFORGE= "/home/minecraft/mods/bettercombat-neoforge-2.3.2+1.21.1.jar"
MULTI = "/home/minecraft/mods/Explorify v1.6.4 f15-88.mod.jar"
BLOCKS = "/home/minecraft/mods/[扩充乐事] expandeddelight-0.1.4.jar"
def exists(p): return os.path.isfile(p)
def test_toml_parser_comment_headers():
"""★ 踩过的坑:表头带行内注释('[[mods]] #mandatory')必须正确识别。"""
text = '''
loaderVersion = "[47,)"
[[mods]] #mandatory
modId = "curtain" #mandatory
version = "${file.jarVersion}"
displayName = "Curtain"
[[dependencies.curtain]]
modId = "forge"
'''
m = _parse_toml(text)
assert len(m["mods"]) == 1, f"mods 段数应为 1,实际 {len(m['mods'])}"
assert m["mods"][0]["modId"] == "curtain", m["mods"][0]
assert m["mods"][0]["displayName"] == "Curtain"
# 依赖段的 modId 不应污染 mods[0]
assert m["mods"][0]["modId"] != "forge"
print(" ✓ TOML: 带行内注释的 [[mods]] 正确识别,依赖段不污染")
def test_forge_real():
if not exists(FORGE):
print(" - forge 样本不存在,跳过"); return
m = parse_mod(FORGE)
assert m.loader == "forge", m.loader
assert m.mod_id == "curtain", m.mod_id
assert m.name == "Curtain", m.name
# curtain 只加命令、不新增方块 —— 0 方块是正确结果
assert m.block_names == [], m.block_names
print(f" ✓ Forge 真实: id={m.mod_id} name={m.name} 方块=0(它只加命令)")
def test_neoforge_real():
if not exists(NEOFORGE):
print(" - neoforge 样本不存在,跳过"); return
m = parse_mod(NEOFORGE)
assert m.loader == "neoforge", m.loader
assert m.mod_id == "bettercombat", m.mod_id
assert m.version == "2.3.2+1.21.1", m.version
print(f" ✓ NeoForge 真实: id={m.mod_id} ver={m.version}")
def test_multi_loader_jar():
"""多加载器 jar(同时含 forge/neoforge/fabric 元数据)必须能诊断。"""
if not exists(MULTI):
print(" - 多加载器样本不存在,跳过"); return
m = parse_mod(MULTI)
assert len(m.loaders_present) >= 2, m.loaders_present
assert m.mod_id == "explorify", m.mod_id
# 结构 mod:用原版方块生成地形,自己不新增方块 ⇒ 0 方块正确
assert m.block_names == [], m.block_names
print(f" ✓ 多加载器 jar: 含 {m.loaders_present},id={m.mod_id}(结构 mod,0 方块)")
def test_block_extraction_real():
"""★ 核心:从真实 mod 提取方块名清单(这是 mod 支持的可见部分)。"""
if not exists(BLOCKS):
print(" - 方块 mod 样本不存在,跳过"); return
m = parse_mod(BLOCKS)
assert m.mod_id == "expandeddelight", m.mod_id
assert len(m.block_names) > 20, f"方块数偏少: {len(m.block_names)}"
# 已知方块(前面实测过)
for want in ("cask", "cheese_wheel", "asparagus_crate"):
assert want in m.block_names, f"缺方块 {want}"
q = m.qualified_blocks()
assert "expandeddelight:cask" in q, q[:5]
# 物品也应提取到
assert len(m.item_names) > 20, len(m.item_names)
print(f" ✓ 方块提取: {len(m.block_names)} 方块 / {len(m.item_names)} 物品,含 cask/cheese_wheel")
def test_no_state_id_claim():
"""★ 诚实性:解析结果里**不含** state id(因为拿不到),摘要必须明说。"""
m = parse_mod(BLOCKS) if exists(BLOCKS) else None
if m is None:
print(" - 跳过"); return
s = summarize([m])
assert "state id" in s["note"], s["note"]
assert "block_names" in s and s["block_name_count"] > 0
# 摘要里不应有任何形如 state id 的映射承诺
assert "state_ids" not in s and "stateId" not in json.dumps(s)
print(f" ✓ 诚实标注: 摘要含 {s['block_name_count']} 方块名,并注明无 state id")
def test_bad_jar_no_crash():
"""损坏的 jar 不能中断整目录扫描。"""
with tempfile.TemporaryDirectory() as d:
p = os.path.join(d, "broken.jar")
open(p, "wb").write(b"this is not a zip")
m = parse_mod(p)
assert not m.ok and "zip" in (m.error or "").lower(), m.error
good = os.path.join(d, "ok.jar")
with zipfile.ZipFile(good, "w") as zf:
zf.writestr("fabric.mod.json", json.dumps({"id":"t","name":"T","version":"1"}))
mods = scan_mods(d)
assert len(mods) == 2, len(mods)
s = summarize(mods)
assert s["loaded"] == 1 and s["failed"] == 1, s
print(f" ✓ 损坏 jar 不中断扫描: loaded={s['loaded']} failed={s['failed']}")
def test_json_meta_without_assets():
"""纯 JSON 元数据、无 assets 的 mod 也要能拿到 id(边界)。"""
with tempfile.TemporaryDirectory() as d:
p = os.path.join(d, "t.jar")
with zipfile.ZipFile(p, "w") as zf:
zf.writestr("fabric.mod.json", json.dumps(
{"id":"mymod","name":"My Mod","version":"2.0",
"depends":{"minecraft":">=1.20","fabric-api":"*"}}))
m = parse_mod(p)
assert m.loader == "fabric" and m.mod_id == "mymod", (m.loader, m.mod_id)
assert m.depends.get("minecraft") == ">=1.20", m.depends
assert m.block_names == []
print(" ✓ fabric 纯元数据: id/depends 正确,无 assets 不报错")
if __name__ == "__main__":
for fn in [test_toml_parser_comment_headers, test_forge_real, test_neoforge_real,
test_multi_loader_jar, test_block_extraction_real, test_no_state_id_claim,
test_bad_jar_no_crash, test_json_meta_without_assets]:
fn()
print("\nmod jar 解析:全部通过")