test(presenter): M306 —— 给「老档案放行」挂网 + 收三处顺带(错文档 / set_progress 分叉)

- host/game/tests.rs:+2 条判据。①`legacy_external_instance_id(version)` 造出的 M273 老磁贴
  ⇒ `Scan::revalidate` 必须 `Ok(())`;②同一个版本、id 换成 `external_instance_id(别的根, 版本)`
  ⇒ 必须 `Err` 且具名。变异实测:只删 `live.rs::discovered` 里那三行 ⇒ ① 红
  (`Err("实例 1.20.4 所属的游戏目录已不在…")`),② 不假红;还原后两条全绿
- host/game/tests.rs:210 的错话改正:段间分隔符**等价于一次乘法**(`hash ^= 0` 是恒等),
  不是「0 字节也过一遍 xor 也会改哈希」
- state.rs:`set_progress` 降成 `#[cfg(test)]`(它就地改 `instances` 而不动 `published` ⇒ 两份
  会分叉;生产推进度走 `CoreEvent::InstanceRuntimeChanged` → `apply`),只被它用的 `Progress`
  导入改成路径写法,文件 398 行(门禁 400)
- live.rs:`discovered` 的文档点名守着那三行的两条用例(crate 头 `#![allow(dead_code)]`,
  删了编译器一声不吭)
This commit is contained in:
AskaEth committed 2026-11-13 22:39:44 +08:00
1 parent b9c5f36fa5
commit 2c5fa53cc9
3 files changed
+86 -6

No files matched your search

@@ -102,6 +102,11 @@ fn profile(env: &HostEnv, instance: &InstanceSummary) -> Result<(), String> {
///
/// 反查不到根 ⇒ 放行(M273 老档案,理由见模块文档):不猜、也不在这里改成「拒绝」,
/// 否则所有老用户的 `ext-` 磁贴会集体点不动。
///
/// **末尾那条放行分支由 `host::game::tests::a_m273_legacy_ext_id_is_allowed` 守着**:本 crate 头是
/// `#![allow(dead_code)]`,删掉它编译器一个信号都不给(M304 r2 复审实测:删掉后整棵 presenter
/// 426 passed / 0 failed),所以「清死代码」之前请先跑那条用例。拒绝那一侧由同文件的
/// `an_ext_id_from_no_visible_root_is_refused` 守着。
fn discovered(env: &HostEnv, instance: &InstanceSummary) -> Result<(), String> {
for root in roots::all(env) {
if external_instance_id(root.key(), &instance.version_id) != instance.id {
+77 -3
View File
@@ -207,9 +207,11 @@ fn isolation_write_then_read_round_trips() {
/// | `legacy_external_instance_id("1.20.4")` | `ext-52d6191436663b4e`(= M273 的「只哈希版本目录名」)|
/// | `external_instance_id("c:/mc/.minecraft", "1.20.4")` | `ext-1e6c24c50b4a95c2`(= root + `0` + version)|
///
/// 抓得住:把分隔符那一次乘法漏掉 / 把 `0` 字节也过一遍 `xor`(两种都改哈希)——那时**所有**存量
/// `ext-` id 都会换一个值(`profiles.json` 里旧的 `ext-` 档案一律反查不到)。M304 把哈希抽成
/// [`super::discovered`] 里的一个多段函数,这条就是那次重构的回归网。
/// 抓得住:漏掉段间那次乘法、或改动迭代顺序——那时**所有**存量 `ext-` id 都会换一个值
/// (`profiles.json` 里旧的 `ext-` 档案一律反查不到)。段间那个分隔符**本身**不单独进哈希:它
/// 等价于多乘一次素数(`hash ^= 0` 是恒等),痕迹只在这一次乘法上——所以「分隔符」与「那一次
/// 乘法」是同一件事,不是两处可分别改动的输入。M304 把哈希抽成 [`super::discovered`] 里的一个
/// 多段函数,这条就是那次重构的回归网。
#[test]
fn instance_id_hashes_are_the_released_ones() {
assert_eq!(
@@ -223,3 +225,75 @@ fn instance_id_hashes_are_the_released_ones() {
"M274 的 id 公式变了(存量 ext- 档案一律反查不到)"
);
}
/// 一条 `ext-` 磁贴,版本本体**不在盘上**(这个临时根里没有 `versions/`)。
///
/// 走生产的构造函数([`super::discovered::external_summary`],id = M274 的 `fnv(根 ‖ 0 ‖ 版本)`),
/// 调用方只把 id 换成要判的那一种写法——这样「磁贴的形状」与界面上的永远同源。`roots[0]` 恒为
/// 配置根([`super::roots::all`] 的不变式:配置根永远第一位)。
fn ext_tile(env: &HostEnv, version_id: &str) -> InstanceSummary {
let roots = super::roots::all(env);
super::discovered::external_summary(env, &roots[0], version_id, &BTreeMap::new())
}
/// 建一个只有 `config/` 的临时根(用例自己的,`pid` + 标签让并行的两条不撞)。
fn bare_root(label: &str) -> (HostEnv, std::path::PathBuf) {
let dir = std::env::temp_dir().join(format!("ull-host-game-{label}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let env = HostEnv::at_root(&dir);
std::fs::create_dir_all(env.layout.config()).expect("config 目录");
(env, dir)
}
/// **M273 老档案必须放行**(M306:给 `refresh/live.rs::discovered` 最后那三行挂网)。
///
/// 判据:`ext-` id 只哈希版本目录名(M274 之前的公式)⇒ `roots::all` 的每个根都反查不到它。
/// 这种 id 的语义本来就是「按配置根解析」,真没版本时启动链给具名阶段失败(进程一个都没起来)
/// ——所以这里必须 `Ok(())`。拒绝它 = 所有存量 M273 用户的 `ext-` 磁贴集体点不动,还配一句错的
/// 原因(「所属的游戏目录已不在」)。
///
/// 抓得住:**只**删掉 `live.rs::discovered` 里
/// `if legacy_external_instance_id(&instance.version_id) == instance.id { return Ok(()); }` 这三行
/// ——本条立即红。这是它唯一的网:crate 头是 `#![allow(dead_code)]`,编译器一声不吭(M304 r2
/// 实测过删掉后整棵 presenter 426 passed / 0 failed)。
/// 抓不住:反查落空后「该拒绝」那一侧(下一条用例管)。
#[test]
fn a_m273_legacy_ext_id_is_allowed() {
let (env, dir) = bare_root("legacy-ok");
let mut tile = ext_tile(&env, "1.20.4");
tile.id = super::discovered::legacy_external_instance_id("1.20.4");
// 先清临时根再断言:断言失败也不在 /tmp 留空壳。
let verdict = Scan::revalidate(&env, &tile);
std::fs::remove_dir_all(&dir).ok();
assert!(
verdict.is_ok(),
"M273 老档案(id 只哈希版本目录名)必须放行,否则存量 `ext-` 磁贴全部点不动:{verdict:?}"
);
}
/// **换个根的 `ext-` id、反查落空,必须拒绝并具名**(同一条分叉的另一侧)。
///
/// 与上一条**同一个版本**,只把 id 换成 `external_instance_id(别的根, 版本)`:盘上根本没有那个根
/// ⇒ 一个根都对不上,而它也不是老档案 ⇒ 必须 `Err`。放行会让 `game_for` 退回配置根,配置根里
/// 恰好有同名版本就把**另一份**拉起来(gameDir / 存档都在另一边)。
///
/// 抓得住:把那条拒绝改写成 `Ok(())`(复审现场的另一半)。它与上一条各钉分叉的一侧,所以
/// 只删老档案那三行时本条**不会**假红——红色只会出现在上一条。
#[test]
fn an_ext_id_from_no_visible_root_is_refused() {
let (env, dir) = bare_root("legacy-refused");
let mut tile = ext_tile(&env, "1.20.4");
// 盘上不存在的根:`roots::all` 只给配置根 ⇒ 这个 id 一个根都对不上,且它不是老档案的公式。
tile.id = super::discovered::external_instance_id("d:/m306-no-such-root/.minecraft", "1.20.4");
let verdict = Scan::revalidate(&env, &tile);
std::fs::remove_dir_all(&dir).ok();
let reason = verdict.expect_err(
"反查落空又不是老档案 ⇒ 必须拒绝(放行会让 game_for 退回配置根,把同名版本拉起来)",
);
assert!(
reason.contains("游戏目录已不在") && reason.contains("1.20.4"),
"具名原因要说清是哪条实例、为什么(现在是:{reason})"
);
}