版本隔离守卫(新套件立即抓出两处真实回流)
## 修掉渲染联测的嵌套实例目录
`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 项全绿**
This commit is contained in:
111
tests/test_version_isolation.py
Normal file
111
tests/test_version_isolation.py
Normal file
@ -0,0 +1,111 @@
|
||||
"""版本隔离守卫:实例目录与内容根必须同级,实例态不得回流进内容根。
|
||||
|
||||
权威口径(`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)} 全部通过")
|
||||
Reference in New Issue
Block a user