按 M218 SURVEY-p1 §4.1 的清单接上 LiteLoader 的安装路线,不动 OptiFine。
- core: 新增 addon::loader::liteloader(官方 versions.json 解析 + 合成清单)与
addon::loader::parent(父版本链读盘定位原版版本);LoaderKind 加 LiteLoader;
LoaderRelease 加 parent_version_id、LoaderPlanInput::vanilla_installed → parent_installed;
LoaderInstallPlan::build 改返回 Result(父版本缺失具名报错);LoaderSource::resolve_on
支持指定父版本(仅 LiteLoader,其它组合具名 unsupported)。
- 继承边界(本 mission 第一步核实):arguments.game 父在前子在后追加,minecraftArguments
子覆盖父,启动顺序 legacy(24) → arguments.game(25)。所以 tweakClass 只写 arguments.game,
绝不写 minecraftArguments(会顶掉 Forge 1.12.2 的 FMLTweaker)。
- maven/fabric 的 `_ =>` 通配改成穷尽分支:非 maven 族不再静默回落 Forge 的仓库;
maven::{metadata_url,installer_url} 改返回 Option<String>。
- presenter: supported() 收 LiteLoader、wizard_choices 六选、dto::instances 按关注点拆成
instances/{loader,runtime,create,tests}.rs(原文件 395 行,动手前先拆)。
- cli: --loader 区分「认不出」(loader_unknown) 与「认得但装不了」(loader_unsupported);
<version> 位写加载器版本 id 时按父版本处理(只有 liteloader 接受);help 只改 install 段。
- app-iced: 下载页加载器 chip 加 LiteLoader(1.5.2~1.12.2,两头按官方没有构建拒),
chip 清单改为委派 Loader::ALL。
- 测试: 真实响应的裁剪夹具(dl.liteloader.com 与 repo.mumfrey.com 的 maven-metadata)
驱动「解析→计划→落盘→继承→启动参数顺序」全链;Forge 父链有回归(FMLTweaker 必须还在
且排在 LiteLoader 之前);父版本缺失/组合不支持/取消/坏表各自具名错误。
158 lines
6.0 KiB
Rust
158 lines
6.0 KiB
Rust
//! 边界与失败面:父版本缺失、组合不支持、数据源形状坏、取消。
|
||
//!
|
||
//! 网络层的失败映射(`DownloadError` → `loader_network`)由 `core::download` 的引擎测试
|
||
//! 与既有加载器测试覆盖,这里**不联网**:外部源的真实可用性在 mission 报告里单独列
|
||
//! (含探测结果),不混进单元测试冒充。
|
||
|
||
use std::sync::atomic::AtomicBool;
|
||
|
||
use unlucky_core::addon::loader::liteloader::parse_versions;
|
||
use unlucky_core::addon::loader::{
|
||
LoaderErrorKind, LoaderInstallPlan, LoaderKind, LoaderPlanInput, LoaderSource,
|
||
};
|
||
use unlucky_core::download::blocking::block_on;
|
||
use unlucky_core::game::arguments::Environment;
|
||
use unlucky_core::game::inherit::resolve;
|
||
use unlucky_core::game::install::{execute_with_cancel, InstallErrorKind};
|
||
use unlucky_core::game::layout::GameLayout;
|
||
|
||
use crate::support::{
|
||
engine, liteloader_release, prefill, put_version, read_version, TempDir, VANILLA_1122,
|
||
};
|
||
|
||
/// 父版本不是原版、又没装在盘上 → **具名报错**,绝不默默改成叠原版。
|
||
#[test]
|
||
fn missing_loader_parent_is_a_named_error() {
|
||
let temp = TempDir::new("missing-parent");
|
||
let layout = GameLayout::new(temp.path());
|
||
put_version(&layout, "1.12.2", VANILLA_1122);
|
||
let release = liteloader_release("1.12.2-forge-14.23.5.2860");
|
||
|
||
let error = LoaderInstallPlan::build(LoaderPlanInput {
|
||
layout: &layout,
|
||
release: &release,
|
||
environment: &Environment::host(),
|
||
parent_installed: false,
|
||
})
|
||
.expect_err("父版本缺失必须报错");
|
||
assert_eq!(error.kind(), LoaderErrorKind::NotFound);
|
||
assert!(
|
||
error.message().contains("1.12.2-forge-14.23.5.2860"),
|
||
"错误里要点名缺的是哪个父版本:{}",
|
||
error.message()
|
||
);
|
||
assert!(error.hint().is_some(), "要给出下一步怎么办");
|
||
}
|
||
|
||
/// 父版本链:读盘定位原版版本 id(合成 loader id / 定位 jar 都用它)。
|
||
#[test]
|
||
fn parent_chain_walks_to_the_vanilla_root() {
|
||
let temp = TempDir::new("parent-chain");
|
||
let layout = GameLayout::new(temp.path());
|
||
put_version(&layout, "1.12.2", VANILLA_1122);
|
||
put_version(
|
||
&layout,
|
||
"1.12.2-forge-14.23.5.2860",
|
||
crate::support::FORGE_1122,
|
||
);
|
||
|
||
assert_eq!(
|
||
unlucky_core::addon::loader::parent::root_version_id(&layout, "1.12.2-forge-14.23.5.2860")
|
||
.expect("两层链应走得到根"),
|
||
"1.12.2"
|
||
);
|
||
assert_eq!(
|
||
unlucky_core::addon::loader::parent::root_version_id(&layout, "1.12.2").expect("根是自己"),
|
||
"1.12.2"
|
||
);
|
||
|
||
// 没装的父版本 → not_found(而不是猜一个原版版本号)。
|
||
let error = unlucky_core::addon::loader::parent::root_version_id(&layout, "9.9.9-forge-1.0.0")
|
||
.expect_err("缺父版本必须报错");
|
||
assert_eq!(error.kind(), LoaderErrorKind::NotFound);
|
||
}
|
||
|
||
/// 只有 LiteLoader 接受「父版本不是原版」;别家**具名拒绝**,不去发请求。
|
||
#[test]
|
||
fn only_liteloader_accepts_a_non_vanilla_parent() {
|
||
let temp = TempDir::new("combo-guard");
|
||
let engine = engine(temp.path());
|
||
let source = LoaderSource::new(&engine, temp.path());
|
||
let error = block_on(async {
|
||
source
|
||
.resolve_on(
|
||
LoaderKind::Forge,
|
||
"1.12.2",
|
||
Some("14.23.5.2860"),
|
||
Some("1.12.2-liteloader-1.12.2-SNAPSHOT"),
|
||
)
|
||
.await
|
||
})
|
||
.expect_err("Forge 叠在 LiteLoader 上必须被拒");
|
||
assert_eq!(error.kind(), LoaderErrorKind::Unsupported);
|
||
assert!(
|
||
error
|
||
.message()
|
||
.contains("1.12.2-liteloader-1.12.2-SNAPSHOT"),
|
||
"要说清是哪个组合:{}",
|
||
error.message()
|
||
);
|
||
}
|
||
|
||
/// 版本表形状坏 / 空表:各自给具名大类,不当成「没有版本」糊过去。
|
||
#[test]
|
||
fn broken_version_table_is_reported_distinctly() {
|
||
let shape = parse_versions(r#"{"versions": []}"#).expect_err("形状不符");
|
||
assert_eq!(shape.kind(), LoaderErrorKind::Metadata);
|
||
let empty = parse_versions(r#"{"meta": {}, "versions": {}}"#).expect_err("空表");
|
||
assert_eq!(empty.kind(), LoaderErrorKind::NotFound);
|
||
|
||
// 版本段缺失(记录没有 version):只剩 latest 别名 → 仍当空表处理。
|
||
let only_latest = r#"{"versions": {"1.7.10": {"artefacts": {"com.mumfrey:liteloader":
|
||
{"latest": {"tweakClass": "x", "version": "", "file": "liteloader.jar"}}}}}}"#;
|
||
let error = parse_versions(only_latest).expect_err("只有 http 别名时没有可用版本");
|
||
assert_eq!(error.kind(), LoaderErrorKind::NotFound);
|
||
}
|
||
|
||
/// 取消:执行段看标志就返回 `Cancelled`,**版本 JSON 不落盘**(不留半成品)。
|
||
#[test]
|
||
fn cancel_leaves_no_manifest() {
|
||
let temp = TempDir::new("cancel");
|
||
let layout = GameLayout::new(temp.path());
|
||
put_version(&layout, "1.12.2", VANILLA_1122);
|
||
let release = liteloader_release("1.12.2");
|
||
let plan = LoaderInstallPlan::build(LoaderPlanInput {
|
||
layout: &layout,
|
||
release: &release,
|
||
environment: &Environment::host(),
|
||
parent_installed: true,
|
||
})
|
||
.expect("计划应可组");
|
||
|
||
let engine = engine(temp.path());
|
||
prefill(&plan.install);
|
||
let cancel = AtomicBool::new(true);
|
||
let error = block_on(execute_with_cancel(
|
||
&engine,
|
||
&plan.install,
|
||
4,
|
||
None,
|
||
Some(&cancel),
|
||
))
|
||
.expect_err("取消必须返回错误");
|
||
assert_eq!(error.kind(), InstallErrorKind::Cancelled);
|
||
assert!(
|
||
!layout.version_json(&release.version_id).exists(),
|
||
"取消之后不许留下版本清单"
|
||
);
|
||
|
||
// 继承链在缺清单时也应明确报「父版本缺失」,而不是静默降级成原版。
|
||
let mut source = std::collections::BTreeMap::new();
|
||
source.insert("1.12.2".to_string(), read_version(&layout, "1.12.2"));
|
||
let error = resolve(&source, &release.version_json).expect_err("父版本缺失");
|
||
assert!(matches!(
|
||
error,
|
||
unlucky_core::game::GameError::MissingParent { .. }
|
||
));
|
||
}
|