优化: run_command 实时流式 + 审批浮窗修复 + 文档探索闭环(fetch_url+obscura引导+model_fetch兼容+prompt策略+厂商预设)

This commit is contained in:
lxy
2026-08-01 17:58:47 +08:00
parent 0e0c6862ba
commit d664bdc309
25 changed files with 1693 additions and 44 deletions
+79 -1
View File
@@ -9,7 +9,7 @@
//!
//! 注:execute 逻辑本身未改动,此文件为零行为变更的纯新增测试。
use df_execute::shell::{execute, ShellRequest, ShellType};
use df_execute::shell::{execute, execute_streaming, ShellRequest, ShellType, StreamKind};
use std::collections::HashMap;
/// 平台默认 ShellType(对齐 shell.rs:31 Default impl:Windows→Cmd, 非 Windows→Sh)
@@ -159,3 +159,81 @@ async fn execute_working_dir() {
// 清理
let _ = std::fs::remove_dir_all(&tmp_for_cleanup);
}
// ============================================================
// execute_streaming 流式测试
// ============================================================
/// 流式:stdout 多行逐行回调,且 ShellResult 完整(行数对齐 + exit_code=0)。
///
/// 治 run_command 黑盒:验证 spawn 后逐行回调 vs 一次性返回的等价性(行内容 + 完整结果)。
#[tokio::test]
async fn streaming_stdout_lines_callback() {
// 多行输出:Cmd 用多个 echo(用 & 串联无依赖),Sh 用 printf 多行
let cmd = if cfg!(windows) {
"@echo line1 & @echo line2 & @echo line3"
} else {
"printf 'line1\\nline2\\nline3\\n'"
};
let mut lines: Vec<(StreamKind, String)> = Vec::new();
let res = execute_streaming(req(cmd), |kind, line| {
lines.push((kind, line.to_string()));
})
.await
.expect("execute_streaming 应返回 Ok");
assert_eq!(res.exit_code, Some(0), "成功命令 exit_code 应为 0");
// stdout 应含三行(line1/line2/line3)
assert!(res.stdout.contains("line1"), "stdout 应含 line1,实际: {:?}", res.stdout);
assert!(res.stdout.contains("line3"), "stdout 应含 line3,实际: {:?}", res.stdout);
// 回调收到的 stdout 行应含三行(过滤 stderr 干扰:Cmd 无 stderr,Sh 无 stderr)
let stdout_lines: Vec<&String> = lines.iter()
.filter(|(k, _)| *k == StreamKind::Stdout)
.map(|(_, l)| l)
.collect();
assert!(
stdout_lines.iter().any(|l| l.contains("line1")),
"回调应收到含 line1 的 stdout 行,实际: {:?}", stdout_lines
);
assert!(
stdout_lines.iter().any(|l| l.contains("line3")),
"回调应收到含 line3 的 stdout 行,实际: {:?}", stdout_lines
);
}
/// 流式:超时仍生效(timeout_secs=1 + 长睡命令,返回 Err)。
#[tokio::test]
async fn streaming_timeout_returns_err() {
let sleep_cmd = if cfg!(windows) {
"ping -n 5 127.0.0.1 > nul".to_string()
} else {
"sleep 5".to_string()
};
let request = ShellRequest {
command: sleep_cmd,
working_dir: None,
env: HashMap::new(),
timeout_secs: Some(1),
shell_type: Some(default_shell()),
};
let result = execute_streaming(request, |_, _| {}).await;
assert!(result.is_err(), "超时应返回 Err,实际: {:?}", result.as_ref().err());
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("超时") || msg.to_lowercase().contains("timeout"),
"错误信息应含超时提示,实际: {}",
msg
);
}
/// 流式:非零退出仍返回 Ok + exit_code 非 0(对齐 execute 语义)。
#[tokio::test]
async fn streaming_nonzero_exit() {
let mut callbacks = 0u32;
let res = execute_streaming(req("exit 1"), |_, _| { callbacks += 1; })
.await
.expect("非零退出应仍返回 Ok");
assert_ne!(res.exit_code, Some(0), "exit 1 的 exit_code 应非 0");
// exit 1 无输出,回调可为 0 次(无行)——不强制断言次数,只确认无 panic
let _ = callbacks;
}