//! 验证「历史种子导入的工作流名」与「正式工作流名」的隔离关系。 //! //! 用户意图:离线导入工具把旧计算结果导入,标记为已完成,避免重算。 //! 关键问题:导入到工作流 A,之后正式启动工作流 B(同名/异名),B 能否看到 A 标记的 converged? use common::config::GridConfig; use common::models::{GridPointParams, ModelSummary}; use mq::sqlite_queue::SqliteTaskQueue; use server::{db::Database, scheduler::GridScheduler}; use std::sync::Arc; fn make_params() -> GridPointParams { use common::models::GridAxisValue; GridPointParams { teff: GridAxisValue::from_value(20000.0), logg: GridAxisValue::from_value(5.0), loghe: GridAxisValue::from_value(-2.0), logc: GridAxisValue::from_value(-4.0), logn: GridAxisValue::from_value(-4.0), logo: GridAxisValue::from_value(-4.0), } } /// 构造一个收敛的 ModelSummary(result_valid=true, atmosphere_has_nan=false), /// 用 point_name 作权威名。供测试模拟离线导入写入 summary_json。 fn make_converged_summary(name: &str, params: &GridPointParams) -> ModelSummary { ModelSummary { name: name.to_string(), params: params.clone(), stages: Vec::new(), result_valid: true, final_max_relc: Some(0.001), final_chmax: Some(0.001), seed: None, atmosphere_has_nan: false, synspec_rc: None, synspec_error: None, synspec_sec: None, elapsed_sec: 0.0, energy_check: None, temp_check: None, emflux_check: None, bfac_check: None, note: None, } } /// 测试辅助:模拟离线导入——upsert 点 + 写收敛 summary(等价旧 mark_grid_point_imported)。 async fn mark_imported( db: &Database, name: &str, workflow: &str, params: &GridPointParams, method: &str, ) { db.upsert_grid_point_named(name, params, 0, workflow) .await .unwrap(); let summary = make_converged_summary(name, params); db.upsert_point_summary(name, workflow, &summary, method) .await .unwrap(); } /// 构造只含一个网格点(t20000_g5.0_he-2_c-4_n-4_o-4)的 config。 fn make_grid_cfg() -> GridConfig { let yaml = "grid:\n teff: [20000]\n logg: [5.0]\n loghe: [-2]\n logc: [-4]\n logn: [-4]\n logo: [-4]\n"; GridConfig::from_yaml_str(yaml).unwrap() } async fn setup() -> (Database, Arc) { let tmp = tempfile::tempdir().unwrap(); let db = Database::new(&tmp.path().join("db.db").to_string_lossy()) .await .unwrap(); let queue = Arc::new( SqliteTaskQueue::new(&tmp.path().join("q.db").to_string_lossy()) .await .unwrap(), ); let sched = Arc::new(GridScheduler::new(db.clone(), queue)); (db, sched) } /// 场景 1(正确用法):导入到工作流 "sdB_cno",再用同名 config initialize_grid。 /// 期望:initialize_grid 的 ON CONFLICT(workflow_name, name) DO NOTHING 保留 converged 状态。 #[tokio::test] async fn test_same_workflow_name_preserves_converged() { let (db, sched) = setup().await; let name = "t20000_g5.0_he-2_c-4_n-4_o-4"; let p = make_params(); // 模拟离线导入:upsert + 写收敛 summary,工作流名 = sdB_cno mark_imported(&db, name, "sdB_cno", &p, "cold_run").await; // 之后正式启动同名工作流:initialize_grid(sdB_cno) sched .initialize_grid(&make_grid_cfg(), "sdB_cno") .await .unwrap(); let st = db.get_grid_point_status(name, "sdB_cno").await.unwrap(); assert_eq!( st.unwrap().0, "completed", "同名工作流:导入的 converged 应被保留,避免重算" ); println!("✓ 场景1(同名):status=converged,旧结果被保留,不会重算"); } /// 场景 2(错误用法):导入到工作流 "imported",之后正式启动 "sdB_cno"。 /// 期望:sdB_cno 分区下是新插入的 pending 行,看不到 imported 分区的 converged。 #[tokio::test] async fn test_different_workflow_name_causes_recompute() { let (db, sched) = setup().await; let name = "t20000_g5.0_he-2_c-4_n-4_o-4"; let p = make_params(); // 模拟离线导入:导入到 "imported" 工作流 mark_imported(&db, name, "imported", &p, "cold_run").await; // 之后正式启动 "sdB_cno" 工作流 sched .initialize_grid(&make_grid_cfg(), "sdB_cno") .await .unwrap(); // imported 分区:converged(种子库有,但不会被 sdB_cno 调度看到) let st_imp = db.get_grid_point_status(name, "imported").await.unwrap(); assert_eq!(st_imp.unwrap().0, "completed"); // sdB_cno 分区:pending(重新算!看不到 imported 的 converged) let st_real = db.get_grid_point_status(name, "sdB_cno").await.unwrap(); assert_eq!( st_real.unwrap().0, "pending", "异名工作流:sdB_cno 看不到 imported 的 converged,会重算" ); println!("✓ 场景2(异名):sdB_cno 分区 status=pending,会重复计算 —— 验证了工作流名必须匹配"); } /// 场景 3(真实意图验证):旧网格有部分点已算完(导入为 converged), /// 新网格比旧网格多了若干点。用同名工作流启动后: /// - 旧点保持 converged(不重算) /// - 新点是 pending(会被调度计算) /// 这正是「同步旧结果避免重算」的核心语义。 #[tokio::test] async fn test_mixed_grid_import_then_init_avoids_recompute() { let (db, sched) = setup().await; let p_old = make_params(); // t20000_g5.0_... // 模拟离线导入:旧网格里这个点已收敛,导入到 sdB_cno mark_imported(&db, "t20000_g5.0_he-2_c-4_n-4_o-4", "sdB_cno", &p_old, "cold_run").await; // 正式启动 sdB_cno,config 比旧网格多了一个新点(t25000) let yaml = "grid:\n teff: [20000, 25000]\n logg: [5.0]\n loghe: [-2]\n logc: [-4]\n logn: [-4]\n logo: [-4]\n"; let cfg = GridConfig::from_yaml_str(yaml).unwrap(); sched.initialize_grid(&cfg, "sdB_cno").await.unwrap(); // 旧点:converged(不重算) let st_old = db .get_grid_point_status("t20000_g5.0_he-2_c-4_n-4_o-4", "sdB_cno") .await .unwrap(); assert_eq!( st_old.unwrap().0, "completed", "旧点应保持 converged 不重算" ); // 新点:pending(会被调度) let st_new = db .get_grid_point_status("t25000_g5.0_he-2_c-4_n-4_o-4", "sdB_cno") .await .unwrap(); assert_eq!(st_new.unwrap().0, "pending", "新点应为 pending 等待计算"); println!("✓ 场景3(混合网格):旧点converged保留 + 新点pending待算 —— 完全符合避免重算的意图"); } /// 场景 4(精度差异命门):旧 conv.json name=g5(无小数),但配置 logg=5.0 → model_name()=g5.0。 /// 导入工具必须把 name 重写为 g5.0 入库,否则 initialize_grid 插入的 g5.0 行与导入的 g5 行 /// 复合唯一键不匹配,导入的 converged 被孤立、g5.0 被重算。 /// 本测试直接模拟「入库的 grid_points.name = g5.0」(即工具重写后的状态), /// 验证 initialize_grid(sdB_cno) 后该行保持 converged(不重算)。 #[tokio::test] async fn test_precision_diff_import_then_init_preserves_converged() { let (db, sched) = setup().await; // 配置权威名(logg=5.0 → g5.0,保留小数) let canonical = "t20000_g5.0_he-2_c-4_n-4_o-4"; use common::models::GridAxisValue; let p = GridPointParams { teff: GridAxisValue::from_value(20000.0), logg: GridAxisValue::from_value(5.0), loghe: GridAxisValue::from_value(-2.0), logc: GridAxisValue::from_value(-4.0), logn: GridAxisValue::from_value(-4.0), logo: GridAxisValue::from_value(-4.0), }; // 模拟离线导入重写 name 后入库:grid_points.name = canonical(g5.0) mark_imported(&db, canonical, "sdB_cno", &p, "cold_run").await; // 启动同名工作流:initialize_grid 用配置 model_name()(=g5.0) 插入 let yaml = "grid:\n teff: [20000]\n logg: [5.0]\n loghe: [-2]\n logc: [-4]\n logn: [-4]\n logo: [-4]\n"; let cfg = GridConfig::from_yaml_str(yaml).unwrap(); sched.initialize_grid(&cfg, "sdB_cno").await.unwrap(); // 关键断言:name=g5.0 的行保持 converged(ON CONFLICT DO NOTHING 命中) let st = db .get_grid_point_status(canonical, "sdB_cno") .await .unwrap(); assert_eq!( st.unwrap().0, "completed", "精度一致(g5.0=g5.0)时导入的 converged 必须保留,不重算" ); println!("✓ 场景4(精度差异命门):g5.0 入库 + initialize_grid → converged 保留,避免重算"); }