use super::*;
const SIZE: TerminalSize = TerminalSize { cols: 80, rows: 24 };
const PINNED_SECOND: i64 = 1_785_500_000;
async fn create_detached_session(handler: &RequestHandler, name: &str) {
let created = handler
.handle(Request::NewSession(NewSessionRequest {
session_name: session_name(name),
detached: true,
size: Some(SIZE),
environment: None,
}))
.await;
assert!(matches!(created, Response::NewSession(_)), "{created:?}");
}
async fn same_second_handler(creation_order: &[&str], use_order: &[&str]) -> RequestHandler {
let handler = RequestHandler::new();
for name in creation_order {
create_detached_session(&handler, name).await;
}
let mut state = handler.state.lock().await;
for name in use_order {
state
.sessions
.session_mut(&session_name(name))
.expect("used session exists")
.touch_attached();
}
for name in creation_order {
state
.sessions
.session_mut(&session_name(name))
.expect("created session exists")
.pin_public_times_for_tests(PINNED_SECOND);
}
assert!(
state
.sessions
.iter()
.all(|(_, session)| session.created_at() == PINNED_SECOND
&& session.activity_at() == PINNED_SECOND),
"the fixture must leave no public second able to order the sessions"
);
drop(state);
handler
}
async fn session_id(handler: &RequestHandler, name: &str) -> rmux_proto::SessionId {
handler
.state
.lock()
.await
.sessions
.session(&session_name(name))
.expect("session exists")
.id()
}
#[tokio::test]
async fn targetless_attach_picks_the_last_used_session_not_the_lowest_id_or_name() {
let handler = same_second_handler(&["m03", "z99", "a01"], &["z99", "m03"]).await;
assert_eq!(
session_id(&handler, "m03").await,
rmux_proto::SessionId::new(0),
"the truly last-used session deliberately holds the lowest creation id"
);
assert_eq!(
handler
.preferred_session_name()
.await
.expect("preferred session resolves"),
session_name("m03")
);
}
#[tokio::test]
async fn targetless_attach_prefers_an_unattached_session_over_the_most_recent_one() {
let handler = same_second_handler(&["a01", "z99", "m03"], &["a01", "m03"]).await;
let (control_tx, _control_rx) = mpsc::unbounded_channel();
handler
.register_attach(140_001, session_name("m03"), control_tx)
.await;
{
let mut state = handler.state.lock().await;
for name in ["a01", "z99", "m03"] {
state
.sessions
.session_mut(&session_name(name))
.expect("session exists")
.pin_public_times_for_tests(PINNED_SECOND);
}
}
assert_eq!(
handler
.preferred_session_name()
.await
.expect("preferred session resolves"),
session_name("a01"),
"unattached preference outranks recency, and a01 was used after z99"
);
}
#[tokio::test]
async fn targetless_attach_ranks_a_later_creation_above_an_earlier_attach() {
let handler = RequestHandler::new();
for name in ["m03", "a01"] {
create_detached_session(&handler, name).await;
}
{
let mut state = handler.state.lock().await;
state
.sessions
.session_mut(&session_name("a01"))
.expect("a01 exists")
.touch_attached();
}
create_detached_session(&handler, "z99").await;
handler.wait_for_initial_panes_for_test().await;
{
let mut state = handler.state.lock().await;
for name in ["m03", "a01", "z99"] {
state
.sessions
.session_mut(&session_name(name))
.expect("session exists")
.pin_public_times_for_tests(PINNED_SECOND);
}
assert!(
state
.sessions
.session(&session_name("a01"))
.expect("a01 exists")
.last_attached_at()
.is_some(),
"a01 must carry the only attach history in this fixture"
);
}
assert_eq!(
handler
.preferred_session_name()
.await
.expect("preferred session resolves"),
session_name("z99")
);
}
#[tokio::test]
async fn destroy_switch_ranks_the_all_session_candidate_set_by_recency() {
let handler = same_second_handler(&["m03", "z99", "a01", "source"], &["z99", "m03"]).await;
set_detach_on_destroy(&handler, "off").await;
let (control_tx, mut control_rx) = mpsc::unbounded_channel();
handler
.register_attach(141_001, session_name("source"), control_tx)
.await;
kill_source_session(&handler).await;
let target = recv_switch_target(&mut control_rx, "detach-on-destroy off").await;
assert_eq!(target.session_name, session_name("m03"));
}
#[tokio::test]
async fn destroy_switch_establishes_its_detached_candidate_set_before_ranking_it() {
let handler = same_second_handler(&["a01", "b02", "c03", "source"], &["b02", "c03"]).await;
set_detach_on_destroy(&handler, "no-detached").await;
let (occupied_tx, _occupied_rx) = mpsc::unbounded_channel();
handler
.register_attach(142_001, session_name("c03"), occupied_tx)
.await;
let (control_tx, mut control_rx) = mpsc::unbounded_channel();
handler
.register_attach(142_002, session_name("source"), control_tx)
.await;
kill_source_session(&handler).await;
let target = recv_switch_target(&mut control_rx, "detach-on-destroy no-detached").await;
assert_eq!(target.session_name, session_name("b02"));
}
async fn set_detach_on_destroy(handler: &RequestHandler, value: &str) {
handler
.state
.lock()
.await
.options
.set(
ScopeSelector::Session(session_name("source")),
OptionName::DetachOnDestroy,
value.to_owned(),
SetOptionMode::Replace,
)
.expect("detach-on-destroy policy is valid");
}
async fn kill_source_session(handler: &RequestHandler) {
let response = handler
.handle(Request::KillSession(KillSessionRequest {
target: session_name("source"),
kill_all_except_target: false,
clear_alerts: false,
kill_group: false,
}))
.await;
assert!(matches!(response, Response::KillSession(_)), "{response:?}");
}