#[cfg(test)]
#[cfg(all(unix, feature = "sandbox-microvm"))]
mod tests {
use super::super::common::*;
use std::io::Write;
use std::os::unix::io::AsRawFd;
use std::time::Duration;
#[test]
fn relay_slow_tty_consumer() {
const BURST_SIZE: usize = 32768;
const FOLLOWUP_SIZE: usize = 256;
const TOTAL: usize = BURST_SIZE + FOLLOWUP_SIZE;
let mut guest_cmd = std::process::Command::new("cat");
guest_cmd.arg("-u");
let relay =
crate::ui::pty_relay::PtyRelay::spawn(&mut guest_cmd).expect("spawn cat on PTY");
relay.disable_guest_echo();
let (tty_primary, mut tty_secondary) = open_pty_pair().expect("open fake tty PTY");
make_raw_fd(tty_secondary.as_raw_fd()).expect("raw mode on tty secondary");
let relay_handle = std::thread::spawn(move || relay.relay_to_fd(tty_primary));
let mut burst: Vec<u8> = Vec::with_capacity(BURST_SIZE);
for i in 0..BURST_SIZE {
let b = b'a' + (i % 26) as u8;
burst.push(b);
loop {
match tty_secondary.write(&[b]) {
Ok(0) => panic!("write returned 0"),
Ok(_) => break,
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
std::thread::sleep(Duration::from_millis(1));
continue;
}
Err(e) => panic!("write error: {e}"),
}
}
}
tty_secondary.flush().ok();
let mut followup: Vec<u8> = Vec::with_capacity(FOLLOWUP_SIZE);
for i in 0..FOLLOWUP_SIZE {
let b = b'Z' - (i % 26) as u8;
followup.push(b);
loop {
match tty_secondary.write(&[b]) {
Ok(0) => panic!("write returned 0"),
Ok(_) => break,
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
std::thread::sleep(Duration::from_millis(1));
continue;
}
Err(e) => panic!("write error: {e}"),
}
}
}
tty_secondary.flush().ok();
set_nonblocking(&tty_secondary).expect("set nonblocking for drain");
let mut echoed: Vec<u8> = Vec::with_capacity(TOTAL);
let deadline = std::time::Instant::now() + Duration::from_secs(10);
while echoed.len() < TOTAL && std::time::Instant::now() < deadline {
echoed.extend(drain_fd_nonblock(&mut tty_secondary));
if echoed.len() < TOTAL {
std::thread::sleep(Duration::from_millis(5));
}
}
drop(tty_secondary);
match relay_handle.join() {
Ok(Ok(status)) => {
eprintln!(
"relay_slow_tty_consumer: relay exited {status:?}, \
burst={BURST_SIZE} followup={FOLLOWUP_SIZE} echoed={}",
echoed.len()
);
}
Ok(Err(e)) => panic!("relay_slow_tty_consumer: relay error: {e}"),
Err(_) => panic!("relay_slow_tty_consumer: relay thread panicked"),
}
let mut echoed_sorted = echoed.clone();
echoed_sorted.sort();
let mut injected: Vec<u8> = Vec::with_capacity(TOTAL);
injected.extend_from_slice(&burst);
injected.extend_from_slice(&followup);
injected.sort();
assert_eq!(
echoed_sorted,
injected,
"slow tty consumer: echo mismatch: injected {} bytes, got {} back",
injected.len(),
echoed.len()
);
}
#[test]
fn relay_drain_inject_boundary() {
const PRE_INJECT: usize = 500;
const LIVE_INJECT: usize = 500;
const TOTAL: usize = PRE_INJECT + LIVE_INJECT;
let mut guest_cmd = std::process::Command::new("cat");
guest_cmd.arg("-u");
let mut relay =
crate::ui::pty_relay::PtyRelay::spawn(&mut guest_cmd).expect("spawn cat on PTY");
relay.disable_guest_echo();
let (tty_primary, mut tty_secondary) = open_pty_pair().expect("open fake tty PTY");
make_raw_fd(tty_secondary.as_raw_fd()).expect("raw mode on tty secondary");
let pre_bytes: Vec<u8> = (0..PRE_INJECT).map(|i| b'a' + (i % 26) as u8).collect();
relay
.write_to_primary(&pre_bytes)
.expect("write_to_primary");
let relay_handle = std::thread::spawn(move || relay.relay_to_fd(tty_primary));
std::thread::sleep(Duration::from_millis(50));
let live_bytes: Vec<u8> = (0..LIVE_INJECT).map(|i| b'A' + (i % 26) as u8).collect();
for b in &live_bytes {
tty_secondary.write_all(&[*b]).expect("write live");
tty_secondary.flush().ok();
std::thread::sleep(Duration::from_millis(1));
}
set_nonblocking(&tty_secondary).expect("set nonblocking");
std::thread::sleep(Duration::from_millis(500));
let echoed = drain_fd_nonblock(&mut tty_secondary);
drop(tty_secondary);
match relay_handle.join() {
Ok(Ok(status)) => {
eprintln!(
"relay_drain_inject_boundary: relay exited {status:?}, \
pre_inject={PRE_INJECT} live_inject={LIVE_INJECT} echoed={}",
echoed.len()
);
}
Ok(Err(e)) => panic!("relay_drain_inject_boundary: relay error: {e}"),
Err(_) => panic!("relay_drain_inject_boundary: relay thread panicked"),
}
assert_eq!(
echoed.len(),
TOTAL,
"drain-inject boundary: expected {TOTAL} echoed bytes, got {}",
echoed.len()
);
let mut echoed_sorted = echoed.clone();
echoed_sorted.sort();
let mut expected: Vec<u8> = Vec::with_capacity(TOTAL);
expected.extend_from_slice(&pre_bytes);
expected.extend_from_slice(&live_bytes);
expected.sort();
assert_eq!(
echoed_sorted, expected,
"drain-inject boundary: echo mismatch"
);
}
}