CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent), MCP server 接入外部 Agent 工具,GUI 开发中。
167 lines
6.0 KiB
Rust
167 lines
6.0 KiB
Rust
//! game 快照 → `LaunchRequest` 适配层测试(缺失库显式记录、features 派生)。
|
||
|
||
use crate::prelude::*;
|
||
|
||
fn write_file(path: &std::path::Path, content: &str) {
|
||
if let Some(parent) = path.parent() {
|
||
std::fs::create_dir_all(parent).expect("建目录");
|
||
}
|
||
std::fs::write(path, content).expect("写文件");
|
||
}
|
||
|
||
fn jvm_tokens() -> Vec<String> {
|
||
vec![
|
||
"-Djava.library.path=${natives_directory}".to_string(),
|
||
"-cp".to_string(),
|
||
"${classpath}".to_string(),
|
||
]
|
||
}
|
||
|
||
fn game_tokens() -> Vec<String> {
|
||
vec![
|
||
"--username".to_string(),
|
||
"${auth_player_name}".to_string(),
|
||
"--version".to_string(),
|
||
"${version_name}".to_string(),
|
||
]
|
||
}
|
||
|
||
#[test]
|
||
fn adapter_records_missing_libraries_and_fails_before_start() {
|
||
let dir = temp_dir("adapter-strict");
|
||
let lib_ok = dir.join("libraries/a.jar");
|
||
let lib_missing = dir.join("libraries/b.jar");
|
||
let primary = dir.join("versions/1.20.1/1.20.1.jar");
|
||
write_file(&lib_ok, "x");
|
||
write_file(&primary, "x");
|
||
let libraries = vec![
|
||
LibraryInput::new(lib_ok.clone(), "org.example:a:1"),
|
||
LibraryInput::new(lib_missing.clone(), "org.example:b:2"),
|
||
];
|
||
let jvm = jvm_tokens();
|
||
let game = game_tokens();
|
||
let snapshot = SnapshotInput {
|
||
version_name: Some("1.20.1 Fabric"),
|
||
version_type: Some("release"),
|
||
game_version: Some("1.20.1"),
|
||
main_class: Some("net.fabricmc.loader.impl.launch.knot.KnotClient"),
|
||
jvm_arguments: &jvm,
|
||
game_arguments: &game,
|
||
libraries: &libraries,
|
||
primary_jar: primary.clone(),
|
||
game_dir: dir.clone(),
|
||
assets_index: Some("17"),
|
||
natives_dir: dir.join("versions/1.20.1/natives"),
|
||
library_dir: dir.join("libraries"),
|
||
assets_root: dir.join("assets"),
|
||
game_assets: dir.join("assets/virtual/legacy"),
|
||
..SnapshotInput::minimal("1.20.1", "")
|
||
};
|
||
let mut options = AdapterOptions::strict();
|
||
options.java = JavaSelection::new("C:/java/bin/java.exe", 21, 64);
|
||
options.memory = MemoryOptions {
|
||
min_mb: Some(512),
|
||
max_mb: Some(2048),
|
||
};
|
||
options.resolution = Some(Resolution {
|
||
width: 1280,
|
||
height: 720,
|
||
});
|
||
|
||
let output = build_request(&snapshot, options);
|
||
assert_eq!(output.classpath_entries, 2, "a.jar + 主 jar");
|
||
assert_eq!(output.missing.len(), 1);
|
||
assert_eq!(output.missing[0].name, "org.example:b:2");
|
||
assert!(output.warnings.iter().any(|warning| matches!(
|
||
warning,
|
||
AdapterWarning::MissingLibrary { name, .. } if name == "org.example:b:2"
|
||
)));
|
||
// features 派生:给了分辨率就打开 has_custom_resolution;1.20.1 支持 QuickPlay。
|
||
assert!(output.request.features.has_custom_resolution);
|
||
assert!(output.request.features.has_quick_plays_support);
|
||
assert_eq!(output.request.instance.name, "1.20.1 Fabric");
|
||
assert_eq!(
|
||
output.request.manifest.main_class.as_deref(),
|
||
Some("net.fabricmc.loader.impl.launch.knot.KnotClient")
|
||
);
|
||
|
||
// 严格模式下缺失库 → 组装前显式失败(不再交给运行时 NoClassDefFoundError)。
|
||
let ctx = PlatformCtx::new(OsFamily::Windows, "x86_64");
|
||
match assemble(&output.request, &ctx) {
|
||
Err(LaunchError::Assembly(AssemblyError::MissingComponents(items))) => {
|
||
assert_eq!(items.len(), 1);
|
||
}
|
||
other => panic!("应因缺组件失败,实际:{other:?}"),
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn adapter_tolerant_mode_skips_missing_libraries() {
|
||
let dir = temp_dir("adapter-tolerant");
|
||
let lib_missing = dir.join("libraries/b.jar");
|
||
let libraries = vec![LibraryInput::new(lib_missing.clone(), "org.example:b:2")];
|
||
let jvm = jvm_tokens();
|
||
let snapshot = SnapshotInput {
|
||
main_class: Some("net.minecraft.client.main.Main"),
|
||
jvm_arguments: &jvm,
|
||
libraries: &libraries,
|
||
primary_jar: dir.join("client.jar"),
|
||
game_dir: dir.clone(),
|
||
assets_index: Some("5"),
|
||
..SnapshotInput::minimal("1.12.2", "")
|
||
};
|
||
let mut options = AdapterOptions::strict();
|
||
options.check_classpath = false;
|
||
options.java = JavaSelection::new("java", 8, 64);
|
||
|
||
let output = build_request(&snapshot, options);
|
||
assert!(
|
||
!output.request.classpath.is_empty(),
|
||
"宽松模式仍保留库(交给启动期报错)"
|
||
);
|
||
assert!(output.request.missing_components.is_empty());
|
||
// 宽松模式不报缺失库(其它告警——如缺游戏参数——与本用例无关)。
|
||
assert!(!output
|
||
.warnings
|
||
.iter()
|
||
.any(|warning| matches!(warning, AdapterWarning::MissingLibrary { .. })));
|
||
}
|
||
|
||
#[test]
|
||
fn adapter_warns_on_incomplete_manifest() {
|
||
let dir = temp_dir("adapter-incomplete");
|
||
let snapshot = SnapshotInput {
|
||
main_class: None,
|
||
..SnapshotInput::minimal("1.7.10", "")
|
||
};
|
||
let output = build_request(&snapshot, AdapterOptions::strict());
|
||
let warnings = &output.warnings;
|
||
assert!(warnings.contains(&AdapterWarning::MissingMainClass));
|
||
assert!(warnings.contains(&AdapterWarning::MissingAssetsIndex));
|
||
assert!(warnings.contains(&AdapterWarning::MissingGameArguments));
|
||
// 1.7.10 < 1.20:不支持 QuickPlay。
|
||
assert!(!output.request.features.has_quick_plays_support);
|
||
assert_eq!(
|
||
output.request.dirs.assets_index, "legacy",
|
||
"缺索引时兜底 legacy"
|
||
);
|
||
let _ = dir;
|
||
}
|
||
|
||
#[test]
|
||
fn adapter_legacy_arguments_are_passed_through() {
|
||
let dir = temp_dir("adapter-legacy");
|
||
let snapshot = SnapshotInput {
|
||
main_class: Some("net.minecraft.launchwrapper.Launch"),
|
||
legacy_arguments: Some("--username ${auth_player_name} --gameDir ${game_directory}"),
|
||
game_dir: dir.clone(),
|
||
..SnapshotInput::minimal("1.12.2", "")
|
||
};
|
||
let output = build_request(&snapshot, AdapterOptions::strict());
|
||
assert!(output.request.manifest.is_legacy());
|
||
assert!(output.request.manifest.game.is_empty());
|
||
assert!(!output
|
||
.warnings
|
||
.contains(&AdapterWarning::MissingGameArguments));
|
||
}
|