fix(m157): r4 修复未收集的周快照用例 + 两处 clippy 红,并补整环回归(复审 p1-2)
P1-1 crates/core/src/launch/overrides/tests.rs:modded_weekly_snapshot_needs_the_root_version_for_the_gate 原先写在 backend_allowed_combines_capability_and_threshold 函数体内(unnameable_test_items,永不执行)。移到模块层;移动后首次真正执行暴露出用例自身的 EmptyJavaPath(AdapterOptions 默认 java 为空),补 JavaSelection 后绿。--list 现在 10 条 launch::overrides::tests::(含该名字)。
P1-2 clippy -D warnings 两处:cli/commands/instance_launch.rs needless_borrows_for_generic_args(去 &);presenter/mock_data.rs items_after_test_module(mock_launch 移到 mod tests 之前)。
P3-a 新增整环回归 app/tests/instances_save_loop.rs:真 Message → dispatch → 后端回错误 Toast → pump → refresh_view → sync_launch_form,断言 4g 草稿与脏标记都还在。
P3-b LaunchForm::matches 内存改按数值比(解析失败退文本),补 memory_is_compared_by_value_not_by_text(04096 / +4096 / 清空三档)。
P3-c 门槛文案不再在 catalog 硬编码 26.2:InstancesLaunchBackendGated 改用 {min} 占位符,页面从 LaunchFacts::min_version(← core GRAPHICS_BACKEND_MIN_VERSION)代入;金表同步;灰化态截图重拍。
This commit is contained in:
1 parent
211e07b470
commit
ee67965b9b
11 files changed
+285
-87
No files matched your search
@@ -12,6 +12,8 @@ use unlucky_presenter::settings_bridge::SettingsBridge;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod appearance;
|
mod appearance;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
mod instances_save_loop;
|
||||||
|
#[cfg(test)]
|
||||||
mod presenter;
|
mod presenter;
|
||||||
|
|
||||||
use super::message::Message;
|
use super::message::Message;
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
//! 「启动参数保存失败」的**整环**回归(M157 r4 复审 P3-a)。
|
||||||
|
//!
|
||||||
|
//! 仓里原来只有这条环的两半:`LaunchForm::sync` 的单测(本 crate 的 `pages/instances/state.rs`)
|
||||||
|
//! 与「Save 只派发不清脏」(`app/update/instances.rs`)。两半各自绿,但**接线**(消息 →
|
||||||
|
//! `dispatch` → 后端回事件 → `pump` → `refresh_view` → `sync_launch_form`)没有任何活着的断言。
|
||||||
|
//! 把 `dirty = false` 提前到派发前,只有这条整环测试会红(单测两半都不会)。
|
||||||
|
//!
|
||||||
|
//! 造的后端与真 host 的失败路径同形:收到 `SetInstanceLaunchArgs` 只回一条 Toast(错误的用户
|
||||||
|
//! 可见反馈),**不回** `InstancesChanged`——盘上没改,快照还是旧的。
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use unlucky_design::color::SchemeFlavor;
|
||||||
|
use unlucky_presenter::dto::{
|
||||||
|
AppCommand, AppEvent, Backend, CoreEvent, InstanceRuntime, InstanceSummary, LaunchArgsView,
|
||||||
|
LoaderInfo, LoaderKind,
|
||||||
|
};
|
||||||
|
use unlucky_presenter::router::Route;
|
||||||
|
use unlucky_presenter::settings_bridge::SettingsBridge;
|
||||||
|
use unlucky_presenter::toast::{Toast, ToastKind};
|
||||||
|
|
||||||
|
use crate::app::message::Message;
|
||||||
|
use crate::app::state::{App, TICK};
|
||||||
|
use crate::cli::Options;
|
||||||
|
use crate::pages;
|
||||||
|
use crate::theme::Families;
|
||||||
|
|
||||||
|
const FAMILIES: Families = Families {
|
||||||
|
text: "Segoe UI",
|
||||||
|
display: "Segoe UI",
|
||||||
|
mono: "Consolas",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 失败路径的后端:命令进台账;`SetInstanceLaunchArgs` 只回一条错误 Toast。
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct FailingSaveBackend {
|
||||||
|
dispatched: Rc<RefCell<Vec<AppCommand>>>,
|
||||||
|
queued: Vec<AppEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Backend for FailingSaveBackend {
|
||||||
|
fn drain_events(&mut self) -> Vec<AppEvent> {
|
||||||
|
std::mem::take(&mut self.queued)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch(&mut self, command: AppCommand) {
|
||||||
|
self.dispatched.borrow_mut().push(command.clone());
|
||||||
|
if matches!(command, AppCommand::SetInstanceLaunchArgs { .. }) {
|
||||||
|
// 真 host 在 `dispatch_inner` 的 Err 分支只发 toast(不发 InstancesChanged)。
|
||||||
|
self.queued.push(AppEvent::Toast(Toast {
|
||||||
|
id: 0,
|
||||||
|
kind: ToastKind::Error,
|
||||||
|
text: "内存超出范围".to_string(),
|
||||||
|
remaining_ms: 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 盘上的实例快照:内存 4096、其余留空(= 保存失败时 pump 会带回来的那份)。
|
||||||
|
fn snapshot() -> Vec<InstanceSummary> {
|
||||||
|
vec![InstanceSummary {
|
||||||
|
id: "survival".to_string(),
|
||||||
|
name: "生存".to_string(),
|
||||||
|
mc_version: "1.20.4".to_string(),
|
||||||
|
loader: LoaderInfo {
|
||||||
|
kind: LoaderKind::Vanilla,
|
||||||
|
version: String::new(),
|
||||||
|
},
|
||||||
|
last_played: None,
|
||||||
|
runtime: InstanceRuntime::Idle,
|
||||||
|
icon: None,
|
||||||
|
isolation: "isolated".to_string(),
|
||||||
|
version_id: "1.20.4".to_string(),
|
||||||
|
version_present: true,
|
||||||
|
launch: LaunchArgsView {
|
||||||
|
instance_id: "survival".to_string(),
|
||||||
|
memory_mb: Some(4096),
|
||||||
|
memory_effective_mb: 4096,
|
||||||
|
..LaunchArgsView::default()
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_with_snapshot() -> (App, Rc<RefCell<Vec<AppCommand>>>) {
|
||||||
|
let dispatched = Rc::new(RefCell::new(Vec::new()));
|
||||||
|
let backend = FailingSaveBackend {
|
||||||
|
dispatched: Rc::clone(&dispatched),
|
||||||
|
queued: vec![AppEvent::Core(CoreEvent::InstancesChanged(snapshot()))],
|
||||||
|
};
|
||||||
|
let app = App::with_presenter(
|
||||||
|
Options::default(),
|
||||||
|
FAMILIES,
|
||||||
|
SchemeFlavor::Light,
|
||||||
|
Box::new(backend),
|
||||||
|
SettingsBridge::memory_only(),
|
||||||
|
);
|
||||||
|
(app, dispatched)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **保存失败不许吃掉草稿**(整环):输入 `4g` → 点保存 → 后端回错误事件 → pump 重建视图。
|
||||||
|
///
|
||||||
|
/// 抓得住:①`Save` 在派发前清脏(草稿被旧快照覆盖成 4096);②`LaunchForm::sync` 去掉
|
||||||
|
/// `matches` 判据(同样覆盖);③`pump` 没把事件喂进 `apply`/`refresh_view`(表单不会被播种,
|
||||||
|
/// 下面的前置断言会红)。
|
||||||
|
/// 抓不住:真 host 的落盘失败(那是 `host::launch_args` 的单测射程)。
|
||||||
|
#[test]
|
||||||
|
fn failed_save_keeps_the_draft_through_the_whole_loop() {
|
||||||
|
let (mut app, dispatched) = app_with_snapshot();
|
||||||
|
|
||||||
|
// 首屏快照到位 → 自动选中 → 表单按盘上的值播种。
|
||||||
|
let _ = app.update(Message::Boot);
|
||||||
|
let _ = app.update(Message::Page(pages::Message::Navigate(Route::Detail(
|
||||||
|
"survival".to_string(),
|
||||||
|
))));
|
||||||
|
app.advance(TICK);
|
||||||
|
assert_eq!(app.launch_form.memory, "4096", "首帧要按快照播种");
|
||||||
|
assert!(!app.launch_form.dirty);
|
||||||
|
|
||||||
|
// 用户把内存改成非法值(host 会拒),然后点保存。
|
||||||
|
let _ = app.update(Message::Page(pages::Message::Instances(
|
||||||
|
pages::InstanceMessage::Memory("4g".to_string()),
|
||||||
|
)));
|
||||||
|
assert_eq!(app.launch_form.memory, "4g");
|
||||||
|
let _ = app.update(Message::Page(pages::Message::Instances(
|
||||||
|
pages::InstanceMessage::Save,
|
||||||
|
)));
|
||||||
|
assert!(
|
||||||
|
dispatched
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.any(|command| matches!(command, AppCommand::SetInstanceLaunchArgs { .. })),
|
||||||
|
"保存要把命令派发出去"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 失败回程:一条错误 Toast → pump → refresh_view → sync_launch_form(旧快照)。
|
||||||
|
app.advance(TICK);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.launch_form.memory, "4g",
|
||||||
|
"保存失败后,pump 用旧快照把用户刚敲的内容冲掉了"
|
||||||
|
);
|
||||||
|
assert!(app.launch_form.dirty, "草稿还在,脏标记也不能掉");
|
||||||
|
}
|
||||||
@@ -196,7 +196,7 @@ fn backend_row<'a>(
|
|||||||
ui,
|
ui,
|
||||||
TypeRole::BodySmall,
|
TypeRole::BodySmall,
|
||||||
false,
|
false,
|
||||||
texts.launch_backend_gated(&facts.mc_version),
|
texts.launch_backend_gated(&facts.mc_version, &facts.min_version),
|
||||||
ui.color(Role::Error),
|
ui.color(Role::Error),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,17 +87,30 @@ impl LaunchForm {
|
|||||||
|
|
||||||
/// 表单当前内容是否与一份快照(= 盘上已持久化的值)完全一致。
|
/// 表单当前内容是否与一份快照(= 盘上已持久化的值)完全一致。
|
||||||
///
|
///
|
||||||
/// 比较的是**文本口径**:内存按「快照有值 → 十进制串,无值 → 空串」对齐,其余直接比原文。
|
/// **内存按数值比**:host 会把 `"04096"` / `"+4096"` 归一成 `4096` 落盘,回程快照给的是
|
||||||
|
/// `Some(4096)`——按文本比会永远不等,于是表单一直脏、也不再跟随外部改动(r4 复审 P3-b)。
|
||||||
|
/// 解析失败(用户正在敲半截)才退回文本口径。
|
||||||
|
/// 其余字段直接比原文(JVM/游戏参数由 core 的 `tokenize` 消费,不做归一)。
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn matches(&self, view: &LaunchArgsView) -> bool {
|
pub fn matches(&self, view: &LaunchArgsView) -> bool {
|
||||||
let memory = view.memory_mb.map(|mb| mb.to_string()).unwrap_or_default();
|
|
||||||
self.jvm.trim() == view.jvm_args.trim()
|
self.jvm.trim() == view.jvm_args.trim()
|
||||||
&& self.game.trim() == view.game_args.trim()
|
&& self.game.trim() == view.game_args.trim()
|
||||||
&& self.memory.trim() == memory
|
&& self.memory_matches(view.memory_mb)
|
||||||
&& self.java.trim() == view.java_path.trim()
|
&& self.java.trim() == view.java_path.trim()
|
||||||
&& self.backend == view.graphics_backend
|
&& self.backend == view.graphics_backend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 内存字段是否等于快照里的值(数值优先,解析失败退文本)。
|
||||||
|
fn memory_matches(&self, persisted: Option<u32>) -> bool {
|
||||||
|
let typed = self.memory.trim();
|
||||||
|
match (typed.parse::<u32>(), persisted) {
|
||||||
|
(Ok(typed), Some(persisted)) => typed == persisted,
|
||||||
|
// 空输入 = 跟随全局:快照那边也应当是「没有实例覆盖」。
|
||||||
|
(Err(_), None) => typed.is_empty(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 提交补丁:**五项全给**(表单是整份可见的,不用增量语义会让「清空」无法表达)。
|
/// 提交补丁:**五项全给**(表单是整份可见的,不用增量语义会让「清空」无法表达)。
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn patch(&self) -> LaunchArgsPatch {
|
pub fn patch(&self) -> LaunchArgsPatch {
|
||||||
@@ -229,6 +242,34 @@ mod tests {
|
|||||||
assert_eq!(form.jvm, "-Xss4m");
|
assert_eq!(form.jvm, "-Xss4m");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// **内存按数值比**(r4 复审 P3-b):`04096` / `+4096` 落盘后回程是 `4096`,必须判等。
|
||||||
|
///
|
||||||
|
/// 抓得住:`matches` 退回文本比较——那样这两类输入会永远「没保存成功」,表单一直脏、
|
||||||
|
/// 之后外部改配置也不再重播种。
|
||||||
|
#[test]
|
||||||
|
fn memory_is_compared_by_value_not_by_text() {
|
||||||
|
for typed in ["04096", "+4096", " 4096 "] {
|
||||||
|
let mut form = LaunchForm::from_view(Some("a"), &view("a", "", Some(4096), "default"));
|
||||||
|
form.memory = typed.to_string();
|
||||||
|
form.dirty = true;
|
||||||
|
assert!(
|
||||||
|
form.matches(&view("a", "", Some(4096), "default")),
|
||||||
|
"`{typed}` 应判等"
|
||||||
|
);
|
||||||
|
form.sync(&view("a", "", Some(4096), "default"));
|
||||||
|
assert!(!form.dirty, "`{typed}` 保存确认后要清脏");
|
||||||
|
}
|
||||||
|
// 数值不等就是不等于;半截输入(解析失败)也不许判等。
|
||||||
|
let mut form = LaunchForm::from_view(Some("a"), &view("a", "", Some(4096), "default"));
|
||||||
|
form.memory = "8".to_string();
|
||||||
|
assert!(!form.matches(&view("a", "", Some(4096), "default")));
|
||||||
|
form.memory = "4g".to_string();
|
||||||
|
assert!(!form.matches(&view("a", "", Some(4096), "default")));
|
||||||
|
// 清空 = 跟随全局:快照也得是「没有实例覆盖」。
|
||||||
|
form.memory.clear();
|
||||||
|
assert!(form.matches(&view("a", "", None, "default")));
|
||||||
|
}
|
||||||
|
|
||||||
/// 补丁五项全给:空串就是「清空」,不留 `None` 让 host 猜。
|
/// 补丁五项全给:空串就是「清空」,不留 `None` 让 host 猜。
|
||||||
#[test]
|
#[test]
|
||||||
fn patch_always_covers_all_five_fields() {
|
fn patch_always_covers_all_five_fields() {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const PAGE_SOURCES: [(&str, &str); 6] = [
|
|||||||
/// | `isolated` / `shared` | core 的隔离档位**稳定 id**(`IsolationMode::as_str`),`isolation()` 只认这两档,其余原样上屏 |
|
/// | `isolated` / `shared` | core 的隔离档位**稳定 id**(`IsolationMode::as_str`),`isolation()` 只认这两档,其余原样上屏 |
|
||||||
/// | `pid` / `reason` | `Catalog::render` 的**占位符名**(与 catalog 里的 `{pid}` / `{reason}` 对应) |
|
/// | `pid` / `reason` | `Catalog::render` 的**占位符名**(与 catalog 里的 `{pid}` / `{reason}` 对应) |
|
||||||
/// | `Instances` | `Texts::keys()` 的**族名前缀**(`Key::ALL` 的过滤条件,不是文案) |
|
/// | `Instances` | `Texts::keys()` 的**族名前缀**(`Key::ALL` 的过滤条件,不是文案) |
|
||||||
const ALLOWED_LITERALS: [&str; 18] = [
|
const ALLOWED_LITERALS: [&str; 19] = [
|
||||||
"{date} {time} UTC",
|
"{date} {time} UTC",
|
||||||
"isolated",
|
"isolated",
|
||||||
"shared",
|
"shared",
|
||||||
@@ -76,6 +76,8 @@ const ALLOWED_LITERALS: [&str; 18] = [
|
|||||||
// M157:`Catalog::render` 的占位符名(`{version}` / `{value}` / `{default}`),不是文案。
|
// M157:`Catalog::render` 的占位符名(`{version}` / `{value}` / `{default}`),不是文案。
|
||||||
"version",
|
"version",
|
||||||
"value",
|
"value",
|
||||||
|
// M157 r4:门槛文案的占位符名(版本号由 DTO 给,目录里不写死)。
|
||||||
|
"min",
|
||||||
// M157:`instances/demo.rs` 的**出图夹具**(用户数据的形状,不是界面文案):
|
// M157:`instances/demo.rs` 的**出图夹具**(用户数据的形状,不是界面文案):
|
||||||
// 一个示例 JVM 参数、两个示例 MC 版本、一个示例 Java 路径,以及夹具开关的环境变量名与取值。
|
// 一个示例 JVM 参数、两个示例 MC 版本、一个示例 Java 路径,以及夹具开关的环境变量名与取值。
|
||||||
"-XX:+UseG1GC",
|
"-XX:+UseG1GC",
|
||||||
|
|||||||
@@ -120,10 +120,15 @@ impl Texts {
|
|||||||
pub(super) fn launch_backend(self) -> &'static str {
|
pub(super) fn launch_backend(self) -> &'static str {
|
||||||
self.text(Key::InstancesLaunchBackend)
|
self.text(Key::InstancesLaunchBackend)
|
||||||
}
|
}
|
||||||
/// 「需游戏版本 26.2 及以上(本实例 1.20.4)」。
|
/// 「需游戏版本 {min} 及以上(本实例 {version})」。
|
||||||
pub(super) fn launch_backend_gated(self, version: &str) -> String {
|
///
|
||||||
self.0
|
/// `min` 来自 DTO(`LaunchFacts::min_version` ← core 的 `GRAPHICS_BACKEND_MIN_VERSION`),
|
||||||
.render(Key::InstancesLaunchBackendGated, &[("version", version)])
|
/// **不在目录里硬编码版本号**——否则 core 一改门槛,文案会继续骗人(r4 复审 P3-c)。
|
||||||
|
pub(super) fn launch_backend_gated(self, version: &str, min: &str) -> String {
|
||||||
|
self.0.render(
|
||||||
|
Key::InstancesLaunchBackendGated,
|
||||||
|
&[("version", version), ("min", min)],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/// 「生效:vulkan」。
|
/// 「生效:vulkan」。
|
||||||
pub(super) fn launch_effective(self, value: &str) -> String {
|
pub(super) fn launch_effective(self, value: &str) -> String {
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ fn patch_from(args: &InstanceLaunchArgs) -> LaunchOverridesPatch {
|
|||||||
|
|
||||||
/// 全局层(三个设置键)。
|
/// 全局层(三个设置键)。
|
||||||
pub(crate) fn global_defaults(ctx: &Ctx) -> GlobalLaunchDefaults {
|
pub(crate) fn global_defaults(ctx: &Ctx) -> GlobalLaunchDefaults {
|
||||||
let store = SettingStore::load(&ctx.launcher_config(), launcher_schema());
|
let store = SettingStore::load(ctx.launcher_config(), launcher_schema());
|
||||||
let text = |key: &str| -> Option<String> {
|
let text = |key: &str| -> Option<String> {
|
||||||
store
|
store
|
||||||
.doc()
|
.doc()
|
||||||
|
|||||||
@@ -295,59 +295,62 @@ fn backend_allowed_combines_capability_and_threshold() {
|
|||||||
// 周快照:看库表。
|
// 周快照:看库表。
|
||||||
assert!(backend_allowed(&GameVersion::parse("26w14a"), &with_vulkan));
|
assert!(backend_allowed(&GameVersion::parse("26w14a"), &with_vulkan));
|
||||||
assert!(!backend_allowed(&GameVersion::parse("26w14a"), &without));
|
assert!(!backend_allowed(&GameVersion::parse("26w14a"), &without));
|
||||||
|
}
|
||||||
|
|
||||||
/// **周快照 + 加载器**(M157 r3 复审 P2-1):装配门限必须按**继承链的 MC 根版本**判。
|
/// **周快照 + 加载器**(M157 r3 复审 P2-1):装配门限必须按**继承链的 MC 根版本**判。
|
||||||
///
|
///
|
||||||
/// 造一个 Fabric 实例(id `26w14a-fabric-0.16.5`,继承周快照 `26w14a`),库表里有
|
/// 造一个 Fabric 实例(id `26w14a-fabric-0.16.5`,继承周快照 `26w14a`),库表里有
|
||||||
/// `org.lwjgl:lwjgl-vulkan`:`game_version = Some("26w14a")` ⇒ 命令行带 `--graphicsBackend vulkan`;
|
/// `org.lwjgl:lwjgl-vulkan`:`game_version = Some("26w14a")` ⇒ 命令行带 `--graphicsBackend vulkan`;
|
||||||
/// 退回到「拿带加载器后缀的 version_id 当游戏版本」⇒ `GameVersion::is_weekly_snapshot`
|
/// 退回到「拿带加载器后缀的 version_id 当游戏版本」⇒ `GameVersion::is_weekly_snapshot`
|
||||||
/// 的 6 字符形态认不出来,参数被跳过(**负向锚点**:这正是复审复现出来的静默丢失)。
|
/// 的 6 字符形态认不出来,参数被跳过(**负向锚点**:这正是复审复现出来的静默丢失)。
|
||||||
///
|
///
|
||||||
/// 抓得住:adapter 的 `game_version` 传错(调用方又回退成 `None`)——周快照 loader 实例
|
/// 抓得住:adapter 的 `game_version` 传错(调用方又回退成 `None`)——周快照 loader 实例
|
||||||
/// 「UI 说可用、命令行里没有」。
|
/// 「UI 说可用、命令行里没有」。
|
||||||
/// 抓不住:两个调用点自己有没有传(那由 presenter `host::launch::tests` 与
|
/// 抓不住:两个调用点自己有没有传(那由 presenter `host::launch::tests` 与
|
||||||
/// CLI `snapshot::tests` 各自的根版本断言守)。
|
/// CLI `snapshot::tests` 各自的根版本断言守)。
|
||||||
#[test]
|
#[test]
|
||||||
fn modded_weekly_snapshot_needs_the_root_version_for_the_gate() {
|
fn modded_weekly_snapshot_needs_the_root_version_for_the_gate() {
|
||||||
let version_id = "26w14a-fabric-0.16.5";
|
let version_id = "26w14a-fabric-0.16.5";
|
||||||
let libraries = [LibraryInput::new(
|
let libraries = [LibraryInput::new(
|
||||||
"C:/mc/libraries/org/lwjgl/lwjgl-vulkan/3.4.3/lwjgl-vulkan-3.4.3.jar",
|
"C:/mc/libraries/org/lwjgl/lwjgl-vulkan/3.4.3/lwjgl-vulkan-3.4.3.jar",
|
||||||
"org.lwjgl:lwjgl-vulkan:3.4.3",
|
"org.lwjgl:lwjgl-vulkan:3.4.3",
|
||||||
)];
|
)];
|
||||||
|
|
||||||
for (game_version, expected) in [(Some("26w14a"), true), (None, false)] {
|
for (game_version, expected) in [(Some("26w14a"), true), (None, false)] {
|
||||||
let snapshot = SnapshotInput {
|
let snapshot = SnapshotInput {
|
||||||
game_version,
|
game_version,
|
||||||
libraries: &libraries,
|
libraries: &libraries,
|
||||||
primary_jar: PathBuf::from("C:/mc/versions/x/x.jar"),
|
primary_jar: PathBuf::from("C:/mc/versions/x/x.jar"),
|
||||||
..SnapshotInput::minimal(version_id, "net.minecraft.client.main.Main")
|
..SnapshotInput::minimal(version_id, "net.minecraft.client.main.Main")
|
||||||
};
|
};
|
||||||
let options = AdapterOptions {
|
let options = AdapterOptions {
|
||||||
check_classpath: false,
|
// `assemble` 要求 Java 可执行文件非空(`EmptyJavaPath`);adapter 默认是空路径,
|
||||||
language: "zh-CN".to_string(),
|
// 所以这里显式给一个(原来这条用例从没被执行过,r4 把它提到模块层才暴露出来)。
|
||||||
..AdapterOptions::default()
|
java: JavaSelection::new("C:/java/bin/java.exe", 21, 64),
|
||||||
};
|
check_classpath: false,
|
||||||
let mut out = build_request(&snapshot, options).request;
|
language: "zh-CN".to_string(),
|
||||||
let resolved = resolve(
|
..AdapterOptions::default()
|
||||||
Some(&LaunchOverrides {
|
};
|
||||||
graphics_backend: Some(GraphicsBackend::Vulkan),
|
let mut out = build_request(&snapshot, options).request;
|
||||||
..LaunchOverrides::default()
|
let resolved = resolve(
|
||||||
}),
|
Some(&LaunchOverrides {
|
||||||
&GlobalLaunchDefaults::default(),
|
graphics_backend: Some(GraphicsBackend::Vulkan),
|
||||||
);
|
..LaunchOverrides::default()
|
||||||
apply(&mut out, &resolved);
|
}),
|
||||||
let command = assemble(&out, &ctx()).expect("组装应成功");
|
&GlobalLaunchDefaults::default(),
|
||||||
let sent = command.args.iter().any(|arg| arg == "--graphicsBackend");
|
);
|
||||||
assert_eq!(
|
apply(&mut out, &resolved);
|
||||||
sent, expected,
|
let command = assemble(&out, &ctx()).expect("组装应成功");
|
||||||
"game_version={game_version:?} 时是否下发不对:{:?}",
|
let sent = command.args.iter().any(|arg| arg == "--graphicsBackend");
|
||||||
command.args
|
assert_eq!(
|
||||||
);
|
sent, expected,
|
||||||
let skipped = command
|
"game_version={game_version:?} 时是否下发不对:{:?}",
|
||||||
.warnings
|
command.args
|
||||||
.iter()
|
);
|
||||||
.any(|warning| matches!(warning, AssemblyWarning::GraphicsBackendSkipped { .. }));
|
let skipped = command
|
||||||
assert_eq!(skipped, !expected, "跳过与否必须与下发相反(不静默)");
|
.warnings
|
||||||
}
|
.iter()
|
||||||
|
.any(|warning| matches!(warning, AssemblyWarning::GraphicsBackendSkipped { .. }));
|
||||||
|
assert_eq!(skipped, !expected, "跳过与否必须与下发相反(不静默)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,7 @@ pub(super) const GOLDEN: &[(&str, &str, &str)] = &[
|
|||||||
("InstancesLaunchJava", "Java executable", "Java 可执行文件"),
|
("InstancesLaunchJava", "Java executable", "Java 可执行文件"),
|
||||||
("InstancesLaunchJavaHint", "Empty = follow the global setting, then auto-pick (managed > JAVA_HOME > PATH).", "留空 = 跟随全局设置,再退到自动挑选(托管 > 环境变量 > 系统路径)。"),
|
("InstancesLaunchJavaHint", "Empty = follow the global setting, then auto-pick (managed > JAVA_HOME > PATH).", "留空 = 跟随全局设置,再退到自动挑选(托管 > 环境变量 > 系统路径)。"),
|
||||||
("InstancesLaunchBackend", "Graphics backend", "图形后端"),
|
("InstancesLaunchBackend", "Graphics backend", "图形后端"),
|
||||||
("InstancesLaunchBackendGated", "Requires MC 26.2+ (this instance: {version}). The flag is not sent on this version.", "需游戏版本 26.2 及以上(本实例 {version}),这个版本不会下发该参数。"),
|
("InstancesLaunchBackendGated", "Requires MC {min}+ (this instance: {version}). The flag is not sent on this version.", "需游戏版本 {min} 及以上(本实例 {version}),这个版本不会下发该参数。"),
|
||||||
("InstancesLaunchEffective", "Effective: {value}", "生效:{value}"),
|
("InstancesLaunchEffective", "Effective: {value}", "生效:{value}"),
|
||||||
("InstancesBackendDefault", "Default", "默认"),
|
("InstancesBackendDefault", "Default", "默认"),
|
||||||
("InstancesBackendOpengl", "OpenGL", "OpenGL 后端"),
|
("InstancesBackendOpengl", "OpenGL", "OpenGL 后端"),
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ macro_rules! instances_entries {
|
|||||||
InstancesLaunchJava => ("Java executable", "Java 可执行文件"),
|
InstancesLaunchJava => ("Java executable", "Java 可执行文件"),
|
||||||
InstancesLaunchJavaHint => ("Empty = follow the global setting, then auto-pick (managed > JAVA_HOME > PATH).", "留空 = 跟随全局设置,再退到自动挑选(托管 > 环境变量 > 系统路径)。"),
|
InstancesLaunchJavaHint => ("Empty = follow the global setting, then auto-pick (managed > JAVA_HOME > PATH).", "留空 = 跟随全局设置,再退到自动挑选(托管 > 环境变量 > 系统路径)。"),
|
||||||
InstancesLaunchBackend => ("Graphics backend", "图形后端"),
|
InstancesLaunchBackend => ("Graphics backend", "图形后端"),
|
||||||
InstancesLaunchBackendGated => ("Requires MC 26.2+ (this instance: {version}). The flag is not sent on this version.", "需游戏版本 26.2 及以上(本实例 {version}),这个版本不会下发该参数。"),
|
InstancesLaunchBackendGated => ("Requires MC {min}+ (this instance: {version}). The flag is not sent on this version.", "需游戏版本 {min} 及以上(本实例 {version}),这个版本不会下发该参数。"),
|
||||||
InstancesLaunchEffective => ("Effective: {value}", "生效:{value}"),
|
InstancesLaunchEffective => ("Effective: {value}", "生效:{value}"),
|
||||||
InstancesBackendDefault => ("Default", "默认"),
|
InstancesBackendDefault => ("Default", "默认"),
|
||||||
InstancesBackendOpengl => ("OpenGL", "OpenGL 后端"),
|
InstancesBackendOpengl => ("OpenGL", "OpenGL 后端"),
|
||||||
|
|||||||
@@ -210,6 +210,29 @@ pub fn mock_versions_for(category: VersionCategory) -> Vec<VersionSummary> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 演示用的启动参数视图(M157):一条真实形状的实例(有 JVM 参数、Vulkan、门槛放行)。
|
||||||
|
fn mock_launch() -> LaunchArgsView {
|
||||||
|
LaunchArgsView {
|
||||||
|
instance_id: "survival".to_string(),
|
||||||
|
jvm_args: "-XX:+UseG1GC".to_string(),
|
||||||
|
game_args: String::new(),
|
||||||
|
memory_mb: Some(4096),
|
||||||
|
memory_effective_mb: 4096,
|
||||||
|
java_path: String::new(),
|
||||||
|
java_effective: "C:/Java/temurin-21/bin/java.exe".to_string(),
|
||||||
|
graphics_backend: "vulkan".to_string(),
|
||||||
|
graphics_backend_effective: "vulkan".to_string(),
|
||||||
|
graphics_backend_allowed: true,
|
||||||
|
mc_version: "26.2-snapshot-2".to_string(),
|
||||||
|
graphics_backend_min_version: "26.2-snapshot-2".to_string(),
|
||||||
|
graphics_backend_options: vec![
|
||||||
|
"default".to_string(),
|
||||||
|
"opengl".to_string(),
|
||||||
|
"vulkan".to_string(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::dto::{AncientKind, VersionCategory, VersionKind};
|
use crate::dto::{AncientKind, VersionCategory, VersionKind};
|
||||||
@@ -240,26 +263,3 @@ mod tests {
|
|||||||
assert!(mock_versions_for(VersionCategory::Mod).is_empty());
|
assert!(mock_versions_for(VersionCategory::Mod).is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 演示用的启动参数视图(M157):一条真实形状的实例(有 JVM 参数、Vulkan、门槛放行)。
|
|
||||||
fn mock_launch() -> LaunchArgsView {
|
|
||||||
LaunchArgsView {
|
|
||||||
instance_id: "survival".to_string(),
|
|
||||||
jvm_args: "-XX:+UseG1GC".to_string(),
|
|
||||||
game_args: String::new(),
|
|
||||||
memory_mb: Some(4096),
|
|
||||||
memory_effective_mb: 4096,
|
|
||||||
java_path: String::new(),
|
|
||||||
java_effective: "C:/Java/temurin-21/bin/java.exe".to_string(),
|
|
||||||
graphics_backend: "vulkan".to_string(),
|
|
||||||
graphics_backend_effective: "vulkan".to_string(),
|
|
||||||
graphics_backend_allowed: true,
|
|
||||||
mc_version: "26.2-snapshot-2".to_string(),
|
|
||||||
graphics_backend_min_version: "26.2-snapshot-2".to_string(),
|
|
||||||
graphics_backend_options: vec![
|
|
||||||
"default".to_string(),
|
|
||||||
"opengl".to_string(),
|
|
||||||
"vulkan".to_string(),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user