Files
agentmc/tests/test_version_isolation.py
JianFeeeee 330867c6df 版本隔离守卫(新套件立即抓出两处真实回流)
## 修掉渲染联测的嵌套实例目录

`test_render_bridge.py` 把实例写成 `<store>/instances/test-mcfold`。
而权威口径(mcview/lib/versions.cjs:59/61)是:

    contentRoot = BASE/store/<key>       共享只读(jar/libraries/assets)
    instanceDir = BASE/instances/<name>  实例独占(mods/config/options/saves)

⇒ 那是「一个版本的内容根里长出了实例态」。而版本隔离要防的崩因正是
「多实例共用 mods」—— 内层目录会把它重新引回来,而那条路径看起来完全合法。
改为 `BASE/instances/test-mcfold`。

## 新增 tests/test_version_isolation.py(4 项,默认套件里跑)

★ 它**不需要 JVM**(纯路径断言),所以必须落在默认测试里 ——
放进 `--with-render` 才跑的套件等于没守卫(大多数人不会加那个参数)。

  ✓ 三层同级(store/ · instances/ · runtime/ 是 BASE 下的兄弟)
  ✓ 内容根干净(无 instances/ 嵌套、无 mods/config/options.txt/saves)
  ✓ 测试实例归位(落在 instances/ 层,不在任何内容根下)
  ✓ 内容根无实例配置(查内容,不只查存在)

## ★ 守卫首次运行就抓出第二处(我此前漏掉的)

```
  ✗ 内容根被实例化:
    - store/vanilla-1.21.1/instances/(内容根里嵌套了实例层)
```

内容是 `instances/default/{options.txt,downloads/log.json}`(生成态,
正式 `instances/default` 已存在)⇒ 已删。两个内容根现已干净。

⇒ 这条说明:**「我检查过了」不等于「有一条会自己跑的检查」** ——
  我上一轮手工核实过 1.20.1 那处并修掉,却没发现 1.21.1 那处;
  守卫一运行,两处一次全暴露。

## 验证

  ./run_tests.sh               → 66 项
  ./run_tests.sh --with-render → **80 项全绿**
2026-09-26 07:23:54 +08:00

112 lines
4.9 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.

"""版本隔离守卫:实例目录与内容根必须同级,实例态不得回流进内容根。
权威口径(`mcview/lib/versions.cjs:59/61`):
contentRoot = BASE/store/<loader>-<version> 共享只读:jar / libraries / assets
instanceDir = BASE/instances/<name> 实例独占:mods / config / options / saves
runtimeDir = BASE/runtime/<name> 实例独占:X display / screenshots / logs
为什么这条值得单独一个文件
--------------------------
它**不需要 JVM**(纯路径断言,毫秒级),所以必须落在默认测试里 ——
放进行 `--with-render` 才跑的套件等于没守卫(大多数人不会加那个参数)。
★ 这条曾经真的被踩过:渲染联测把实例写成 `<store>/instances/<name>`,
于是「一个版本的内容根」里长出了实例态。而版本隔离要防的崩因正是
「多实例共用 mods」—— 内层目录会把它重新引回来,而那条路径**看起来完全合法**。
所以检查的不是「路径能不能用」,而是「它违反了哪条分层」。
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
BASE = Path("/home/newqqagent/mcview")
STORE = BASE / "store"
INSTANCES = BASE / "instances"
RUNTIME = BASE / "runtime"
# 测试会真正使用的实例(新增测试实例时加到这里,守卫才会管到它)
TEST_INSTANCES = ["test-mcfold", "exp-vanilla-1.20.1", "exp-loader-1201"]
def test_three_layers_are_siblings():
"""三层(store / instances / runtime)是 BASE 下的**兄弟**,不是嵌套。"""
for d in (STORE, INSTANCES, RUNTIME):
assert d.parent == BASE, f"{d} 应在 {BASE} 直下,实际父目录 {d.parent}"
assert STORE != INSTANCES and INSTANCES != RUNTIME
print(f" ✓ 三层同级({STORE.name}/ · {INSTANCES.name}/ · {RUNTIME.name}/)")
def test_no_instance_state_inside_content_roots():
"""★ 内容根里不得出现实例态(instances/ 或独占文件)。
这是本条守卫的核心:内容根是**共享只读**的,一旦有实例独占物落进来,
下一个实例就会读到它 —— 而那正是「共用 mods」类崩因的开端。
"""
if not STORE.is_dir():
print(" - 跳过(无 store/)")
return
offenders = []
for root in sorted(p for p in STORE.iterdir() if p.is_dir()):
if (root / "instances").exists():
offenders.append(f"{root}/instances/(内容根里嵌套了实例层)")
# 实例独占物(凡出现即说明有实例把内容根当成自己的家)
for name in ("mods", "config", "options.txt", "saves"):
p = root / name
if p.exists():
offenders.append(f"{p}(实例独占物落在内容根)")
assert not offenders, "内容根被实例化:\n - " + "\n - ".join(offenders)
print(f" ✓ 内容根干净({len(list(STORE.iterdir()))} 个版本目录)")
def test_test_instances_live_in_instances_layer():
"""测试用的实例目录必须落在 instances/ 层,且不在任何内容根下。"""
missing = []
for name in TEST_INSTANCES:
p = INSTANCES / name
if not p.exists():
missing.append(name)
continue
assert STORE not in p.parents, f"实例落在了内容根里:{p}"
# 缺目录不算失败(测试会自建),但已存在的必须位置正确
print(f" ✓ 测试实例归位({len(TEST_INSTANCES) - len(missing)}/{len(TEST_INSTANCES)} 已建)")
def test_content_root_has_no_shared_instance_config():
"""内容根里的 options.txt / mods 是「共用」的直接证据,必须为空。
★ 与上一条的区别:上一条查「存在」,这条查「内容」——
空目录也可能只是没写进去,所以要看是否真有文件。
"""
if not STORE.is_dir():
print(" - 跳过(无 store/)")
return
bad = []
for root in sorted(p for p in STORE.iterdir() if p.is_dir()):
for name in ("mods", "config"):
d = root / name
if d.is_dir() and any(d.iterdir()):
bad.append(f"{d}({len(list(d.iterdir()))} 项)")
assert not bad, "共享内容根里有实例配置(会让多实例互相污染):\n - " + "\n - ".join(bad)
print(" ✓ 内容根无实例配置")
if __name__ == "__main__":
tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
failed = []
for t in tests:
try:
t()
except AssertionError as e:
failed.append(f"{t.__name__}: {e}")
print(f" ✗ {t.__name__}: {e}")
except Exception as e:
failed.append(f"{t.__name__}: {e!r}")
print(f" ✗ {t.__name__}: {e!r}")
print()
if failed:
print(f"版本隔离守卫: {len(tests)-len(failed)}/{len(tests)} 通过")
sys.exit(1)
print(f"版本隔离守卫: {len(tests)}/{len(tests)} 全部通过")