rmux-server 0.1.1

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

#[tokio::test]
async fn parsed_queue_accepts_display_message_format_flag() {
    let handler = RequestHandler::new();
    let alpha = session_name("alpha");
    assert!(matches!(
        handler
            .handle(Request::NewSession(NewSessionRequest {
                session_name: alpha.clone(),
                detached: true,
                size: Some(TerminalSize { cols: 80, rows: 24 }),
                environment: None,
            }))
            .await,
        Response::NewSession(_)
    ));

    let parsed = CommandParser::new()
        .parse("display-message -p -F '#{session_name}' -t alpha")
        .expect("commands parse");
    let output = handler
        .execute_parsed_commands_for_test(std::process::id(), parsed)
        .await
        .expect("display-message -F executes");

    assert_eq!(output.stdout(), b"alpha\n");
}

#[tokio::test]
async fn parse_control_commands_rejects_invalid_prompt_history_type() {
    let handler = RequestHandler::new();

    let parsed = handler
        .parse_control_commands("show-prompt-history -T bogus")
        .await
        .expect("command should parse before execution");
    let error = handler
        .execute_parsed_commands_for_test(std::process::id(), parsed)
        .await
        .expect_err("invalid prompt type should fail during execution");

    assert_eq!(
        error,
        rmux_proto::RmuxError::Server("invalid type: bogus".to_owned())
    );
}

#[tokio::test]
async fn parsed_queue_set_environment_requires_a_value() {
    let handler = RequestHandler::new();
    let parsed = CommandParser::new()
        .parse("set-environment -g TERM")
        .expect("commands parse");

    let error = handler
        .execute_parsed_commands_for_test(std::process::id(), parsed)
        .await
        .expect_err("missing set-environment value should fail");

    assert_eq!(
        error,
        rmux_proto::RmuxError::Server("no value specified".to_owned())
    );
}

#[tokio::test]
async fn hook_string_mode_newlines_share_one_abort_group() {
    let handler = RequestHandler::new();

    let result = with_hook_execution(Vec::new(), async {
        handler
            .execute_hook_command(
                std::process::id(),
                "show-buffer -b missing\nset-buffer -b skipped no",
            )
            .await
    })
    .await;

    assert!(result.is_err());
    assert!(matches!(
        handler
            .handle(Request::ShowBuffer(ShowBufferRequest {
                name: Some("skipped".to_owned()),
            }))
            .await,
        Response::Error(_)
    ));
}

#[tokio::test]
async fn wait_for_signal_wakes_current_waiters_and_latches_one_future_wait() {
    let handler = Arc::new(RequestHandler::new());
    let first_waiter = {
        let handler = Arc::clone(&handler);
        tokio::spawn(async move { handler.handle(wait_for("signal", WaitForMode::Wait)).await })
    };
    let second_waiter = {
        let handler = Arc::clone(&handler);
        tokio::spawn(async move { handler.handle(wait_for("signal", WaitForMode::Wait)).await })
    };
    yield_until_counts(&handler, "signal", (2, 0, false)).await;

    assert_eq!(
        handler
            .handle(wait_for("signal", WaitForMode::Signal))
            .await,
        Response::WaitFor(WaitForResponse)
    );
    assert_eq!(
        first_waiter.await.expect("first waiter task"),
        Response::WaitFor(WaitForResponse)
    );
    assert_eq!(
        second_waiter.await.expect("second waiter task"),
        Response::WaitFor(WaitForResponse)
    );
    yield_until_counts(&handler, "signal", (0, 0, false)).await;

    assert_eq!(
        handler
            .handle(wait_for("future", WaitForMode::Signal))
            .await,
        Response::WaitFor(WaitForResponse)
    );
    yield_until_counts(&handler, "future", (0, 0, true)).await;

    let future_waiter = {
        let handler = Arc::clone(&handler);
        tokio::spawn(async move { handler.handle(wait_for("future", WaitForMode::Wait)).await })
    };
    assert_eq!(
        future_waiter.await.expect("future waiter task"),
        Response::WaitFor(WaitForResponse)
    );
    yield_until_counts(&handler, "future", (0, 0, false)).await;

    let second_future_waiter = {
        let handler = Arc::clone(&handler);
        tokio::spawn(async move { handler.handle(wait_for("future", WaitForMode::Wait)).await })
    };
    yield_until_counts(&handler, "future", (1, 0, false)).await;
    assert!(!second_future_waiter.is_finished());
    second_future_waiter.abort();
    assert!(second_future_waiter
        .await
        .expect_err("waiter is cancelled")
        .is_cancelled());
    yield_until_counts(&handler, "future", (0, 0, false)).await;
}

#[tokio::test]
async fn wait_for_unlock_hands_locks_to_queued_waiters_in_fifo_order() {
    let handler = Arc::new(RequestHandler::new());

    assert_eq!(
        handler.handle(wait_for("lock", WaitForMode::Lock)).await,
        Response::WaitFor(WaitForResponse)
    );
    yield_until_counts(&handler, "lock", (0, 0, true)).await;

    let first = spawn_wait_for(&handler, "lock", WaitForMode::Lock);
    yield_until_counts(&handler, "lock", (0, 1, true)).await;
    let second = spawn_wait_for(&handler, "lock", WaitForMode::Lock);
    yield_until_counts(&handler, "lock", (0, 2, true)).await;

    assert_eq!(
        handler.handle(wait_for("lock", WaitForMode::Unlock)).await,
        Response::WaitFor(WaitForResponse)
    );
    assert_eq!(
        first.await.expect("first lock"),
        Response::WaitFor(WaitForResponse)
    );
    yield_until_counts(&handler, "lock", (0, 1, true)).await;
    assert!(!second.is_finished());

    assert_eq!(
        handler.handle(wait_for("lock", WaitForMode::Unlock)).await,
        Response::WaitFor(WaitForResponse)
    );
    assert_eq!(
        second.await.expect("second lock"),
        Response::WaitFor(WaitForResponse)
    );
    yield_until_counts(&handler, "lock", (0, 0, true)).await;

    assert_eq!(
        handler.handle(wait_for("lock", WaitForMode::Unlock)).await,
        Response::WaitFor(WaitForResponse)
    );
    yield_until_counts(&handler, "lock", (0, 0, false)).await;
}

#[tokio::test]
async fn wait_for_unlock_on_unlocked_channel_returns_error() {
    let handler = RequestHandler::new();

    let response = handler
        .handle(wait_for("missing", WaitForMode::Unlock))
        .await;

    assert!(matches!(response, Response::Error(_)));
}

#[tokio::test]
async fn wait_for_cancellation_removes_plain_and_lock_waiters() {
    let handler = Arc::new(RequestHandler::new());

    let plain = spawn_wait_for(&handler, "cancel-plain", WaitForMode::Wait);
    yield_until_counts(&handler, "cancel-plain", (1, 0, false)).await;
    plain.abort();
    assert!(plain
        .await
        .expect_err("plain waiter is cancelled")
        .is_cancelled());
    yield_until_counts(&handler, "cancel-plain", (0, 0, false)).await;

    assert_eq!(
        handler
            .handle(wait_for("cancel-lock", WaitForMode::Lock))
            .await,
        Response::WaitFor(WaitForResponse)
    );
    let lock = spawn_wait_for(&handler, "cancel-lock", WaitForMode::Lock);
    yield_until_counts(&handler, "cancel-lock", (0, 1, true)).await;
    lock.abort();
    assert!(lock
        .await
        .expect_err("lock waiter is cancelled")
        .is_cancelled());
    yield_until_counts(&handler, "cancel-lock", (0, 0, true)).await;
}

#[tokio::test]
async fn wait_for_shutdown_releases_plain_and_lock_waiters() {
    let handler = Arc::new(RequestHandler::new());

    assert_eq!(
        handler
            .handle(wait_for("shutdown-lock", WaitForMode::Lock))
            .await,
        Response::WaitFor(WaitForResponse)
    );
    let plain = spawn_wait_for(&handler, "shutdown-plain", WaitForMode::Wait);
    yield_until_counts(&handler, "shutdown-plain", (1, 0, false)).await;
    let lock = spawn_wait_for(&handler, "shutdown-lock", WaitForMode::Lock);
    yield_until_counts(&handler, "shutdown-lock", (0, 1, true)).await;

    handler.shutdown_wait_for_for_test();

    assert!(matches!(
        plain.await.expect("plain waiter"),
        Response::Error(_)
    ));
    assert!(matches!(
        lock.await.expect("lock waiter"),
        Response::Error(_)
    ));
    yield_until_counts(&handler, "shutdown-plain", (0, 0, false)).await;
    yield_until_counts(&handler, "shutdown-lock", (0, 0, false)).await;
}

fn spawn_wait_for(
    handler: &Arc<RequestHandler>,
    channel: &'static str,
    mode: WaitForMode,
) -> tokio::task::JoinHandle<Response> {
    let handler = Arc::clone(handler);
    tokio::spawn(async move { handler.handle(wait_for(channel, mode)).await })
}

async fn yield_until_counts(
    handler: &RequestHandler,
    channel: &str,
    expected: (usize, usize, bool),
) {
    for _ in 0..100 {
        if handler.wait_for_counts(channel) == expected {
            return;
        }
        tokio::task::yield_now().await;
    }

    assert_eq!(handler.wait_for_counts(channel), expected);
}