use std::time::{Duration, Instant};
use termlens::{Error, Terminal};
const FLOOD: &str = r"stty -icanon -echo; i=0; while [ $i -lt 1000 ]; do printf '\033[6n'; i=$((i+1)); done; printf 'DONE\n'; sleep 3";
#[test]
fn a_child_that_never_reads_its_replies_cannot_wedge_the_drain() {
let start = Instant::now();
let mut t = Terminal::builder()
.size(80, 24)
.env_clear()
.timeout(Duration::from_secs(10))
.args(["-c", FLOOD])
.spawn("/bin/sh")
.expect("spawn");
t.wait_until(|s| s.contains("DONE"))
.expect("the drain kept running, so DONE arrived");
drop(t);
assert!(
start.elapsed() < Duration::from_secs(10),
"teardown took {:?} — the drain or the reap is blocking",
start.elapsed()
);
}
#[test]
fn undelivered_replies_are_named_where_the_kernel_makes_them_visible() {
let mut t = Terminal::builder()
.size(80, 24)
.env_clear()
.timeout(Duration::from_secs(10))
.args(["-c", FLOOD])
.spawn("/bin/sh")
.expect("spawn");
t.wait_until(|s| s.contains("DONE"))
.expect("the drain kept running");
let err = t
.wait_until_for(|s| s.contains("never-appears"), Duration::from_millis(200))
.expect_err("the predicate can never hold");
let message = err.to_string();
assert!(matches!(err, Error::Timeout { .. }), "got: {err}");
assert!(
err.screen().is_some_and(|s| s.contains("DONE")),
"{message}"
);
#[cfg(target_os = "macos")]
assert!(
message.contains("not reading its input"),
"the backlog should be diagnosed: {message}"
);
}
#[test]
fn replies_still_reach_an_application_that_reads_them() -> termlens::Result<()> {
let mut t = Terminal::builder()
.timeout(Duration::from_secs(10))
.args([
"-c",
concat!(
r"stty -icanon -echo; printf 'abc\033[6n'; ",
r#"reply=$(head -c 6 | tr '\033' 'E'); "#,
r#"printf '\nunblocked:%s' "$reply"; read guard"#
),
])
.spawn("/bin/sh")?;
t.wait_until(|s| s.contains("unblocked:E[1;4R"))?;
t.send(termlens::Key::Enter)?;
assert!(t.wait_exit()?.success());
Ok(())
}