Files
Unlucky-Launcher/crates/presenter/src/host/install_processors_tests.rs
T
AskaEth 1105a86448
CI / runtime smoke (macos arm64, probe) (push) Skipped
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
CI / runtime smoke (ubuntu x64, xvfb) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好一个文件) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Release / 选槽位(默认两个 Windows) (push) Canceled after 0s
Release / build (${{ matrix.slot }}) (push) Canceled after 0s
Release / GitHub Release (push) Canceled after 0s
Unlucky Launcher v0.2.2
2026-11-07 08:46:11 +08:00

214 lines
7.8 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.
//! host/install_processors.rs 的单测(拆文件守 400 行上限)。
//!
//! 夹具在 [`super::fixture`]:这里只断言「计划怎么组」「找不到 Java 怎么说」
//! 「哪一步挂了怎么报」「进度文案长什么样」。
use std::path::PathBuf;
use unlucky_core::addon::loader::forge::InstallProfile;
use unlucky_core::addon::loader::processors::{ProcessorError, StepStatus};
use unlucky_core::addon::loader::LoaderKind as CoreLoaderKind;
use unlucky_core::download::DownloadErrorKind;
use unlucky_core::game::install::{FileFailure, FileOutcome, FileRole, FileStatus, InstallReport};
use unlucky_core::game::java::JavaRequirement;
use super::fixture::{input, installer, outcome, profile, Fake, Harness};
use super::{diagnostic, java_missing, libraries_failed, stage, step_line, StageInput};
#[test]
fn step_line_names_the_step_and_marks_the_outcome() {
let executed = step_line(&outcome(StepStatus::Executed), 9);
assert_eq!(
executed,
"installer 处理器 3/9:net.minecraftforge:installertools:1.3.0(1234 ms)"
);
let skipped = step_line(&outcome(StepStatus::Skipped), 9);
assert!(skipped.contains("产物已就绪"), "{skipped}");
}
#[test]
fn diagnostic_keeps_the_stable_code_and_the_hint() {
let failed = ProcessorError::failed("net.minecraftforge:installertools:1.3.0", "1", "boom")
.with_hint("确认 Java 版本与磁盘权限");
let text = diagnostic(&failed);
assert!(text.contains("processor_failed"), "{text}");
assert!(text.contains("退出码 1"), "{text}");
assert!(text.contains("boom"), "stderr 尾部要留:{text}");
assert!(text.contains("建议:确认 Java 版本"), "{text}");
let plan = diagnostic(&ProcessorError::plan("install profile 缺 `jar`"));
assert!(plan.contains("processor_plan"), "{plan}");
}
#[test]
fn java_missing_points_at_the_java_page() {
let managed = PathBuf::from("/tmp/.ULL/java");
let text = java_missing(&JavaRequirement::table(21, None), &managed);
assert!(text.contains("Java 21+"), "{text}");
assert!(text.contains("Java」页"), "{text}");
assert!(text.contains("JAVA_HOME"), "{text}");
assert!(text.contains(&managed.display().to_string()), "{text}");
}
#[test]
fn libraries_failed_lists_at_most_three() {
let files: Vec<FileOutcome> = (0..4)
.map(|index| FileOutcome {
role: FileRole::Library,
name: format!("lib{index}.jar"),
dest: PathBuf::from(format!("lib{index}.jar")),
status: FileStatus::Failed,
bytes: 0,
source: None,
failure: Some(FileFailure {
kind: DownloadErrorKind::Network,
message: "连接超时".to_string(),
}),
})
.collect();
let report = InstallReport {
files,
version_json: None,
warnings: Vec::new(),
};
let text = libraries_failed(&report);
assert!(text.contains("4 个失败,共 4 个"), "{text}");
assert!(text.contains("lib0.jar"), "{text}");
assert!(!text.contains("lib3.jar"), "最多列三条:{text}");
}
#[test]
fn stage_skips_when_the_version_json_is_already_there() {
let h = Harness::new();
let installer = installer();
let profile = profile();
let mut input = input(&profile, &installer);
input.installed = true;
let fake = Fake::new();
let result = stage(&h.env, &h.engine, &input, &fake, &h.reporter(), &h.cancel);
assert!(matches!(result, Ok(None)));
assert!(fake.calls().is_empty(), "已装完就不该再动网络/Java");
assert!(h.log().contains("跳过 installer processors"), "{}", h.log());
}
#[test]
fn stage_skips_an_empty_processor_list() {
let h = Harness::new();
let installer = installer();
let profile = InstallProfile::default();
let input = input(&profile, &installer);
let fake = Fake::new();
let result = stage(&h.env, &h.engine, &input, &fake, &h.reporter(), &h.cancel);
assert!(matches!(result, Ok(None)), "没有 processor 就没这段");
assert!(fake.calls().is_empty());
}
#[test]
fn stage_reports_missing_installer_metadata() {
let h = Harness::new();
let input = StageInput {
kind: CoreLoaderKind::Forge,
mc_version: "1.20.1",
version_id: "1.20.1-forge-47.2.0",
profile: None,
installer: None,
installed: false,
};
let fake = Fake::new();
let err = stage(&h.env, &h.engine, &input, &fake, &h.reporter(), &h.cancel).unwrap_err();
assert!(err.contains("install profile"), "{err}");
assert!(h.log().contains("失败:"), "{}", h.log());
}
#[test]
fn stage_builds_the_command_line_from_the_install_profile() {
let h = Harness::new();
let installer = installer();
let profile = profile();
let input = input(&profile, &installer);
let fake = Fake::new();
let run = stage(&h.env, &h.engine, &input, &fake, &h.reporter(), &h.cancel)
.expect("stage 成功")
.expect("应当真的跑了 processors");
assert_eq!(run.executed(), 1);
let steps = fake.steps();
assert_eq!(steps.len(), 1);
let (index, label, args, classpath) = &steps[0];
assert_eq!(*index, 1);
assert_eq!(label, "net.minecraftforge:installertools:1.3.0");
assert_eq!(args, &vec!["--task".to_string(), "MCP_DATA".to_string()]);
assert_eq!(*classpath, 1, "jar 自己不在 -cp 里(拼命令行时才补)");
assert_eq!(fake.calls(), vec!["libraries", "java", "run"]);
let messages = h.messages();
assert!(
messages.iter().any(|m| m.contains("installer 处理器 1/1")),
"面板要看到当前处理器:{messages:?}"
);
assert!(
messages
.last()
.is_some_and(|m| m.contains("正在写入版本清单")),
"{messages:?}"
);
assert!(
h.log().contains("processors 完成:执行 1 步"),
"{}",
h.log()
);
}
#[test]
fn stage_fails_loudly_without_a_usable_java() {
let h = Harness::new();
let installer = installer();
let profile = profile();
let input = input(&profile, &installer);
let mut fake = Fake::new();
fake.java = None;
let err = stage(&h.env, &h.engine, &input, &fake, &h.reporter(), &h.cancel).unwrap_err();
assert!(err.contains("Java"), "{err}");
assert!(err.contains("托管目录"), "{err}");
assert_eq!(
fake.calls(),
vec!["libraries", "java"],
"没 Java 就不该起进程"
);
assert!(h.log().contains("失败:"), "{}", h.log());
}
#[test]
fn stage_fails_loudly_when_a_processor_exits_nonzero() {
let h = Harness::new();
let installer = installer();
let profile = profile();
let input = input(&profile, &installer);
let mut fake = Fake::new();
fake.run_error = Some(ProcessorError::failed(
"net.minecraftforge:installertools:1.3.0",
"1",
"java.lang.NoSuchMethodError",
));
let err = stage(&h.env, &h.engine, &input, &fake, &h.reporter(), &h.cancel).unwrap_err();
assert!(err.contains("processor_failed"), "{err}");
assert!(err.contains("NoSuchMethodError"), "{err}");
let log = h.log();
assert!(log.contains("失败:"), "{log}");
assert!(log.contains("NoSuchMethodError"), "日志要能回溯:{log}");
}
#[test]
fn stage_reports_incomplete_library_downloads() {
let h = Harness::new();
let installer = installer();
let profile = profile();
let input = input(&profile, &installer);
let mut fake = Fake::new();
fake.libraries_ok = false;
let err = stage(&h.env, &h.engine, &input, &fake, &h.reporter(), &h.cancel).unwrap_err();
assert!(err.contains("依赖库没下全"), "{err}");
assert!(err.contains("连接超时"), "{err}");
assert_eq!(fake.calls(), vec!["libraries"], "库没下全就别去选 Java");
let progress = h.meter.lock().expect("进度锁").progress.clone();
assert!(progress.files_total >= 2, "依赖库要计入文件总数");
}