`game::java::discover::path_key` 只在 `cfg!(windows)` 分支折分隔符,POSIX 上 把 `C:\a\b\java.exe` 原样留成带反斜杠的键,与 `C:/a/b/java.exe` 不同键 → `dedupe` 不去重。三条用例(Windows 语义无 cfg 门)因此在 `test (ubuntu-latest)` 挂:`path_key_normalises_separators`、 `dedupe_keeps_the_higher_priority_source`、 `java_discover::dedupe_collapses_one_install_found_twice`。 - `path_key`:`\?\` 前缀剥离与分隔符折成 `/` 改为**与平台无关**(同一个 `bin/java[.exe]` 用哪种拼法都是同一个文件,键必须相同);大小写折叠仍只有 Windows 做——POSIX 上 `Java` 与 `java` 是两个目录,折了会把两份真实安装误并。 已知取舍写进文档:POSIX 上文件名里的字面反斜杠会一并被当分隔符。 - 三条用例:不靠 `#[cfg(windows)]` 跳过。`path_key` 单测改成平台无关断言 + 大小写折叠的双侧断言(Windows 折 / POSIX 不折,两侧都钉住);集成用例的 「同一路径第二种写法」按平台给(Windows 用大小写+分隔符,POSIX 用分隔符)。 - 回归钉子:新增 `one_install_normalises_to_one_key_whatever_the_spelling` (普通写法 / 反斜杠写法 / `\?\` 前缀写法 / 混合写法 四种拼法同键)与 `path_key_folds_case_only_where_the_filesystem_does`。 验证:Linux(rust:1-bookworm 容器,ull-linux-target 热缓存) `cargo test -p unlucky-core --lib --test game` → 224 + 120 passed / 0 failed (基线是 lib 220/2 failed、game 119/1 failed); `cargo test --workspace --all-targets --no-fail-fast` → 34 个测试块 1137 passed / 0 failed(该次跑在 M48 launch 修复叠加的临时副本上,因为 M48 尚未合入 main,见报告)。Windows 本机 `cargo fmt --check`、 `cargo clippy -p unlucky-core --all-targets -D warnings`、 `cargo test -p unlucky-core` 全绿(17 个测试块 0 failed)。
130 lines
4.3 KiB
Rust
130 lines
4.3 KiB
Rust
//! `core::game::java::discover`:标准安装位置扫描与来源优先级。
|
||
//!
|
||
//! 真机上扫到什么取决于本机装了什么,所以断言只钉**不变量**:
|
||
//! 扫描结果形状、来源优先级(env 胜过扫盘)、去重保留高优先级、缺目录不报错。
|
||
|
||
use unlucky_core::game::java::discover;
|
||
use unlucky_core::game::java::mapping::{JavaRequirement, RequirementSource};
|
||
use unlucky_core::game::java::{pick, JavaOrigin, JavaRuntime};
|
||
|
||
fn runtime(path: &str, major: u32, origin: JavaOrigin) -> JavaRuntime {
|
||
JavaRuntime {
|
||
binary: std::path::PathBuf::from(path),
|
||
origin,
|
||
version: Some(format!("openjdk version \"{major}.0.1\"")),
|
||
major: Some(major),
|
||
bits: 64,
|
||
vendor: None,
|
||
}
|
||
}
|
||
|
||
fn at_least(minimum: u32) -> JavaRequirement {
|
||
JavaRequirement::new(minimum, None, RequirementSource::Table)
|
||
}
|
||
|
||
#[test]
|
||
fn scanned_paths_always_point_at_a_java_binary() {
|
||
// 本机可能一个都没有(裸机/容器)——那也算通过,只是列表为空。
|
||
for binary in discover::scan_java_binaries() {
|
||
assert!(
|
||
binary.is_file(),
|
||
"扫描结果必须是真实存在的文件:{}",
|
||
binary.display()
|
||
);
|
||
assert_eq!(
|
||
binary
|
||
.file_name()
|
||
.map(|name| name.to_string_lossy().into_owned()),
|
||
Some(unlucky_core::game::java::java_binary_name().to_string()),
|
||
"扫描结果必须是 bin/java[.exe]:{}",
|
||
binary.display()
|
||
);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn standard_roots_are_absolute_and_unique() {
|
||
let roots = discover::standard_roots();
|
||
for root in &roots {
|
||
assert!(
|
||
root.is_absolute(),
|
||
"候选根必须是绝对路径:{}",
|
||
root.display()
|
||
);
|
||
}
|
||
let mut keys: Vec<String> = roots.iter().map(|root| discover::path_key(root)).collect();
|
||
let total = keys.len();
|
||
keys.sort();
|
||
keys.dedup();
|
||
assert_eq!(keys.len(), total, "候选根不该重复");
|
||
}
|
||
|
||
#[test]
|
||
fn environment_java_beats_scanned_java_when_both_satisfy() {
|
||
// D13 的「优先级链不变」:环境变量是用户意图,扫盘只是候选。
|
||
let candidates = [
|
||
runtime(
|
||
"C:/Program Files/Eclipse Adoptium/jdk-21/bin/java.exe",
|
||
21,
|
||
JavaOrigin::System,
|
||
),
|
||
runtime(
|
||
"C:/Program Files/Microsoft/jdk-21/bin/java.exe",
|
||
21,
|
||
JavaOrigin::JavaHome,
|
||
),
|
||
];
|
||
let picked = pick(&candidates, &at_least(17)).expect("应有候选");
|
||
assert_eq!(picked.origin, JavaOrigin::JavaHome);
|
||
}
|
||
|
||
#[test]
|
||
fn scanned_java_is_still_used_when_nothing_else_matches() {
|
||
// MSI 装了 Temurin 21、没设环境变量、PATH 上是 Java 8:1.20.5 只认扫盘那个。
|
||
let candidates = [
|
||
runtime("java", 8, JavaOrigin::Path),
|
||
runtime(
|
||
"C:/Program Files/Java/jdk-21/bin/java.exe",
|
||
21,
|
||
JavaOrigin::System,
|
||
),
|
||
];
|
||
let picked = pick(&candidates, &at_least(21)).expect("应有候选");
|
||
assert_eq!(picked.origin, JavaOrigin::System);
|
||
}
|
||
|
||
#[test]
|
||
fn dedupe_collapses_one_install_found_twice() {
|
||
// `JAVA_HOME` 指向 Program Files 里的 JDK → 同一个 java 不应列两次。
|
||
// 第二条的「另一种写法」随平台给:Windows 上大小写和分隔符都可能不一致(NTFS 不区分
|
||
// 大小写),POSIX 上大小写是有效区别,只能靠分隔符拼出同一路径的第二种写法。
|
||
let duplicate = if cfg!(windows) {
|
||
"c:/program files/java/jdk-21/bin/java.exe"
|
||
} else {
|
||
"C:\\Program Files\\Java\\jdk-21/bin/java.exe"
|
||
};
|
||
let candidates = discover::dedupe(vec![
|
||
runtime(
|
||
"C:/Program Files/Java/jdk-21/bin/java.exe",
|
||
21,
|
||
JavaOrigin::JavaHome,
|
||
),
|
||
runtime(duplicate, 21, JavaOrigin::System),
|
||
]);
|
||
assert_eq!(candidates.len(), 1);
|
||
assert_eq!(candidates[0].origin, JavaOrigin::JavaHome);
|
||
}
|
||
|
||
#[test]
|
||
fn registry_absence_is_tolerated() {
|
||
// 非 Windows 必然为空;Windows 上没装老 Oracle JDK 也是空。
|
||
let homes = discover::registry_homes();
|
||
for home in &homes {
|
||
assert!(
|
||
home.is_absolute(),
|
||
"注册表里的 JavaHome 应是绝对路径:{}",
|
||
home.display()
|
||
);
|
||
}
|
||
}
|