fix(m195): 复审 r1 三项 —— 解压检查点真守卫、presenter 接线测试、取消文案改回真话
P2-1(解压循环检查点没有真守卫):那条用例原来的断言(status/target/files/bytes/无残留暂存) 在删掉 `extract_overrides` 的检查点后**依然成立**——提交前检查点⑤会替它停一次,于是几 GB 的包 会一直解到结束而测试全绿。改成数 `ImportStage::Extract` 事件:有检查点 = 1 条、删掉 = 3 条 (两面实测)。模块文档同时如实登记"还没有真守卫"的检查点:②③④⑤、全新导入 commit 的取消 分支、phase.rs 远端下载逐条、complete.rs 补全循环的 break。 P2-2(presenter 的真令牌接线零测试):新增 `pack/tests/cancel.rs`,直接调 `import::run` 并把 传入的 `Arc<AtomicBool>` **预先置 true**,判据只能是盘上事实(目标目录不存在 + profiles.json 零实例)——"没收到 Report"那种判据两条路都成立,`run` 早退条件里的 `cancel.load()` 会把 "没接线"完全遮住。 P2-3(用户可见文案说反了话):`PackCancelled` 中英双语从「导入会在后台继续跑完 / Import keeps running in the background…已经写下的文件会留下」改为「导入已取消 —— 后台已停手并回滚:没有留下 半包,也没有覆盖已有文件」;golden 金表同步;`model/tests.rs` 那条把旧文案钉死的用例改成钉新 语义(必须说清停手+回滚,且**禁止**出现 background);`model.rs` 的 `cancelled` 字段文档与 `dispatch.rs` 的取消注释同族修正。 验证:`cargo test -p unlucky-core -p unlucky-presenter` 1480 passed / 0 failed; i18n 31 / 0;app-iced packui 28 / 0;三 crate clippy 零 warning。
This commit is contained in:
1 parent
1441a240fe
commit
db5b08b9ab
9 files changed
+133
-29
No files matched your search
@@ -126,7 +126,7 @@ pub(in crate::host) fn in_flight(env: &HostEnv) -> usize {
|
||||
///
|
||||
/// 复审 r2 P2-1 的教训:手工 `finished()` 只在 `block_on(import_pack(..))` 之后调用,
|
||||
/// 而失败早退(探测失败 / 目标目录不可用)都在它之前 ⇒ 计数永远留在「在跑」,于是下一次
|
||||
/// **下载页**的「取消安装」不再发 `InstallCancelled`,还给内容库画一句「导入还在后台跑」的假话。
|
||||
/// **下载页**的「取消安装」不再发 `InstallCancelled`,还给内容库画一句「导入还在跑」的假话。
|
||||
///
|
||||
/// 现在计数由句柄的 `Drop` 负责:**任何**退出路径(早退、报错、`?`、panic 展开)都会减一。
|
||||
pub(super) struct InFlight {
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
//! | [`a_failed_new_instance_directory_can_be_reused_for_retry`] | 失败残留的空目录不挡重试 |
|
||||
//! | [`pack_epoch_is_per_host_and_is_its_own_counter`] | 代次按 host 分开,不再跨 host 串扰 |
|
||||
//! | [`a_failed_import_releases_the_in_flight_flag_and_keeps_install_cancel_intact`] | 早退路径放回「在跑」计数,取消安装仍是安装的取消 |
|
||||
//! | [`cancel::a_pre_cancelled_flag_really_stops_the_core_pipeline`] | 取消标记**真的**交给 core(盘上零残留、档案零实例) |
|
||||
//!
|
||||
//! 夹具(临时目录、写文件、泵 host)在 [`harness`]。
|
||||
|
||||
mod cancel;
|
||||
mod harness;
|
||||
mod scan;
|
||||
|
||||
@@ -388,7 +390,7 @@ fn a_failed_import_releases_the_in_flight_flag_and_keeps_install_cancel_intact()
|
||||
!pack_events(&events)
|
||||
.iter()
|
||||
.any(|event| matches!(event, PackEvent::Cancelled)),
|
||||
"此刻没有导入在跑,不该给内容库发「导入还在后台跑」"
|
||||
"此刻没有导入在跑,不该给内容库发「导入已取消」"
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//! presenter 的**真令牌**接线(M195 复审 P2-2):取消标记必须真的交给 core。
|
||||
//!
|
||||
//! # 为什么判据只能是盘上的事实
|
||||
//!
|
||||
//! 只断言"没收到 `PackEvent::Report`"是**两条路都成立**的伪证明:`import::run` 的早退条件是
|
||||
//! `report.status == Cancelled || cancel.load()`,后者会把"根本没接线"完全遮住——core 照旧跑完
|
||||
//! 并登记实例,界面只看到面板收回。那正是本 mission 要消灭的形态,所以本用例盯的是**目标目录
|
||||
//! 与 `profiles.json`**:标记一进来就是 true ⇒ 目录里一个文件都不该有、实例一个都不该登记。
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::dto::{ConflictDecisionsView, ImportRequestView};
|
||||
|
||||
use super::harness::{temp_dir, write};
|
||||
|
||||
/// 一个能走完管线的最小 MultiMC 包(目录形态:不需要 zip 写入器)。
|
||||
fn multimc_pack(dir: &std::path::Path) -> std::path::PathBuf {
|
||||
let source = dir.join("Better MC");
|
||||
write(
|
||||
&source.join("mmc-pack.json"),
|
||||
br#"{"formatVersion": 1, "components": [
|
||||
{"uid": "net.minecraft", "version": "1.20.1"}]}"#,
|
||||
);
|
||||
write(&source.join(".minecraft/mods/sodium.jar"), &[0x11; 1024]);
|
||||
write(&source.join(".minecraft/config/options.txt"), b"fov=90\n");
|
||||
source
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pre_cancelled_flag_really_stops_the_core_pipeline() {
|
||||
let dir = temp_dir("cancel-wired");
|
||||
let source = multimc_pack(&dir);
|
||||
let data = dir.join("data");
|
||||
std::fs::create_dir_all(&data).expect("data root");
|
||||
let env = crate::host::HostEnv::at_root(&data);
|
||||
let request = ImportRequestView {
|
||||
pack: source,
|
||||
instance: None,
|
||||
instance_name: Some("Cancelled Pack".to_string()),
|
||||
decisions: ConflictDecisionsView::default(),
|
||||
};
|
||||
// 目标目录与"是否登记"都按 host 的真口径算(判据不能自己另算一份)。
|
||||
let (target, register) = super::super::import::target_of(&env, &request).expect("目标目录");
|
||||
assert!(register, "走新实例这条路才覆盖「登记」这一环");
|
||||
|
||||
let (tx, _rx) = crate::host::job::channel();
|
||||
let cancel = Arc::new(AtomicBool::new(true));
|
||||
super::super::import::run(&env, &tx, &request, 11, Arc::clone(&cancel));
|
||||
drop(tx);
|
||||
assert!(cancel.load(Ordering::SeqCst), "用例前提:标记一直是 true");
|
||||
|
||||
assert!(
|
||||
!target.exists(),
|
||||
"取消后目标目录不该存在(core 在入口检查点就停手,暂存目录也删了):{}",
|
||||
target.display()
|
||||
);
|
||||
let profiles =
|
||||
unlucky_core::game::install::instance::read_profiles(&env.layout).expect("读档案");
|
||||
assert!(
|
||||
profiles.profiles.is_empty(),
|
||||
"取消后不许登记实例:{:?}",
|
||||
profiles.profiles
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
@@ -72,9 +72,10 @@ impl CoreHost {
|
||||
if let Some(cancel) = &self.install_cancel {
|
||||
cancel.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
// M113: import/export —— 同一块面板的「取消」对导入也生效:立标记 + 作废 pack 代次
|
||||
// M113: import/export —— 同一块面板的「取消」对导入也生效:立标记 + 作废 pack 代次。
|
||||
// 返回值告诉我们「刚才有没有导入在跑」:有的话**不能**再发 install 那条取消信号
|
||||
// (它说的是「安装已取消、实例未创建」,而导入会在后台跑完并登记实例)。
|
||||
// (它说的是「安装已取消、实例未创建」,而导入的取消是**管线级**的:core 在检查点
|
||||
// 停手并回滚,文案见 `PackCancelled`;M195 之前它会在后台跑完并登记实例)。
|
||||
let pack_was_running = addon::cancel_pack(&self.env);
|
||||
if pack_was_running {
|
||||
self.send(CoreEvent::Pack(Box::new(PackEvent::Cancelled)));
|
||||
|
||||
Reference in new issue
Block a user