98 lines
3.0 KiB
Rust
98 lines
3.0 KiB
Rust
//! `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<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
|
||
let sink: Box<dyn Fn(ToolUpdate) + Send + Sync> = {
|
||
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());
|
||
}
|