use std::sync::mpsc;
use std::time::{Duration, Instant};
use espelho::VtQuery;
use tear_types::MultiplexerControl;
use crate::{InProcess, PaneGrid};
const CATALOG: [VtQuery; 6] = [
VtQuery::CursorPosition,
VtQuery::DeviceStatus,
VtQuery::PrimaryDeviceAttributes,
VtQuery::TerminalVersion,
VtQuery::OscForeground,
VtQuery::OscBackground,
];
const fn catalog_index(q: VtQuery) -> usize {
match q {
VtQuery::CursorPosition => 0,
VtQuery::DeviceStatus => 1,
VtQuery::PrimaryDeviceAttributes => 2,
VtQuery::TerminalVersion => 3,
VtQuery::OscForeground => 4,
VtQuery::OscBackground => 5,
}
}
fn realistic_streams(q: VtQuery) -> Vec<Vec<u8>> {
let w = q.wire();
vec![
w.to_vec(),
[b"\x1b[1;32muser@host\x1b[0m $ ".as_slice(), w, b" ls\r\n"].concat(),
[b"\x1b[?1049h\x1b[2J\x1b[H".as_slice(), w, b"\x1b[?1049l"].concat(),
CATALOG.iter().flat_map(|o| o.wire().to_vec()).collect(),
[b"\x1b".as_slice(), w].concat(),
]
}
#[test]
fn catalog_is_exhaustive() {
let all: Vec<u8> = CATALOG.iter().flat_map(|q| q.wire().to_vec()).collect();
let mut cursor = 0;
let mut found = Vec::new();
while let Some((q, end)) = VtQuery::scan(&all, cursor) {
found.push(q);
cursor = end;
}
assert_eq!(found.as_slice(), CATALOG.as_slice());
}
#[test]
fn catalog_index_matches_catalog_order() {
for (i, q) in CATALOG.iter().enumerate() {
assert_eq!(
catalog_index(*q),
i,
"{q:?} is at CATALOG[{i}] but catalog_index says {}",
catalog_index(*q)
);
}
assert_eq!(
CATALOG.len(),
6,
"CATALOG changed size — update catalog_index's match and this count \
together, or the two halves of the guard drift apart"
);
}
#[test]
fn pane_grid_never_panics_on_any_prefix_of_query_streams() {
for q in CATALOG {
for stream in realistic_streams(q) {
for end in 0..=stream.len() {
let mut grid = PaneGrid::new(80, 24);
grid.feed(&stream[..end]);
let _ = grid.snapshot();
}
let mut grid = PaneGrid::new(80, 24);
for b in &stream {
grid.feed(std::slice::from_ref(b));
}
let _ = grid.snapshot();
}
}
}
#[test]
fn query_wires_leave_no_residue_in_rendered_text() {
for q in CATALOG {
let mut grid = PaneGrid::new(80, 24);
grid.feed(b"before|");
grid.feed(q.wire());
grid.feed(b"|after");
let snap = grid.snapshot();
let row0 = snap
.to_text_rows()
.into_iter()
.next()
.unwrap_or_default();
assert!(
row0.trim_end() == "before||after",
"query {q:?} left residue in the grid: {row0:?}"
);
}
}
#[test]
fn query_wires_pass_through_verbatim_to_subscribers() {
let inproc = InProcess::new();
let sid = inproc
.new_session("espelho-relay", "/bin/cat")
.expect("new_session(/bin/cat)");
let pane = *inproc.get_session(sid).unwrap().panes.keys().next().unwrap();
let rx = inproc.subscribe_pane_bytes(pane).expect("subscribe");
for q in CATALOG {
let mut framed = b"marker-".to_vec();
framed.extend_from_slice(q.wire());
framed.extend_from_slice(b"-end\n");
inproc.send_keys(pane, &framed).expect("send_keys");
let deadline = Instant::now() + Duration::from_secs(5);
let mut buf = Vec::<u8>::new();
let mut relayed = false;
while Instant::now() < deadline {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(chunk) => {
buf.extend_from_slice(&chunk);
let mut cursor = 0;
while let Some((found, end)) = VtQuery::scan(&buf, cursor) {
if found == q {
relayed = true;
break;
}
cursor = end;
}
if relayed {
break;
}
}
Err(mpsc::RecvTimeoutError::Timeout) => {}
Err(mpsc::RecvTimeoutError::Disconnected) => break,
}
}
assert!(
relayed,
"query {q:?} did not survive verbatim through the relay — \
downstream terminal could never answer it; transcript: {:?}",
String::from_utf8_lossy(&buf)
);
}
}