Files
Unlucky-Launcher/crates/core/tests/game/java_discover.rs
T
AskaEth dc4fac8391 fix(core/game): M49 —— Java 去重键平台无关归一化(Linux CI 三红)
`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)。
2026-11-01 11:00:25 +08:00

130 lines
4.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! `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()
);
}
}