use std::process::Command;
use std::time::Duration;
fn get_binary_path() -> String {
if let Ok(path) = std::env::var("SELFWARE_BINARY") {
return path;
}
env!("CARGO_BIN_EXE_selfware").to_string()
}
fn run_selfware_with_timeout(
args: &[&str],
timeout_secs: u64,
) -> std::io::Result<std::process::Output> {
use std::io::{Error, ErrorKind};
use std::process::Stdio;
let binary = get_binary_path();
let mut child = Command::new(&binary)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| {
Error::new(
e.kind(),
format!(
"Failed to spawn {}: {}. Run tests with: cargo test",
binary, e
),
)
})?;
let timeout = Duration::from_secs(timeout_secs);
let start = std::time::Instant::now();
loop {
match child.try_wait()? {
Some(_status) => {
return child.wait_with_output();
}
None => {
if start.elapsed() > timeout {
let _ = child.kill();
return Err(Error::new(
ErrorKind::TimedOut,
format!("Command timed out after {} seconds", timeout_secs),
));
}
std::thread::sleep(Duration::from_millis(100));
}
}
}
}
#[test]
#[ignore = "Backend-dependent test with variable latency; run with --include-ignored"]
#[cfg(feature = "integration")]
fn test_model_tool_calls_are_parsed() {
let output = run_selfware_with_timeout(
&["--yolo", "run", "list files in the current directory"],
180, )
.expect("Failed to run selfware");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stdout.contains("Tool succeeded") || stdout.contains("✓"),
"Should have successful tool calls. stdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
!stderr.contains("no valid tool calls were parsed"),
"Tool calls should be parsed successfully. stderr: {}",
stderr
);
}
#[test]
#[ignore = "Backend-dependent; requires live LLM endpoint. Run with --include-ignored"]
#[cfg(feature = "integration")]
fn test_analyze_tool_calls_work() {
let output = run_selfware_with_timeout(&["--yolo", "analyze", "./Cargo.toml"], 180)
.expect("Failed to run selfware");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stdout.is_empty() || !stderr.is_empty(),
"Analyze should produce some output"
);
assert!(
!stderr
.contains("Content appears to contain tool-related keywords but no valid tool calls"),
"All tool calls should be parsed. stderr: {}",
stderr
);
}
#[test]
#[ignore = "Backend-dependent test with variable latency; run with --include-ignored"]
#[cfg(feature = "integration")]
fn test_model_format_compatibility() {
let output = run_selfware_with_timeout(&["--yolo", "run", "read the file Cargo.toml"], 90)
.expect("Failed to run selfware");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let tool_succeeded = stdout.matches("Tool succeeded").count() + stdout.matches("✓").count();
assert!(
tool_succeeded >= 1,
"Should have at least 1 successful tool call. Got {}. stdout: {}",
tool_succeeded,
stdout
);
let parse_warnings = stderr.matches("no valid tool calls were parsed").count()
+ stderr.matches("tool-related keywords but no valid").count();
assert!(
parse_warnings == 0,
"Should have 0 parse warnings, got {}. stderr: {}",
parse_warnings,
stderr
);
}
#[test]
#[ignore = "Backend-dependent; slow/flaky with local models. Run with --include-ignored"]
#[cfg(feature = "integration")]
fn test_interactive_analyze_parses_tools() {
use std::io::Write;
use std::process::Stdio;
let binary = get_binary_path();
let mut child = Command::new(&binary)
.args(["--yolo", "chat"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to spawn selfware");
if let Some(mut stdin) = child.stdin.take() {
stdin
.write_all(b"list files in current directory\nexit\n")
.ok();
}
let timeout = Duration::from_secs(180);
let start = std::time::Instant::now();
let output = loop {
match child.try_wait() {
Ok(Some(_status)) => {
break child.wait_with_output().expect("Failed to get output");
}
Ok(None) => {
if start.elapsed() > timeout {
let _ = child.kill();
panic!(
"Interactive test timed out after {} seconds",
timeout.as_secs()
);
}
std::thread::sleep(Duration::from_millis(100));
}
Err(e) => {
panic!("Error waiting for child: {}", e);
}
}
};
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stdout.contains("Tool succeeded") || stdout.contains("✓") || !stdout.is_empty(),
"Interactive mode should produce output. stdout: {}",
stdout
);
assert!(
!stderr.contains("no valid tool calls were parsed"),
"Interactive tool calls should parse. stderr: {}",
stderr
);
}