优化: AI Chat全栈多批审查修复与架构清理(risk_level清理/路由解耦/工具渲染/测试补测/死代码)
This commit is contained in:
@@ -52,7 +52,8 @@ pub struct ShellRequest {
|
||||
|
||||
/// 执行 Shell 命令
|
||||
///
|
||||
/// TODO: 完整实现,支持超时、环境变量、工作目录等
|
||||
/// 支持超时(timeout_secs)、环境变量(env)、工作目录(working_dir),
|
||||
/// kill_on_drop(true) 保证超时后子进程不残留,shell_type 可选 Cmd/PowerShell/Sh。
|
||||
pub async fn execute(request: ShellRequest) -> anyhow::Result<ShellResult> {
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
|
||||
161
crates/df-execute/tests/shell.rs
Normal file
161
crates/df-execute/tests/shell.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
//! Shell 执行器基础集成测试。
|
||||
//!
|
||||
//! 覆盖 execute() 五类行为,跨平台(Windows=Cmd, 非 Windows=Sh):
|
||||
//! 1. 成功:exit_code=Some(0) + stdout 非空 + duration_ms>0
|
||||
//! 2. 非零退出:exit_code=Some(非0),返回 Ok(退出码在 ShellResult 内,非 Err)
|
||||
//! 3. 超时:timeout_secs=1 + 长睡命令,返回 Err
|
||||
//! 4. env 注入:env HashMap 传 KEY=VAL,命令回显验证
|
||||
//! 5. working_dir:传临时目录,命令回显当前目录验证
|
||||
//!
|
||||
//! 注:execute 逻辑本身未改动,此文件为零行为变更的纯新增测试。
|
||||
|
||||
use df_execute::shell::{execute, ShellRequest, ShellType};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 平台默认 ShellType(对齐 shell.rs:31 Default impl:Windows→Cmd, 非 Windows→Sh)
|
||||
fn default_shell() -> ShellType {
|
||||
if cfg!(windows) {
|
||||
ShellType::Cmd
|
||||
} else {
|
||||
ShellType::Sh
|
||||
}
|
||||
}
|
||||
|
||||
/// 构造最小 ShellRequest,默认无超时/无 env/无 working_dir
|
||||
fn req(command: &str) -> ShellRequest {
|
||||
ShellRequest {
|
||||
command: command.to_string(),
|
||||
working_dir: None,
|
||||
env: HashMap::new(),
|
||||
timeout_secs: None,
|
||||
shell_type: Some(default_shell()),
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 1. 成功:exit_code=0 + stdout 非空 + duration_ms>0 ----------
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_success_echo() {
|
||||
// 平台无关:Cmd/Sh 都认 echo。Cmd 输出无尾换行,Sh 输出带 \n——只断言非空与包含。
|
||||
let result = execute(req("echo hello_df_execute")).await;
|
||||
assert!(result.is_ok(), "execute 应返回 Ok: {:?}", result.err());
|
||||
let res = result.unwrap();
|
||||
assert_eq!(res.exit_code, Some(0), "成功命令 exit_code 应为 0");
|
||||
assert!(
|
||||
res.stdout.trim().contains("hello_df_execute"),
|
||||
"stdout 应包含回显文本,实际: {:?}",
|
||||
res.stdout
|
||||
);
|
||||
assert!(res.duration_ms > 0, "duration_ms 应 > 0,实际: {}", res.duration_ms);
|
||||
}
|
||||
|
||||
// ---------- 2. 非零退出:exit_code=Some(非0),返回 Ok ----------
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_nonzero_exit() {
|
||||
// 平台无关:false 是 POSIX 内建 + Windows 的 cmd 也内置(返回退出码 1)。
|
||||
// 非 Windows 用 sh -c 'false';Windows 用 cmd /C 'cmd /C exit 1' 更稳——
|
||||
// 这里统一用 false,Windows cmd 内置 false.exe 自 Win10 起可用,失败则 exit_code 非 0 即满足断言。
|
||||
let result = execute(req("exit 1")).await;
|
||||
assert!(result.is_ok(), "非零退出应仍返回 Ok(退出码在 ShellResult 内),实际: {:?}", result);
|
||||
let res = result.unwrap();
|
||||
assert_ne!(res.exit_code, Some(0), "exit 1 的 exit_code 应非 0,实际: {:?}", res.exit_code);
|
||||
}
|
||||
|
||||
// ---------- 3. 超时:timeout_secs=1 + 长睡命令,返回 Err ----------
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_timeout_returns_err() {
|
||||
// 平台无关睡眠命令:Cmd 用 timeout(Windows 内置),Sh 用 sleep。各自分支。
|
||||
let sleep_cmd = if cfg!(windows) {
|
||||
// cmd 的 timeout /T 需按键中断,用 ping 假睡更稳(>1s);但 timeout 内置更简洁,
|
||||
// 这里用 ping -n 5 (≈4s) 跨 Win 版本稳。
|
||||
"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(request).await;
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"超时应返回 Err,实际: {:?}",
|
||||
result.as_ref().err()
|
||||
);
|
||||
// 错误信息应含「超时」(对齐 shell.rs:101 文案)
|
||||
let msg = result.unwrap_err().to_string();
|
||||
assert!(
|
||||
msg.contains("超时") || msg.to_lowercase().contains("timeout"),
|
||||
"错误信息应含超时提示,实际: {}",
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- 4. env 注入:env HashMap 传 KEY=VAL,命令回显验证 ----------
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_env_injection() {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("DF_EXECUTE_TEST_KEY".to_string(), "env_value_42".to_string());
|
||||
// 平台无关回显 env 变量:Cmd 用 %VAR%,Sh 用 $VAR
|
||||
let echo_cmd = if cfg!(windows) {
|
||||
"echo %DF_EXECUTE_TEST_KEY%"
|
||||
} else {
|
||||
"echo $DF_EXECUTE_TEST_KEY"
|
||||
};
|
||||
let request = ShellRequest {
|
||||
command: echo_cmd.to_string(),
|
||||
working_dir: None,
|
||||
env,
|
||||
timeout_secs: None,
|
||||
shell_type: Some(default_shell()),
|
||||
};
|
||||
let res = execute(request).await.expect("env 注入命令应返回 Ok");
|
||||
assert_eq!(res.exit_code, Some(0));
|
||||
assert!(
|
||||
res.stdout.trim().contains("env_value_42"),
|
||||
"stdout 应含注入的 env 值,实际: {:?}",
|
||||
res.stdout
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- 5. working_dir:传临时目录,命令回显当前目录验证 ----------
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_working_dir() {
|
||||
// 用 std::env::temp_dir()(标准库,零新增依赖)。生成唯一子目录避免并行测试互相干扰。
|
||||
let unique = format!("df_exec_test_{}", std::process::id());
|
||||
let tmp = std::env::temp_dir().join(&unique);
|
||||
std::fs::create_dir_all(&tmp).expect("创建临时目录失败");
|
||||
// 测试后清理(即便失败也尽量不残留)
|
||||
let tmp_for_cleanup = tmp.clone();
|
||||
|
||||
// 平台无关回显当前目录:Cmd 用 cd(无参数)不靠谱,用 echo %CD%;Sh 用 pwd。
|
||||
let pwd_cmd = if cfg!(windows) { "echo %CD%" } else { "pwd" };
|
||||
let request = ShellRequest {
|
||||
command: pwd_cmd.to_string(),
|
||||
working_dir: Some(tmp.to_string_lossy().to_string()),
|
||||
env: HashMap::new(),
|
||||
timeout_secs: None,
|
||||
shell_type: Some(default_shell()),
|
||||
};
|
||||
let res = execute(request).await.expect("working_dir 命令应返回 Ok");
|
||||
assert_eq!(res.exit_code, Some(0));
|
||||
// 规范化比较(去尾空白/大小写在 Windows 不敏感但路径用户自定义,只断言包含)
|
||||
let stdout_norm = res.stdout.trim().to_lowercase().replace('\\', "/");
|
||||
let tmp_norm = tmp.to_string_lossy().to_lowercase().replace('\\', "/");
|
||||
assert!(
|
||||
stdout_norm.contains(&tmp_norm),
|
||||
"stdout 应含 working_dir 路径,实际: {:?},期望包含: {:?}",
|
||||
res.stdout,
|
||||
tmp.to_string_lossy()
|
||||
);
|
||||
|
||||
// 清理
|
||||
let _ = std::fs::remove_dir_all(&tmp_for_cleanup);
|
||||
}
|
||||
Reference in New Issue
Block a user