#![cfg(unix)]
use std::fs;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
fn launcher_exits_cleanly_in_a_pseudo_terminal() {
if Command::new("script").arg("--version").output().is_err() {
return;
}
let binary = env!("CARGO_BIN_EXE_bot-forge");
let command = if cfg!(target_os = "macos") {
format!("printf 0 | script -q /dev/null {binary}")
} else {
format!("printf 0 | script -q -c {binary} /dev/null")
};
let output = Command::new("sh").args(["-c", &command]).output().unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
let combined = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(combined.contains("bot-forge"));
}
#[test]
fn launcher_restores_terminal_after_resize_and_ctrl_c() {
if Command::new("expect").arg("-v").output().is_err() {
return;
}
let binary = env!("CARGO_BIN_EXE_bot-forge");
let script = format!(
r#"
set timeout 5
spawn {binary}
expect "↑↓ navigate"
stty rows 12 columns 40
set child [exp_pid]
exec kill -INT $child
expect eof
catch wait result
set code [lindex $result 3]
if {{$code != 130 && $code != 1}} {{ exit 1 }}
"#
);
let path = temp_file("resize-ctrl-c.exp");
fs::write(&path, script).unwrap();
let output = Command::new("expect").arg(&path).output().unwrap();
fs::remove_file(path).unwrap();
assert!(
output.status.success(),
"stdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let combined = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(combined.contains("bot-forge"));
}
#[test]
fn component_selection_restores_the_main_screen_before_confirmation() {
if Command::new("expect").arg("-v").output().is_err() {
return;
}
let binary = env!("CARGO_BIN_EXE_bot-forge");
let config = temp_file("selection.toml");
let transcript = temp_file("selection.log");
fs::write(
&config,
r#"catalog = "rust-dev"
[policy]
allow_shell = true
[profiles.smoke]
components = ["terminal-test"]
[profiles.standard]
inherits = ["smoke"]
[[components]]
id = "terminal-test"
platforms = ["*"]
[components.detect]
kind = "command"
program = "false"
[components.install]
backend = "shell"
command = "true"
resources = ["terminal-test"]
"#,
)
.unwrap();
let script = format!(
r#"
set timeout 10
log_user 1
log_file -noappend {{{}}}
spawn sh -c "stty rows 24 columns 80; exec {binary} install smoke --config {}"
expect "Enter install"
send "\r"
expect "Selected components: 1"
expect "Y confirm"
send "n"
expect eof
catch wait result
set code [lindex $result 3]
if {{$code != 3}} {{ exit 1 }}
"#,
transcript.display(),
config.display(),
);
let path = temp_file("selection.exp");
fs::write(&path, script).unwrap();
let output = Command::new("expect").arg(&path).output().unwrap();
let captured = fs::read(&transcript).unwrap_or_default();
fs::remove_file(path).unwrap();
fs::remove_file(config).unwrap();
fs::remove_file(transcript).unwrap();
assert!(
output.status.success(),
"stdout:\n{}\nstderr:\n{}\ntranscript:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
String::from_utf8_lossy(&captured)
);
let leave_screen = captured
.windows(b"\x1b[?1049l".len())
.position(|window| window == b"\x1b[?1049l")
.expect("selection did not leave the alternate screen");
let selected = captured
.windows(b"Selected components: 1".len())
.position(|window| window == b"Selected components: 1")
.expect("selection summary was not printed");
assert!(leave_screen < selected);
}
#[test]
fn install_progress_stays_on_the_current_line_with_theme_color() {
if Command::new("expect").arg("-v").output().is_err() {
return;
}
let binary = env!("CARGO_BIN_EXE_bot-forge");
let config = temp_file("progress.toml");
let transcript = temp_file("progress.log");
let state_home = temp_file("progress-home");
fs::create_dir_all(&state_home).unwrap();
fs::write(
&config,
r#"catalog = "rust-dev"
[policy]
allow_shell = true
[profiles.smoke]
components = ["terminal-progress"]
[profiles.standard]
inherits = ["smoke"]
[[components]]
id = "terminal-progress"
platforms = ["*"]
[components.detect]
kind = "shell"
command = 'test -f "$BOT_FORGE_HOME/terminal-progress"'
[components.install]
backend = "shell"
command = 'printf "Compiling terminal-progress\\n"; sleep 1; touch "$BOT_FORGE_HOME/terminal-progress"'
resources = ["terminal-progress"]
"#,
)
.unwrap();
let script = format!(
r#"
set timeout 10
log_user 1
log_file -noappend {{{}}}
spawn sh -c "stty rows 24 columns 80; exec env -u NO_COLOR TERM=xterm-256color BOT_FORGE_HOME={} HOME={} {binary} install smoke --config {}"
expect "Enter install"
send "\r"
expect "Y confirm"
send "y"
expect "Compiling terminal-progress"
expect eof
"#,
transcript.display(),
state_home.display(),
state_home.display(),
config.display(),
);
let path = temp_file("progress.exp");
fs::write(&path, script).unwrap();
let output = Command::new("expect").arg(&path).output().unwrap();
let captured = fs::read(&transcript).unwrap_or_default();
fs::remove_file(path).unwrap();
fs::remove_file(config).unwrap();
fs::remove_file(transcript).unwrap();
fs::remove_dir_all(state_home).unwrap();
assert!(
output.status.success(),
"stdout:\n{}\nstderr:\n{}\ntranscript:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
String::from_utf8_lossy(&captured)
);
assert!(
captured
.windows(b"\r\x1b[2K".len())
.any(|window| window == b"\r\x1b[2K")
);
assert!(
captured
.windows(b"\x1b[1;38;2;255;165;0m".len())
.any(|window| window == b"\x1b[1;38;2;255;165;0m")
);
assert!(
!captured
.windows(b"\r\n\r\n\r\n\r\n".len())
.any(|window| window == b"\r\n\r\n\r\n\r\n")
);
assert!(
!captured
.windows(b"0/0".len())
.any(|window| window == b"0/0")
);
}
fn temp_file(suffix: &str) -> std::path::PathBuf {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
std::env::temp_dir().join(format!("bot-forge-{}-{nonce}-{suffix}", std::process::id()))
}