From 27980cf5b3906d91d2c52028ce251047ecbb3718 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 13 Nov 2026 00:28:32 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(presenter):=20junction=20=E5=A4=B9?= =?UTF-8?q?=E5=85=B7=E6=94=B9=E8=B5=B0=20platform::child::quiet=EF=BC=8C?= =?UTF-8?q?=E4=B8=A4=E5=A4=84=E5=89=AF=E6=9C=AC=E6=94=B6=E6=88=90=E4=B8=80?= =?UTF-8?q?=E4=BB=BD=E5=85=B1=E4=BA=AB=20helper=EF=BC=88M291=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/presenter/src/host/game/roots.rs | 45 +++++++++++-------- .../src/host/game/tests_multiroot.rs | 20 ++------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/crates/presenter/src/host/game/roots.rs b/crates/presenter/src/host/game/roots.rs index a065664..6bad2a9 100644 --- a/crates/presenter/src/host/game/roots.rs +++ b/crates/presenter/src/host/game/roots.rs @@ -38,7 +38,8 @@ //! # 只读 //! //! 本模块只 `is_dir()` / `canonicalize()`:不建目录、不写一个字节。官方启动器那个根上尤其要紧 -//! ——那是**别人的**目录。 +//! ——那是**别人的**目录。(唯一的例外是 `#[cfg(test)]` 的 [`make_dir_link`]:它只在用例自己的 +//! 临时目录里造链接,不属于任何产品路径。) use std::collections::BTreeSet; use std::path::{Path, PathBuf}; @@ -131,6 +132,31 @@ pub(in crate::host) fn key_of(path: &Path) -> String { } } +/// 造一个目录链接:Windows 用 junction(无管理员权限也能建),Unix 用符号链接。造不出来返回 `false`。 +/// +/// 模块级而不是 `mod tests` 里,因为 [`super::tests_multiroot`] 的判据②也要造链接(M291):两处 +/// 各写一份一字不差的 `cmd /c mklink /J` 只是把同一个坑埋两遍,`child_quiet_guard` 也是一次抓两处。 +/// 可见性只开到 `crate::host`——调用方就这两个用例模块。 +/// +/// 走 [`unlucky_core::platform::child::quiet`]:本仓库的 exe 是 GUI 程序(PE Subsystem = 2), +/// spawn `cmd.exe` 这种控制台程序时 Windows 会新开一块控制台(桌面上「黑框闪一下」)。 +/// 语义不变:只看退出码(`.status()`),不读输出,失败一律 `false`。 +#[cfg(all(test, windows))] +pub(in crate::host) fn make_dir_link(link: &Path, target: &Path) -> bool { + unlucky_core::platform::child::quiet("cmd") + .args(["/c", "mklink", "/J"]) + .arg(link) + .arg(target) + .status() + .map(|status| status.success()) + .unwrap_or(false) +} + +#[cfg(all(test, not(windows)))] +pub(in crate::host) fn make_dir_link(link: &Path, target: &Path) -> bool { + std::os::unix::fs::symlink(target, link).is_ok() +} + #[cfg(test)] mod tests { use super::*; @@ -186,21 +212,4 @@ mod tests { ); std::fs::remove_dir_all(&root).ok(); } - - /// 造一个目录链接:Windows 用 junction(无管理员权限也能建),Unix 用符号链接。 - #[cfg(windows)] - fn make_dir_link(link: &Path, target: &Path) -> bool { - std::process::Command::new("cmd") - .args(["/c", "mklink", "/J"]) - .arg(link) - .arg(target) - .status() - .map(|status| status.success()) - .unwrap_or(false) - } - - #[cfg(not(windows))] - fn make_dir_link(link: &Path, target: &Path) -> bool { - std::os::unix::fs::symlink(target, link).is_ok() - } } diff --git a/crates/presenter/src/host/game/tests_multiroot.rs b/crates/presenter/src/host/game/tests_multiroot.rs index 16585f3..ecf6536 100644 --- a/crates/presenter/src/host/game/tests_multiroot.rs +++ b/crates/presenter/src/host/game/tests_multiroot.rs @@ -77,22 +77,8 @@ fn write_world(root: &Path, name: &str) { std::fs::write(dir.join("level.dat"), b"fixture").expect("level.dat"); } -/// 目录链接(Windows 用 junction:不需要管理员;其它平台符号链接)。造不出来返回 `false`。 -#[cfg(windows)] -fn link_dir(link: &Path, target: &Path) -> bool { - std::process::Command::new("cmd") - .args(["/c", "mklink", "/J"]) - .arg(link) - .arg(target) - .status() - .map(|status| status.success()) - .unwrap_or(false) -} - -#[cfg(not(windows))] -fn link_dir(link: &Path, target: &Path) -> bool { - std::os::unix::fs::symlink(target, link).is_ok() -} +// 目录链接夹具走 `host::game::roots::make_dir_link`——一份代码两处共用(M291)。别在这里再写一份 +// `cmd /c mklink /J`:多一个 spawn 点就多一个绕过 `platform::child::quiet` 的机会。 // ------------------------------------------------------------------ 判据①:两个根各一条 @@ -194,7 +180,7 @@ fn a_junction_to_the_configured_root_is_not_a_second_root() { let mut env = HostEnv::at_root(&root.0); write_version(&env.game, "A"); let link = root.0.join("linked"); - if !link_dir(&link, env.game.root()) { + if !super::roots::make_dir_link(&link, env.game.root()) { // 造不出链接就**不假绿**:这一条判据在造不出链接的盘上等于没验。 eprintln!("跳过:这个环境造不出目录链接({})", link.display()); return; From 375cc4f1fa780ba85b970d18c17dcd8643dc007c Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 13 Nov 2026 00:31:14 +0800 Subject: [PATCH 2/3] =?UTF-8?q?test(presenter):=20junction=20=E5=A4=B9?= =?UTF-8?q?=E5=85=B7=E6=94=B9=E6=88=90=E9=80=90=E6=AE=B5=20join=EF=BC=88?= =?UTF-8?q?=E5=B8=A6=E6=AD=A3=E6=96=9C=E6=9D=A0=E7=9A=84=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E8=AE=A9=20mklink=20=E6=8A=A5=E6=97=A0=E6=95=88=E8=AF=AD?= =?UTF-8?q?=E6=B3=95=EF=BC=8C=E7=94=A8=E4=BE=8B=E4=B8=80=E7=9B=B4=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E8=B7=B3=E8=BF=87=EF=BC=89=EF=BC=8C=E5=AE=88=E5=8D=AB?= =?UTF-8?q?=E8=A1=A5=E8=AE=B0=20M291=20=E5=8F=98=E5=BC=82=EF=BC=88M291?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/core/tests/child_quiet_guard.rs | 6 ++++++ crates/presenter/src/host/game/roots.rs | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/core/tests/child_quiet_guard.rs b/crates/core/tests/child_quiet_guard.rs index 3f994be..89ca7f7 100644 --- a/crates/core/tests/child_quiet_guard.rs +++ b/crates/core/tests/child_quiet_guard.rs @@ -35,6 +35,12 @@ //! `app-iced/src/platform/locale.rs` 的 `quiet(program)` 改回裸构造 ⇒ 红(用户报的那处 //! `reg.exe`);⑦`export.rs` 三个 opener 留裸 + 清单写明理由 ⇒ **不**红。 //! +//! M291(`presenter` 的 junction 夹具):⑧`host/game/roots.rs` 的 `make_dir_link` 改回裸 +//! `Command::new("cmd")` ⇒ 红并点名 `crates/presenter/src/host/game/roots.rs:146`。这一条同时钉 +//! 一处**收并**:改之前本守卫一次抓到两处(`mklink /J` 的产品副本 `roots.rs:193` 与测试副本 +//! `tests_multiroot.rs:83`),M291 把两份收成一个共享夹具(`roots::make_dir_link`,`#[cfg(test)]`), +//! 所以现在的变异只可能剩一处。 +//! //! ## P1-1:守卫自己被变异验了两遍才对的 //! //! ③第一次**没红**:断言搜整份源码,而 `quiet` 的文档注释里就写着 diff --git a/crates/presenter/src/host/game/roots.rs b/crates/presenter/src/host/game/roots.rs index 6bad2a9..0a0b6d3 100644 --- a/crates/presenter/src/host/game/roots.rs +++ b/crates/presenter/src/host/game/roots.rs @@ -194,7 +194,9 @@ mod tests { #[test] fn junction_to_the_same_folder_is_one_root() { let root = temp_root("ull-roots-junction"); - let target = root.join("official/.minecraft"); + // 逐段 `join`:`join("official/.minecraft")` 会留下正斜杠,而 `cmd` 把 `/` 当开关前缀, + // `mklink` 于是报「无效语法」⇒ 这条用例一直走的是下面的「跳过」分支(M291 实测)。 + let target = root.join("official").join(".minecraft"); std::fs::create_dir_all(target.join("versions")).expect("建目录"); let link = root.join("linked"); if !make_dir_link(&link, &target) { From 7d163174119bb9bb11730503dc0456747e322db8 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 13 Nov 2026 00:48:38 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(m291):=20=E5=AE=88=E5=8D=AB=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=9C=A8=20400=20=E8=A1=8C=E5=86=85=E6=94=B6=EF=BC=88?= =?UTF-8?q?=E2=91=A7=20=E5=B9=B6=E5=85=A5=20=E2=91=A6=20=E6=AE=B5=EF=BC=8C?= =?UTF-8?q?=E5=87=80=E5=A2=9E=200=20=E8=A1=8C=EF=BC=89=EF=BC=8Cmake=5Fdir?= =?UTF-8?q?=5Flink=20=E5=8F=AF=E8=A7=81=E6=80=A7=E6=94=B6=E7=AA=84?= =?UTF-8?q?=E5=88=B0=20pub(super)=EF=BC=88M291=20r1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/core/tests/child_quiet_guard.rs | 14 ++++---------- crates/presenter/src/host/game/roots.rs | 6 +++--- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/core/tests/child_quiet_guard.rs b/crates/core/tests/child_quiet_guard.rs index 89ca7f7..1031c2c 100644 --- a/crates/core/tests/child_quiet_guard.rs +++ b/crates/core/tests/child_quiet_guard.rs @@ -30,16 +30,10 @@ //! `creation_flags(CREATE_NO_WINDOW)` ⇒ [`the_constructor_actually_passes_create_no_window`] 红; //! ④`game/java/discover.rs` 的 `quiet("reg")` 改回裸构造 ⇒ 必须红(见 P1-1)。 //! -//! M217(落在**此前不在射程内**的 crate 上):⑤`presenter/src/host/env.rs` 塞裸 -//! `Command::new("tzutil")` ⇒ 红并点名 `crates/presenter/src/host/env.rs:<行>`;⑥ -//! `app-iced/src/platform/locale.rs` 的 `quiet(program)` 改回裸构造 ⇒ 红(用户报的那处 -//! `reg.exe`);⑦`export.rs` 三个 opener 留裸 + 清单写明理由 ⇒ **不**红。 -//! -//! M291(`presenter` 的 junction 夹具):⑧`host/game/roots.rs` 的 `make_dir_link` 改回裸 -//! `Command::new("cmd")` ⇒ 红并点名 `crates/presenter/src/host/game/roots.rs:146`。这一条同时钉 -//! 一处**收并**:改之前本守卫一次抓到两处(`mklink /J` 的产品副本 `roots.rs:193` 与测试副本 -//! `tests_multiroot.rs:83`),M291 把两份收成一个共享夹具(`roots::make_dir_link`,`#[cfg(test)]`), -//! 所以现在的变异只可能剩一处。 +//! M217(此前不在射程内的 crate):⑤`presenter/src/host/env.rs` 塞裸 `Command::new("tzutil")` +//! ⇒ 红并点名 `crates/presenter/src/host/env.rs:<行>`;⑥`app-iced/src/platform/locale.rs` 的 +//! `quiet(program)` 改回裸构造 ⇒ 红(用户报的 `reg.exe`);⑦`export.rs` 三个 opener 留裸 + +//! 理由 ⇒ **不**红;⑧M291:`roots.rs::make_dir_link` 改回裸构造 ⇒ 红并点名(收并后仅 1 处)。 //! //! ## P1-1:守卫自己被变异验了两遍才对的 //! diff --git a/crates/presenter/src/host/game/roots.rs b/crates/presenter/src/host/game/roots.rs index 0a0b6d3..3ead039 100644 --- a/crates/presenter/src/host/game/roots.rs +++ b/crates/presenter/src/host/game/roots.rs @@ -136,13 +136,13 @@ pub(in crate::host) fn key_of(path: &Path) -> String { /// /// 模块级而不是 `mod tests` 里,因为 [`super::tests_multiroot`] 的判据②也要造链接(M291):两处 /// 各写一份一字不差的 `cmd /c mklink /J` 只是把同一个坑埋两遍,`child_quiet_guard` 也是一次抓两处。 -/// 可见性只开到 `crate::host`——调用方就这两个用例模块。 +/// 可见性只开到 `super`(`host::game`)——两个调用方都是它的直接子模块。 /// /// 走 [`unlucky_core::platform::child::quiet`]:本仓库的 exe 是 GUI 程序(PE Subsystem = 2), /// spawn `cmd.exe` 这种控制台程序时 Windows 会新开一块控制台(桌面上「黑框闪一下」)。 /// 语义不变:只看退出码(`.status()`),不读输出,失败一律 `false`。 #[cfg(all(test, windows))] -pub(in crate::host) fn make_dir_link(link: &Path, target: &Path) -> bool { +pub(super) fn make_dir_link(link: &Path, target: &Path) -> bool { unlucky_core::platform::child::quiet("cmd") .args(["/c", "mklink", "/J"]) .arg(link) @@ -153,7 +153,7 @@ pub(in crate::host) fn make_dir_link(link: &Path, target: &Path) -> bool { } #[cfg(all(test, not(windows)))] -pub(in crate::host) fn make_dir_link(link: &Path, target: &Path) -> bool { +pub(super) fn make_dir_link(link: &Path, target: &Path) -> bool { std::os::unix::fs::symlink(target, link).is_ok() }