新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理
后端: - 工作流推进链(D-03):advance_task/状态机/闸门走 df-nodes Node trait,conditions 条件引擎扩展 - 想法评估闭环:启发式评分+对抗评估,df-ideas/scoring + df-storage/idea_eval_repo + idea 前端打通 - 全局事件数据总线:df-ai/context+context_helpers+augmentation 跨模块解耦 - AI planner/plan_hint/intent:aichat B 路线并行多轮基础 - patch_file 加固(TD-03/04):读改写整体锁防 lost update,expected_hash 合约闭环 - 压缩超时兜底(F-15 卡死根治) - F-09 多会话并发:LlmConcurrency per-conv + streamingGuard 前端守护 + verify 脚本 - 知识注入 DRY/skills/audit 扩展 清理: - aichat 技术债(误报 allow/死导入/过时注释 30 项) - URGENT.md 删除(11 项加急全解决/迁 todo) - 文档整理(todo/待决策/待审查/ARCHITECTURE/INDEX + 总线/技术债审查新文档)
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
//! DagExecutor 测试套件 — 从 executor.rs 抽离(纯搬迁,零行为变更)
|
||||
//!
|
||||
//! 抽离说明:executor.rs 原 513 行中 tests mod 占 305 行(60%),主体 run 仅 157 行。
|
||||
//! 独立成文件降低阅读 run 主体的滚动噪音,测试逻辑、断言、节点 mock 全部原样搬迁。
|
||||
//! 抽离说明:executor.rs 主体 run 逻辑之外曾带大量内联测试,占比偏高(抽离前的历史
|
||||
//! 行数分布不再维护),独立成文件降低阅读 run 主体的滚动噪音。测试逻辑、断言、节点 mock
|
||||
//! 全部原样搬迁。
|
||||
//! 通过 `#[path = "executor_helpers.rs"]` 内联回 executor.rs 的 #[cfg(test)] mod,
|
||||
//! 不进 lib.rs、不改 pub 路径、不影响外部调用方。
|
||||
//!
|
||||
//! SW-01 TOCTOU 相关测试(test_cancelled_node_skips_set_failed /
|
||||
//! test_cancelled_node_skips_set_completed / test_cancelled_node_emits_node_cancelled_event)
|
||||
//! 原样保留,锁定 executor.rs:136/138-153 的 Ok/Err 分支 is_cancelled 短路行为。
|
||||
//! 原样保留,锁定 executor.rs 节点执行完毕后 Ok/Err 两分支的 is_cancelled 短路行为
|
||||
//! (已取消节点跳过 set_completed/set_failed,保持 Cancelled 终态)。
|
||||
|
||||
use super::*;
|
||||
use crate::node::{Node, NodeResult, NodeSchema};
|
||||
@@ -312,3 +314,206 @@ async fn test_cancelled_node_emits_node_cancelled_event() {
|
||||
NodeStatus::Cancelled
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// conditions-eval feature:边条件路由测试(feature flag 开启时才编译)
|
||||
// ============================================================================
|
||||
|
||||
/// 测试节点:把 config.ok 作为 bool 输出到 { "ok": <bool> }。
|
||||
/// 边 condition "$.ok == true" 据此判定路由。
|
||||
#[cfg(feature = "conditions-eval")]
|
||||
struct BoolFlagNode {
|
||||
ok: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "conditions-eval")]
|
||||
#[async_trait]
|
||||
impl Node for BoolFlagNode {
|
||||
async fn execute(&self, _ctx: NodeContext) -> NodeResult {
|
||||
Ok(NodeOutput::from_value(serde_json::json!({ "ok": self.ok })))
|
||||
}
|
||||
|
||||
fn schema(&self) -> NodeSchema {
|
||||
NodeSchema {
|
||||
params: serde_json::Value::Null,
|
||||
output: serde_json::Value::Null,
|
||||
}
|
||||
}
|
||||
|
||||
fn node_type(&self) -> &str {
|
||||
"bool_flag"
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试节点:执行时记录到共享 Mutex,用于断言节点是否被执行。
|
||||
#[cfg(feature = "conditions-eval")]
|
||||
struct RecordingNode {
|
||||
flag: std::sync::Arc<std::sync::Mutex<Vec<String>>>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "conditions-eval")]
|
||||
#[async_trait]
|
||||
impl Node for RecordingNode {
|
||||
async fn execute(&self, _ctx: NodeContext) -> NodeResult {
|
||||
self.flag.lock().unwrap().push(self.name.clone());
|
||||
Ok(NodeOutput::empty())
|
||||
}
|
||||
|
||||
fn schema(&self) -> NodeSchema {
|
||||
NodeSchema {
|
||||
params: serde_json::Value::Null,
|
||||
output: serde_json::Value::Null,
|
||||
}
|
||||
}
|
||||
|
||||
fn node_type(&self) -> &str {
|
||||
"recording"
|
||||
}
|
||||
}
|
||||
|
||||
/// conditions-eval 开启时:边 condition 求值 true → 下游执行。
|
||||
///
|
||||
/// 结构:branch(true) --condition "$.ok == true"--> target_run
|
||||
/// branch(true) --condition "$.ok == false"--> target_skip
|
||||
/// branch.ok==true:第一条边满足 → target_run 执行;第二条边 false → target_skip 跳过。
|
||||
#[cfg(feature = "conditions-eval")]
|
||||
#[tokio::test]
|
||||
async fn conditions_eval_routes_by_edge_condition() {
|
||||
let mut dag = Dag::new();
|
||||
dag.add_node("branch".to_string(), Box::new(BoolFlagNode { ok: true }));
|
||||
|
||||
let ran = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
|
||||
let ran_run = ran.clone();
|
||||
let ran_skip = ran.clone();
|
||||
|
||||
dag.add_node(
|
||||
"target_run".to_string(),
|
||||
Box::new(RecordingNode { flag: ran_run, name: "target_run".to_string() }),
|
||||
);
|
||||
dag.add_node(
|
||||
"target_skip".to_string(),
|
||||
Box::new(RecordingNode { flag: ran_skip, name: "target_skip".to_string() }),
|
||||
);
|
||||
|
||||
// 两条带条件的出边:branch.ok==true 命中第一条
|
||||
dag.add_edge_with_condition(
|
||||
"branch".to_string(),
|
||||
"target_run".to_string(),
|
||||
"$.ok == true".to_string(),
|
||||
);
|
||||
dag.add_edge_with_condition(
|
||||
"branch".to_string(),
|
||||
"target_skip".to_string(),
|
||||
"$.ok == false".to_string(),
|
||||
);
|
||||
|
||||
let mut executor = DagExecutor::new(EventBus::new(), "test-cond-route".to_string());
|
||||
let outputs = executor
|
||||
.run(&dag, serde_json::Value::Null)
|
||||
.await
|
||||
.expect("run");
|
||||
|
||||
// target_run 被执行(条件命中);target_skip 被跳过(条件不满足,不入 outputs)
|
||||
assert!(
|
||||
outputs.contains_key("target_run"),
|
||||
"条件命中的下游应执行,outputs: {:?}",
|
||||
outputs.keys().collect::<Vec<_>>()
|
||||
);
|
||||
assert!(
|
||||
!outputs.contains_key("target_skip"),
|
||||
"条件不满足的下游应跳过(不入 outputs),outputs: {:?}",
|
||||
outputs.keys().collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let ran = ran.lock().unwrap();
|
||||
assert!(
|
||||
ran.contains(&"target_run".to_string()),
|
||||
"target_run 应被执行,ran: {:?}",
|
||||
ran
|
||||
);
|
||||
assert!(
|
||||
!ran.contains(&"target_skip".to_string()),
|
||||
"target_skip 不应被执行,ran: {:?}",
|
||||
ran
|
||||
);
|
||||
}
|
||||
|
||||
/// conditions-eval 开启时:节点所有入边均带 condition 且全部 false → 跳过执行。
|
||||
/// 验证状态机保持 Pending(跳过节点不 set_running/Completed/Failed)。
|
||||
#[cfg(feature = "conditions-eval")]
|
||||
#[tokio::test]
|
||||
async fn conditions_eval_skip_keeps_pending() {
|
||||
use df_types::types::NodeStatus;
|
||||
|
||||
let mut dag = Dag::new();
|
||||
dag.add_node("src".to_string(), Box::new(BoolFlagNode { ok: true }));
|
||||
dag.add_node(
|
||||
"skip_target".to_string(),
|
||||
Box::new(RecordingNode {
|
||||
flag: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
|
||||
name: "skip_target".to_string(),
|
||||
}),
|
||||
);
|
||||
// src.ok==true,但边要求 == false → 不满足 → 跳过
|
||||
dag.add_edge_with_condition(
|
||||
"src".to_string(),
|
||||
"skip_target".to_string(),
|
||||
"$.ok == false".to_string(),
|
||||
);
|
||||
|
||||
let mut executor = DagExecutor::new(EventBus::new(), "test-cond-skip".to_string());
|
||||
let outputs = executor
|
||||
.run(&dag, serde_json::Value::Null)
|
||||
.await
|
||||
.expect("run");
|
||||
|
||||
assert!(!outputs.contains_key("skip_target"), "skip_target 应被跳过不入 outputs");
|
||||
// 跳过的节点保持 Pending(未被状态机转换)
|
||||
assert_eq!(
|
||||
executor.state_machine.get(&"skip_target".to_string()),
|
||||
NodeStatus::Pending,
|
||||
"跳过的节点应保持 Pending"
|
||||
);
|
||||
assert_eq!(
|
||||
executor.state_machine.get(&"src".to_string()),
|
||||
NodeStatus::Completed,
|
||||
"源节点应正常完成"
|
||||
);
|
||||
}
|
||||
|
||||
/// conditions-eval 开启时:无条件入边 + 条件入边混合,无条件的总放行(不被跳过)。
|
||||
#[cfg(feature = "conditions-eval")]
|
||||
#[tokio::test]
|
||||
async fn conditions_eval_unconditional_edge_always_passes() {
|
||||
let mut dag = Dag::new();
|
||||
dag.add_node("a".to_string(), Box::new(BoolFlagNode { ok: true }));
|
||||
dag.add_node("b".to_string(), Box::new(BoolFlagNode { ok: false }));
|
||||
dag.add_node(
|
||||
"mix".to_string(),
|
||||
Box::new(RecordingNode {
|
||||
flag: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
|
||||
name: "mix".to_string(),
|
||||
}),
|
||||
);
|
||||
// 一条无条件边(b → mix)+ 一条条件边(a → mix, 要求 a.ok==false 不满足)
|
||||
dag.add_edge("b".to_string(), "mix".to_string());
|
||||
dag.add_edge_with_condition(
|
||||
"a".to_string(),
|
||||
"mix".to_string(),
|
||||
"$.ok == false".to_string(),
|
||||
);
|
||||
|
||||
let mut executor = DagExecutor::new(EventBus::new(), "test-cond-mix".to_string());
|
||||
let outputs = executor
|
||||
.run(&dag, serde_json::Value::Null)
|
||||
.await
|
||||
.expect("run");
|
||||
|
||||
// 存在无条件入边 → mix 必执行(无条件边不被跳过逻辑计入 has_any_cond_edge 的「全部 false」)
|
||||
assert!(
|
||||
outputs.contains_key("mix"),
|
||||
"存在无条件入边时节点应执行,outputs: {:?}",
|
||||
outputs.keys().collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user