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:
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 老档案,理由见模块文档):不猜、也不在这里改成「拒绝」,
|
/// 反查不到根 ⇒ 放行(M273 老档案,理由见模块文档):不猜、也不在这里改成「拒绝」,
|
||||||
/// 否则所有老用户的 `ext-` 磁贴会集体点不动。
|
/// 否则所有老用户的 `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> {
|
fn discovered(env: &HostEnv, instance: &InstanceSummary) -> Result<(), String> {
|
||||||
for root in roots::all(env) {
|
for root in roots::all(env) {
|
||||||
if external_instance_id(root.key(), &instance.version_id) != instance.id {
|
if external_instance_id(root.key(), &instance.version_id) != instance.id {
|
||||||
|
|||||||
@@ -207,9 +207,11 @@ fn isolation_write_then_read_round_trips() {
|
|||||||
/// | `legacy_external_instance_id("1.20.4")` | `ext-52d6191436663b4e`(= M273 的「只哈希版本目录名」)|
|
/// | `legacy_external_instance_id("1.20.4")` | `ext-52d6191436663b4e`(= M273 的「只哈希版本目录名」)|
|
||||||
/// | `external_instance_id("c:/mc/.minecraft", "1.20.4")` | `ext-1e6c24c50b4a95c2`(= root + `0` + version)|
|
/// | `external_instance_id("c:/mc/.minecraft", "1.20.4")` | `ext-1e6c24c50b4a95c2`(= root + `0` + version)|
|
||||||
///
|
///
|
||||||
/// 抓得住:把分隔符那一次乘法漏掉 / 把 `0` 字节也过一遍 `xor`(两种都改哈希)——那时**所有**存量
|
/// 抓得住:漏掉段间那次乘法、或改动迭代顺序——那时**所有**存量 `ext-` id 都会换一个值
|
||||||
/// `ext-` id 都会换一个值(`profiles.json` 里旧的 `ext-` 档案一律反查不到)。M304 把哈希抽成
|
/// (`profiles.json` 里旧的 `ext-` 档案一律反查不到)。段间那个分隔符**本身**不单独进哈希:它
|
||||||
/// [`super::discovered`] 里的一个多段函数,这条就是那次重构的回归网。
|
/// 等价于多乘一次素数(`hash ^= 0` 是恒等),痕迹只在这一次乘法上——所以「分隔符」与「那一次
|
||||||
|
/// 乘法」是同一件事,不是两处可分别改动的输入。M304 把哈希抽成 [`super::discovered`] 里的一个
|
||||||
|
/// 多段函数,这条就是那次重构的回归网。
|
||||||
#[test]
|
#[test]
|
||||||
fn instance_id_hashes_are_the_released_ones() {
|
fn instance_id_hashes_are_the_released_ones() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -223,3 +225,75 @@ fn instance_id_hashes_are_the_released_ones() {
|
|||||||
"M274 的 id 公式变了(存量 ext- 档案一律反查不到)"
|
"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})"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -25,7 +25,6 @@ use crate::dto::{
|
|||||||
LogSession,
|
LogSession,
|
||||||
NoticeView,
|
NoticeView,
|
||||||
PackState,
|
PackState,
|
||||||
Progress,
|
|
||||||
SettingGroupView,
|
SettingGroupView,
|
||||||
UpdateView,
|
UpdateView,
|
||||||
VersionSummary,
|
VersionSummary,
|
||||||
@@ -355,8 +354,10 @@ impl AppState {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 覆盖某实例的启动进度(mock / 真实后端共用同一入口)。
|
/// 覆盖某实例的启动进度(**只给单测用**:生产路径没有调用方,它就地改 `instances` 而不动
|
||||||
pub fn set_progress(&mut self, id: &str, progress: Progress) {
|
/// `published` ⇒ 两份会分叉;生产推进度走 `CoreEvent::InstanceRuntimeChanged` → `apply`)。
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn set_progress(&mut self, id: &str, progress: crate::dto::Progress) {
|
||||||
if let Some(instance) = self.instances.iter_mut().find(|item| item.id == id) {
|
if let Some(instance) = self.instances.iter_mut().find(|item| item.id == id) {
|
||||||
if let InstanceRuntime::Busy {
|
if let InstanceRuntime::Busy {
|
||||||
progress: current, ..
|
progress: current, ..
|
||||||
|
|||||||
Reference in new issue
Block a user