extern crate hrdr_test_support;
mod common;
use std::io::Write;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use common::{Chat, MockServer, drain_pty, pty_text, text_chunk};
use portable_pty::{CommandBuilder, PtySize, native_pty_system};
const DONE: Duration = Duration::from_secs(60);
fn set_a_colour(seen: &str) -> bool {
seen.contains("\x1b[38;5;")
}
fn pty_available() -> bool {
native_pty_system()
.openpty(PtySize {
rows: 24,
cols: 90,
pixel_width: 0,
pixel_height: 0,
})
.is_ok()
}
fn skip_for_want_of_a_pty() -> bool {
if pty_available() || std::env::var_os("CI").is_some() {
return false;
}
eprintln!("skipping: no pty available (a Landlock sandbox blocks /dev/ptmx)");
true
}
fn run_on_a_pty(env: &[(&str, &str)]) -> String {
let server = MockServer::start(vec![Chat::Sse(vec![
text_chunk("c1", "Hello."),
common::stop_chunk("c1"),
"[DONE]".to_string(),
])]);
let home = tempfile::tempdir().expect("temp home");
let project = tempfile::tempdir().expect("temp project");
let config_dir = home.path().join("hrdr");
std::fs::create_dir_all(&config_dir).expect("config dir");
std::fs::write(
config_dir.join("config.toml"),
format!(
"model = \"mock://m\"\n\n[providers.mock]\nbase_url = \"{}\"\napi_key = \"k\"\n",
server.base_url()
),
)
.expect("write config.toml");
let pty = native_pty_system()
.openpty(PtySize {
rows: 40,
cols: 100,
pixel_width: 0,
pixel_height: 0,
})
.expect("open pty");
let mut cmd = CommandBuilder::new(env!("CARGO_BIN_EXE_hrdr"));
cmd.args(["run", "say hello"]);
cmd.cwd(project.path());
for (key, value) in [
("HOME", home.path()),
("USERPROFILE", home.path()),
("APPDATA", home.path()),
("LOCALAPPDATA", home.path()),
("XDG_CONFIG_HOME", home.path()),
("XDG_DATA_HOME", home.path()),
("XDG_STATE_HOME", home.path()),
("XDG_CACHE_HOME", home.path()),
] {
cmd.env(key, value);
}
cmd.env("TERM", "xterm-256color");
for key in [
"HRDR_MODEL",
"HRDR_API_KEY",
"RUST_LOG",
"NO_COLOR",
"CLICOLOR",
] {
cmd.env_remove(key);
}
for (key, value) in env {
cmd.env(key, value);
}
let mut child = pty.slave.spawn_command(cmd).expect("spawn hrdr");
drop(pty.slave);
let reader = pty.master.try_clone_reader().expect("pty reader");
let writer: Arc<Mutex<Box<dyn Write + Send>>> =
Arc::new(Mutex::new(pty.master.take_writer().expect("pty writer")));
let seen = drain_pty(reader, writer);
let deadline = Instant::now() + DONE;
while Instant::now() < deadline {
if matches!(child.try_wait(), Ok(Some(_))) {
break;
}
std::thread::sleep(Duration::from_millis(50));
}
let _ = child.kill();
let _ = child.wait();
std::thread::sleep(Duration::from_millis(200));
pty_text(&seen)
}
#[test]
fn chrome_on_a_terminal_is_coloured() {
if skip_for_want_of_a_pty() {
return;
}
let seen = run_on_a_pty(&[]);
assert!(
seen.contains("[usage]"),
"the chrome this test is about must have run: {seen:?}"
);
assert!(set_a_colour(&seen), "a terminal gets colour: {seen:?}");
}
#[test]
fn no_color_turns_it_off_on_a_terminal() {
if skip_for_want_of_a_pty() {
return;
}
let seen = run_on_a_pty(&[("NO_COLOR", "1")]);
assert!(
seen.contains("[usage]"),
"the chrome this test is about must have run: {seen:?}"
);
assert!(
!set_a_colour(&seen),
"NO_COLOR is honoured on a terminal: {seen:?}"
);
}
#[test]
fn term_dumb_turns_it_off_on_a_terminal() {
if skip_for_want_of_a_pty() {
return;
}
let seen = run_on_a_pty(&[("TERM", "dumb")]);
assert!(
seen.contains("[usage]"),
"the chrome this test is about must have run: {seen:?}"
);
assert!(!set_a_colour(&seen), "TERM=dumb is honoured: {seen:?}");
}