//! `shell` 工具的单元测试(unix 分支;Windows 分支在有 Windows 的 CI 上跑)。 //! Unit tests for the `shell` tool (unix branch; the windows branch runs on //! Windows CI). use focus_core::tool::Tool; use focus_core::tool::ToolUpdate; use focus_json::JsonValue; use focus_tools::ShellTool; use std::fs; use std::path::PathBuf; use std::sync::{Arc, Mutex}; fn temp_dir(tag: &str) -> PathBuf { let d = std::env::temp_dir().join(format!("focus-tools-shell-{}-{}", tag, std::process::id())); let _ = fs::remove_dir_all(&d); fs::create_dir_all(&d).unwrap(); d } fn cmd_args(command: &str) -> JsonValue { let mut args = JsonValue::obj(); args.insert("command", command.into()).ok(); args } #[cfg(unix)] #[test] fn runs_bash_and_captures_stdout() { let dir = temp_dir("stdout"); let tool = ShellTool::new(&dir); let r = tool.execute("id", &cmd_args("echo hello"), None).unwrap(); let text = r.content[0].as_text().unwrap().text.clone(); assert_eq!(text.trim(), "hello"); assert_eq!(r.details.get_num("exitCode"), Some(0.0)); assert_eq!(r.details.get_bool("timedOut"), Some(false)); } #[cfg(unix)] #[test] fn captures_stderr_and_exit_code() { let dir = temp_dir("stderr"); let tool = ShellTool::new(&dir); let r = tool .execute("id", &cmd_args("echo oops >&2; exit 3"), None) .unwrap(); assert_eq!(r.details.get_num("exitCode"), Some(3.0)); let stderr = r.details.get_str("stderr").unwrap_or(""); assert!(stderr.contains("oops"), "got: {}", stderr); } #[cfg(unix)] #[test] fn times_out_and_kills() { let dir = temp_dir("timeout"); let tool = ShellTool::new(&dir); let mut args = cmd_args("sleep 5"); args.insert("timeoutMs", 200u64.into()).ok(); let r = tool.execute("id", &args, None).unwrap(); assert_eq!(r.details.get_bool("timedOut"), Some(true)); let text = r.content[0].as_text().unwrap().text.clone(); assert!(text.contains("timed out"), "got: {}", text); } #[cfg(unix)] #[test] fn streams_updates_line_by_line() { let dir = temp_dir("updates"); let tool = ShellTool::new(&dir); let lines: Arc>> = Arc::new(Mutex::new(Vec::new())); let sink: Box = { let lines = lines.clone(); Box::new(move |u: ToolUpdate| { if let Some(t) = u.content[0].as_text() { lines.lock().unwrap().push(t.text.clone()); } }) }; let r = tool .execute("id", &cmd_args("printf 'a\\nb\\nc\\n'"), Some(&*sink)) .unwrap(); assert_eq!(r.details.get_num("exitCode"), Some(0.0)); let got = lines.lock().unwrap(); assert_eq!( *got, vec!["a".to_string(), "b".to_string(), "c".to_string()] ); } #[cfg(unix)] #[test] fn runs_in_project_root() { let dir = temp_dir("cwd"); let tool = ShellTool::new(&dir); let r = tool.execute("id", &cmd_args("pwd"), None).unwrap(); let text = r.content[0].as_text().unwrap().text.clone(); assert_eq!(text.trim(), dir.display().to_string()); }