use super::set_titles_support::{
delivered_titles, new_detached_session, remembered_title, set_global, title_capable_context,
};
use super::*;
const GEOMETRY_TITLE_FORMAT: &str = "GEN=#{client_width}x#{client_height}";
const FIRST_SIZE: TerminalSize = TerminalSize { cols: 80, rows: 24 };
const REPLACEMENT_SIZE: TerminalSize = TerminalSize {
cols: 132,
rows: 43,
};
fn title_for(size: TerminalSize) -> String {
format!("GEN={}x{}", size.cols, size.rows)
}
async fn attach_sized_client(
handler: &RequestHandler,
session: &rmux_proto::SessionName,
attach_pid: u32,
client_size: TerminalSize,
) -> mpsc::UnboundedReceiver<AttachControl> {
let (control_tx, control_rx) = mpsc::unbounded_channel();
let uid = current_owner_uid();
handler
.register_attach_with_access(
attach_pid,
session.clone(),
None,
AttachRegistration {
control_tx,
control_backlog: Arc::new(AtomicUsize::new(0)),
closing: Arc::new(AtomicBool::new(false)),
persistent_overlay_epoch: Arc::new(AtomicU64::new(0)),
terminal_context: title_capable_context(),
client_title: None,
flags: crate::handler::attach_support::ClientFlags::default(),
render_stream: false,
uid,
user: rmux_os::identity::UserIdentity::Uid(uid),
can_write: true,
client_size: Some(client_size),
},
)
.await
.expect("attach registration succeeds");
control_rx
}
async fn armed_session(
label: &str,
attach_pid: u32,
) -> (
RequestHandler,
rmux_proto::SessionName,
mpsc::UnboundedReceiver<AttachControl>,
) {
let handler = RequestHandler::new();
let session = session_name(label);
new_detached_session(&handler, &session).await;
set_global(&handler, OptionName::SetTitlesString, GEOMETRY_TITLE_FORMAT).await;
set_global(&handler, OptionName::SetTitles, "on").await;
let control_rx = attach_sized_client(&handler, &session, attach_pid, FIRST_SIZE).await;
(handler, session, control_rx)
}
async fn replace_with_second_generation(
handler: &RequestHandler,
session: &rmux_proto::SessionName,
attach_pid: u32,
) -> mpsc::UnboundedReceiver<AttachControl> {
let replacement_rx = attach_sized_client(handler, session, attach_pid, REPLACEMENT_SIZE).await;
assert_eq!(
remembered_title(handler, attach_pid).await,
None,
"a fresh registration starts with no remembered title of its own"
);
replacement_rx
}
async fn assert_replacement_untouched_by(
handler: &RequestHandler,
attach_pid: u32,
replacement_rx: &mut mpsc::UnboundedReceiver<AttachControl>,
context: &str,
) {
let stale_title = title_for(FIRST_SIZE);
let delivered = delivered_titles(replacement_rx);
assert!(
!delivered.contains(&stale_title),
"{context}: the replacement must not be shown the replaced generation's \
title, got {delivered:?}"
);
assert_ne!(
remembered_title(handler, attach_pid).await,
Some(stale_title),
"{context}: the replacement must not remember a title it was never sent"
);
}
#[tokio::test]
async fn a_session_redraw_never_delivers_a_replaced_generation_s_frame() {
let attach_pid = 60_182;
let (handler, session, _first_rx) =
armed_session("redraw-generation-session", attach_pid).await;
let pause = crate::handler::attach_support::install_generic_render_delivery_pause(attach_pid);
let refresh_handler = handler.clone();
let refresh_session = session.clone();
let refresh = tokio::spawn(async move {
refresh_handler
.refresh_attached_session(&refresh_session)
.await;
});
tokio::time::timeout(ATTACH_LIFECYCLE_TIMEOUT, pause.reached.notified())
.await
.expect("the session redraw reaches its delivery boundary");
let mut replacement_rx = replace_with_second_generation(&handler, &session, attach_pid).await;
pause.release.notify_one();
refresh.await.expect("session refresh task");
assert_replacement_untouched_by(
&handler,
attach_pid,
&mut replacement_rx,
"refresh_attached_session",
)
.await;
}
#[tokio::test]
async fn a_client_redraw_with_no_expected_identity_still_binds_its_generation() {
let attach_pid = 60_183;
let (handler, session, _first_rx) = armed_session("redraw-generation-client", attach_pid).await;
let pause = crate::handler::attach_support::install_generic_render_delivery_pause(attach_pid);
let refresh_handler = handler.clone();
let refresh_session = session.clone();
let refresh = tokio::spawn(async move {
refresh_handler
.refresh_attached_client(attach_pid, &refresh_session)
.await;
});
tokio::time::timeout(ATTACH_LIFECYCLE_TIMEOUT, pause.reached.notified())
.await
.expect("the client redraw reaches its delivery boundary");
let mut replacement_rx = replace_with_second_generation(&handler, &session, attach_pid).await;
pause.release.notify_one();
refresh.await.expect("client refresh task");
assert_replacement_untouched_by(
&handler,
attach_pid,
&mut replacement_rx,
"refresh_attached_client",
)
.await;
}
#[tokio::test]
async fn an_undisturbed_generic_redraw_still_reaches_its_own_client() {
let attach_pid = 60_184;
let (handler, session, mut control_rx) =
armed_session("redraw-generation-undisturbed", attach_pid).await;
handler.refresh_attached_client(attach_pid, &session).await;
assert_eq!(
delivered_titles(&mut control_rx),
vec![title_for(FIRST_SIZE)],
"the client still receives its own geometry"
);
assert_eq!(
remembered_title(&handler, attach_pid).await,
Some(title_for(FIRST_SIZE)),
"and the server remembers what it just showed that client"
);
handler.refresh_attached_client(attach_pid, &session).await;
let delivered = delivered_titles(&mut control_rx);
assert!(
delivered.is_empty(),
"an unchanged title is still deduplicated, got {delivered:?}"
);
set_global(
&handler,
OptionName::SetTitlesString,
"CHANGED-#{client_width}",
)
.await;
let delivered = delivered_titles(&mut control_rx);
assert_eq!(
delivered.last().map(String::as_str),
Some("CHANGED-80"),
"the live client still receives its own changed title, got {delivered:?}"
);
}