rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use super::*;

#[tokio::test]
async fn moved_pane_stays_live_while_its_destroyed_source_window_retires() {
    let handler = RequestHandler::new();
    let session_name = session_name("retained-pane-move");
    let mut state = handler.state.lock().await;
    state
        .sessions
        .create_session(session_name.clone(), terminal_size())
        .expect("create session");
    state
        .sessions
        .session_mut(&session_name)
        .expect("session exists")
        .create_window(terminal_size())
        .expect("create destination window");

    let source_pane = PaneTarget::with_window(session_name.clone(), 0, 0);
    let source_window = WindowTarget::with_window(session_name.clone(), 0);
    let pane_lease = state
        .capture_retained_pane_lifecycle_target(&source_pane)
        .expect("capture pane lease");
    let window_lease = state
        .capture_retained_window_lifecycle_target(&source_window)
        .expect("capture window lease");
    let pane_id = state
        .sessions
        .session(&session_name)
        .and_then(|session| session.window_at(0))
        .and_then(|window| window.pane(0))
        .expect("source pane exists")
        .id();

    state
        .sessions
        .session_mut(&session_name)
        .expect("session exists")
        .join_pane(
            SessionPaneTarget::new(0, 0),
            SessionPaneTarget::new(1, 0),
            PaneJoinOptions::new(SplitDirection::Vertical, false, false, false, None),
        )
        .expect("move last source pane into destination window");
    state.retire_removed_lifecycle_targets();

    let resolved_pane = match pane_lease.resolve(&state) {
        LeaseResolution::Live(Target::Pane(target)) => target,
        resolution => panic!("moved pane should stay live, got {resolution:?}"),
    };
    assert_eq!(resolved_pane.window_index(), 1);
    assert_eq!(
        state
            .sessions
            .session(&session_name)
            .and_then(|session| session.window_at(resolved_pane.window_index()))
            .and_then(|window| window.pane(resolved_pane.pane_index()))
            .map(rmux_core::Pane::id),
        Some(pane_id)
    );
    assert_retired(&window_lease, &state);

    state.retire_respawned_lifecycle_panes(&[pane_id]);
    assert_retired(&pane_lease, &state);
}

#[tokio::test]
async fn retained_targets_follow_surviving_aliases_deterministically() {
    let handler = RequestHandler::new();
    let alpha = create_handler_session(&handler, "retained-alias-alpha").await;
    let beta = create_handler_session(&handler, "retained-alias-beta").await;
    let gamma = create_handler_session(&handler, "retained-alias-gamma").await;
    let response = handler
        .handle(Request::NewWindow(Box::new(NewWindowRequest {
            target: alpha.clone(),
            name: None,
            detached: true,
            start_directory: None,
            environment: None,
            command: None,
            process_command: None,
            target_window_index: Some(1),
            insert_at_target: false,
        })))
        .await;
    assert!(matches!(response, Response::NewWindow(_)), "{response:?}");
    handler.wait_for_initial_panes_for_test().await;
    for (session_name, window_index) in [(&gamma, 3), (&gamma, 2), (&beta, 1)] {
        let response = handler
            .handle(Request::LinkWindow(LinkWindowRequest {
                source: WindowTarget::with_window(alpha.clone(), 0),
                target: WindowTarget::with_window(session_name.clone(), window_index),
                after: false,
                before: false,
                kill_destination: false,
                detached: true,
            }))
            .await;
        assert!(matches!(response, Response::LinkWindow(_)), "{response:?}");
    }

    let source_window = WindowTarget::with_window(alpha.clone(), 0);
    let source_pane = PaneTarget::with_window(alpha.clone(), 0, 0);
    let (window_lease, pane_lease) = {
        let state = handler.state.lock().await;
        let window_lease = state
            .capture_retained_window_lifecycle_target(&source_window)
            .expect("capture retained window alias");
        let pane_lease = state
            .capture_retained_pane_lifecycle_target(&source_pane)
            .expect("capture retained pane alias");
        (window_lease, pane_lease)
    };

    let response = handler
        .handle(Request::UnlinkWindow(UnlinkWindowRequest {
            target: source_window,
            kill_if_last: false,
        }))
        .await;
    assert!(
        matches!(response, Response::UnlinkWindow(_)),
        "{response:?}"
    );

    let state = handler.state.lock().await;
    assert_eq!(
        window_lease.resolve(&state),
        LeaseResolution::Live(Target::Window(WindowTarget::with_window(gamma.clone(), 2,)))
    );
    assert_eq!(
        pane_lease.resolve(&state),
        LeaseResolution::Live(Target::Pane(PaneTarget::with_window(gamma, 2, 0)))
    );
}

#[tokio::test]
async fn surviving_alias_becomes_the_retirement_slot_after_original_slot_reuse() {
    let handler = RequestHandler::new();
    let alpha = create_handler_session(&handler, "retained-cursor-alpha").await;
    let beta = create_handler_session(&handler, "retained-cursor-beta").await;
    let response = handler
        .handle(Request::NewWindow(Box::new(NewWindowRequest {
            target: alpha.clone(),
            name: None,
            detached: true,
            start_directory: None,
            environment: None,
            command: None,
            process_command: None,
            target_window_index: Some(1),
            insert_at_target: false,
        })))
        .await;
    assert!(matches!(response, Response::NewWindow(_)), "{response:?}");
    handler.wait_for_initial_panes_for_test().await;
    let source = WindowTarget::with_window(alpha.clone(), 0);
    let response = handler
        .handle(Request::LinkWindow(LinkWindowRequest {
            source: source.clone(),
            target: WindowTarget::with_window(beta.clone(), 1),
            after: false,
            before: false,
            kill_destination: false,
            detached: true,
        }))
        .await;
    assert!(matches!(response, Response::LinkWindow(_)), "{response:?}");
    let lease = {
        let state = handler.state.lock().await;
        state
            .capture_retained_window_lifecycle_target(&source)
            .expect("capture retained aliased window")
    };

    let response = handler
        .handle(Request::UnlinkWindow(UnlinkWindowRequest {
            target: source,
            kill_if_last: false,
        }))
        .await;
    assert!(
        matches!(response, Response::UnlinkWindow(_)),
        "{response:?}"
    );
    {
        let state = handler.state.lock().await;
        assert_eq!(
            lease.resolve(&state),
            LeaseResolution::Live(Target::Window(WindowTarget::with_window(beta.clone(), 1)))
        );
    }

    let response = handler
        .handle(Request::NewWindow(Box::new(NewWindowRequest {
            target: alpha,
            name: None,
            detached: true,
            start_directory: None,
            environment: None,
            command: None,
            process_command: None,
            target_window_index: Some(0),
            insert_at_target: false,
        })))
        .await;
    assert!(matches!(response, Response::NewWindow(_)), "{response:?}");
    handler.wait_for_initial_panes_for_test().await;

    let response = handler
        .handle(Request::UnlinkWindow(UnlinkWindowRequest {
            target: WindowTarget::with_window(beta, 1),
            kill_if_last: true,
        }))
        .await;
    assert!(
        matches!(response, Response::UnlinkWindow(_)),
        "{response:?}"
    );
    let state = handler.state.lock().await;
    assert_retired(&lease, &state);
}

#[tokio::test]
async fn respawn_boundary_retires_the_old_pane_lifetime_even_when_id_survives() {
    let handler = RequestHandler::new();
    let session_name = session_name("retained-pane-respawn");
    let mut state = handler.state.lock().await;
    state
        .sessions
        .create_session(session_name.clone(), terminal_size())
        .expect("create session");
    let target = PaneTarget::with_window(session_name.clone(), 0, 0);
    let lease = state
        .capture_retained_pane_lifecycle_target(&target)
        .expect("capture pane lease");
    let pane_id = state
        .sessions
        .session(&session_name)
        .and_then(|session| session.window_at(0))
        .and_then(|window| window.pane(0))
        .expect("pane exists")
        .id();

    state.retire_respawned_lifecycle_panes(&[pane_id]);

    assert_retired(&lease, &state);
    assert!(
        state
            .sessions
            .session(&session_name)
            .and_then(|session| session.window_at(0))
            .and_then(|window| window.pane(0))
            .is_some_and(|pane| pane.id() == pane_id),
        "respawn retirement is a lifetime boundary, not numeric-id removal"
    );
}

#[tokio::test]
async fn same_transaction_slot_replacement_invalidates_instead_of_retargeting() {
    let handler = RequestHandler::new();
    let session_name = session_name("retained-window-replacement");
    let mut state = handler.state.lock().await;
    state
        .sessions
        .create_session(session_name.clone(), terminal_size())
        .expect("create session");
    let target = WindowTarget::with_window(session_name.clone(), 0);
    let lease = state
        .capture_retained_window_lifecycle_target(&target)
        .expect("capture window lease");

    let session = state
        .sessions
        .session_mut(&session_name)
        .expect("session exists");
    session
        .remove_window_allowing_empty(0)
        .expect("remove original window");
    session
        .insert_window_with_initial_pane(0, terminal_size())
        .expect("replace numeric slot");
    state.retire_removed_lifecycle_targets();

    assert_eq!(lease.resolve(&state), LeaseResolution::Replaced);
}

#[tokio::test]
async fn retired_window_never_reacquires_a_later_numeric_slot_reuse() {
    let handler = RequestHandler::new();
    let session_name = session_name("retained-window-reuse");
    let mut state = handler.state.lock().await;
    state
        .sessions
        .create_session(session_name.clone(), terminal_size())
        .expect("create session");
    let target = WindowTarget::with_window(session_name.clone(), 0);
    let lease = state
        .capture_retained_window_lifecycle_target(&target)
        .expect("capture window lease");
    assert!(matches!(lease.resolve(&state), LeaseResolution::Live(_)));

    state
        .sessions
        .session_mut(&session_name)
        .expect("session exists")
        .remove_window_allowing_empty(0)
        .expect("remove original window");
    state.retire_removed_lifecycle_targets();
    assert_retired(&lease, &state);

    state
        .sessions
        .session_mut(&session_name)
        .expect("session exists")
        .insert_window_with_initial_pane(0, terminal_size())
        .expect("reuse old numeric slot later");
    assert_retired(&lease, &state);
}