use rexpect::session::spawn_command;
use std::path::PathBuf;
use std::process::Command;
const SMOKE_MODEL: &str = "qwen3:0.6b";
const SPAWN_TIMEOUT_MS: u64 = 90_000;
fn ferrum_bin() -> PathBuf {
if let Ok(bin) = std::env::var("CARGO_BIN_EXE_ferrum") {
return PathBuf::from(bin);
}
let current = std::env::current_exe().expect("test exe path");
let dir = current
.parent()
.and_then(|p| p.parent())
.expect("target dir");
let mut bin = dir.join("ferrum");
if cfg!(windows) {
bin.set_extension("exe");
}
assert!(bin.exists(), "ferrum binary not found at {}", bin.display());
bin
}
fn spawn_ferrum_run() -> rexpect::session::PtySession {
let mut cmd = Command::new(ferrum_bin());
cmd.args(["run", SMOKE_MODEL, "--disable-thinking"]);
cmd.env("NO_COLOR", "1");
spawn_command(cmd, Some(SPAWN_TIMEOUT_MS)).expect("spawn ferrum on pty")
}
#[test]
#[ignore = "loads real model — run with `cargo test -- --ignored`"]
fn test_pty_bye_command_exits() {
let mut p = spawn_ferrum_run();
p.exp_string("Ready.").expect("ready banner");
p.send_line("/bye").expect("send /bye");
p.exp_string("Goodbye").expect("goodbye banner");
p.exp_eof().expect("clean EOF after /bye");
}
#[test]
#[ignore = "loads real model"]
fn test_pty_ctrl_d_clean_exit() {
let mut p = spawn_ferrum_run();
p.exp_string("Ready.").expect("ready banner");
p.send_control('d').expect("send Ctrl+D");
p.exp_string("Goodbye").expect("goodbye banner after EOF");
p.exp_eof().expect("clean EOF after Ctrl+D");
}
#[test]
#[ignore = "loads real model"]
fn test_pty_sigint_terminates() {
let mut p = spawn_ferrum_run();
p.exp_string("Ready.").expect("ready banner");
p.send_control('c').expect("send Ctrl+C");
p.exp_eof()
.expect("process should die on SIGINT within timeout");
}