rmux-server 0.1.1

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
#![cfg(unix)]

mod common;

use std::error::Error;
use std::time::Duration;

use common::{send_request, session_name, start_server, TestHarness, PTY_TEST_LOCK};
use rmux_proto::{
    CapturePaneRequest, NewSessionRequest, PaneTarget, Request, Response, SendKeysRequest,
    ShowBufferRequest, TerminalSize,
};
use tokio::time::sleep;

#[tokio::test]
async fn capture_pane_reads_unattached_transcript() -> Result<(), Box<dyn Error>> {
    let _pty_guard = PTY_TEST_LOCK.lock().await;
    let harness = TestHarness::new("capture-unattached");
    let server = start_server(&harness).await?;
    let target = PaneTarget::with_window(session_name("alpha"), 0, 0);
    let marker = "server_capture_unattached_marker";

    let created = send_request(
        harness.socket_path(),
        &Request::NewSession(NewSessionRequest {
            session_name: session_name("alpha"),
            detached: true,
            size: Some(TerminalSize { cols: 80, rows: 24 }),
            environment: None,
        }),
    )
    .await?;
    assert!(matches!(created, Response::NewSession(_)));

    let sent = send_request(
        harness.socket_path(),
        &Request::SendKeys(SendKeysRequest {
            target: target.clone(),
            keys: vec![format!("printf '{marker}\\n'"), "Enter".to_owned()],
        }),
    )
    .await?;
    assert!(matches!(sent, Response::SendKeys(_)));

    let output = wait_for_capture(harness.socket_path(), target.clone(), marker).await?;
    assert!(String::from_utf8_lossy(&output).contains(marker));

    let captured = send_request(
        harness.socket_path(),
        &Request::CapturePane(CapturePaneRequest {
            target,
            start: None,
            end: None,
            print: false,
            buffer_name: Some("server-cap".to_owned()),
            alternate: false,
            escape_ansi: false,
            escape_sequences: false,
            join_wrapped: false,
            use_mode_screen: false,
            preserve_trailing_spaces: false,
            do_not_trim_spaces: false,
            pending_input: false,
            quiet: false,
            start_is_absolute: false,
            end_is_absolute: false,
        }),
    )
    .await?;
    match captured {
        Response::CapturePane(response) => {
            assert_eq!(response.buffer_name.as_deref(), Some("server-cap"));
            assert!(response.command_output().is_none());
        }
        other => panic!("expected capture-pane response, got {other:?}"),
    }

    let show = send_request(
        harness.socket_path(),
        &Request::ShowBuffer(ShowBufferRequest {
            name: Some("server-cap".to_owned()),
        }),
    )
    .await?;
    assert!(String::from_utf8_lossy(
        show.command_output()
            .expect("show-buffer returns output")
            .stdout()
    )
    .contains(marker));

    server.shutdown().await?;
    Ok(())
}

async fn wait_for_capture(
    socket_path: &std::path::Path,
    target: PaneTarget,
    marker: &str,
) -> Result<Vec<u8>, Box<dyn Error>> {
    for _ in 0..100 {
        let response = send_request(
            socket_path,
            &Request::CapturePane(CapturePaneRequest {
                target: target.clone(),
                start: None,
                end: None,
                print: true,
                buffer_name: None,
                alternate: false,
                escape_ansi: false,
                escape_sequences: false,
                join_wrapped: false,
                use_mode_screen: false,
                preserve_trailing_spaces: false,
                do_not_trim_spaces: false,
                pending_input: false,
                quiet: false,
                start_is_absolute: false,
                end_is_absolute: false,
            }),
        )
        .await?;
        let output = response
            .command_output()
            .expect("capture-pane -p returns output");
        if String::from_utf8_lossy(output.stdout()).contains(marker) {
            return Ok(output.stdout().to_vec());
        }

        sleep(Duration::from_millis(20)).await;
    }

    Err(format!("capture output never contained marker {marker}").into())
}