use std::time::{Duration, Instant};
use rmux_core::input::mode;
use rmux_proto::{PaneTarget, SessionName};
use tokio::time::sleep;
use crate::test_shell::final_sink::{
create_final_sink_session, describe_missing_bracketed_mode, observe_pane_output, FinalSinkSlot,
};
use super::super::RequestHandler;
use super::deferred_initial_gate::DeferredInitialGate;
const OPEN: &[u8] = b"\x1b[200~";
const CLOSE: &[u8] = b"\x1b[201~";
const OBSERVATION_TIMEOUT: Duration = Duration::from_secs(30);
fn wrapped(body: &[u8]) -> Vec<u8> {
let mut wrapped = Vec::with_capacity(OPEN.len() + body.len() + CLOSE.len());
wrapped.extend_from_slice(OPEN);
wrapped.extend_from_slice(body);
wrapped.extend_from_slice(CLOSE);
wrapped
}
fn deferred_runtime() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.expect("build isolated deferred-pane runtime")
}
async fn bracketed_mode_while_starting(handler: &RequestHandler, session: &SessionName) -> bool {
let state = handler.state.lock().await;
assert!(
state.pane_is_starting_in_window(session, 0, 0),
"the pane left `Starting` before the proof could observe it"
);
state
.sessions
.session(session)
.and_then(|session| session.window_at(0))
.and_then(|window| window.pane(0))
.map(rmux_core::Pane::id)
.and_then(|pane_id| state.pane_screen_state(session, pane_id))
.is_some_and(|screen| screen.mode & mode::MODE_BRACKETPASTE != 0)
}
async fn await_child_bracketed_announcement(handler: &RequestHandler, session: &SessionName) {
let deadline = Instant::now() + OBSERVATION_TIMEOUT;
loop {
if bracketed_mode_while_starting(handler, session).await {
return;
}
if Instant::now() >= deadline {
let target = PaneTarget::with_window(session.clone(), 0, 0);
let observed = {
let state = handler.state.lock().await;
observe_pane_output(&state, &target)
};
panic!(
"the real child never announced ESC[?2004h through pane output \
while its pane was starting\n{}",
describe_missing_bracketed_mode(
&target.to_string(),
true,
OBSERVATION_TIMEOUT,
&observed,
)
);
}
sleep(Duration::from_millis(25)).await;
}
}
async fn attach(handler: &RequestHandler, session: &SessionName) -> u32 {
let requester_pid = std::process::id();
let (control_tx, _control_rx) = tokio::sync::mpsc::unbounded_channel();
let _attach_id = handler
.register_attach(requester_pid, session.clone(), control_tx)
.await;
requester_pid
}
async fn assert_runtime_session_is_the_session(handler: &RequestHandler, session: &SessionName) {
let state = handler.state.lock().await;
assert_eq!(
&state.runtime_session_name_for_window(session, 0),
session,
"the deferred gate is keyed by runtime session name"
);
}
#[test]
fn deferred_bracketed_paste_reaches_a_child_that_announced_the_capability_itself() {
deferred_runtime().block_on(async {
let handler = RequestHandler::new();
let session = SessionName::new("final-sink-deferred-aware").expect("valid session name");
let gate = DeferredInitialGate::install(&session);
let expected = wrapped("deferred\r\nβ😀 body".as_bytes());
let slot = FinalSinkSlot::new("deferred-aware", &expected, true);
create_final_sink_session(&handler, &session, &slot).await;
assert_runtime_session_is_the_session(&handler, &session).await;
await_child_bracketed_announcement(&handler, &session).await;
let requester_pid = attach(&handler, &session).await;
handler
.handle_attached_live_input_for_test(requester_pid, &expected)
.await
.expect("bracketed paste is accepted while the pane starts");
assert!(
handler
.state
.lock()
.await
.pane_is_starting_in_window(&session, 0, 0),
"the paste must have been queued, not written to a live terminal"
);
drop(gate);
let target = PaneTarget::with_window(session.clone(), 0, 0);
handler
.wait_for_pane_startup_to_finish_for_test(&target)
.await;
slot.assert_application_bytes(
"a paste queued before startup finished must reach the child with the \
delimiters its own announcement selected",
);
});
}
#[test]
fn deferred_bracketed_paste_to_a_child_that_never_announced_arrives_unwrapped() {
deferred_runtime().block_on(async {
let handler = RequestHandler::new();
let session = SessionName::new("final-sink-deferred-unaware").expect("valid session name");
let gate = DeferredInitialGate::install(&session);
let body = "deferred-unaware\r\nβ😀".as_bytes();
let slot = FinalSinkSlot::new("deferred-unaware", body, false);
create_final_sink_session(&handler, &session, &slot).await;
assert_runtime_session_is_the_session(&handler, &session).await;
slot.wait_until_ready();
assert!(
!bracketed_mode_while_starting(&handler, &session).await,
"a child that never announced ESC[?2004h must not be treated as aware"
);
let requester_pid = attach(&handler, &session).await;
handler
.handle_attached_live_input_for_test(requester_pid, &wrapped(body))
.await
.expect("bracketed paste is accepted while the pane starts");
assert!(
handler
.state
.lock()
.await
.pane_is_starting_in_window(&session, 0, 0),
"the paste must have been queued, not written to a live terminal"
);
drop(gate);
let target = PaneTarget::with_window(session.clone(), 0, 0);
handler
.wait_for_pane_startup_to_finish_for_test(&target)
.await;
slot.assert_application_bytes(
"a queued paste must reach an unannounced child with no delimiters",
);
});
}