ci / rust (push) Failing after 5m11s
Per-attached-session task:
- Owns one WS to `WS /api/attach/:id` and a `vt100::Parser`.
- Binary frames → `parser.process(bytes)`. Text frames → `ClientControl::Ping`
is replied to as Pong; other text frames are ignored in v1.
- `SessionInput::Bytes(b)` becomes a binary frame; `Resize { cols, rows }`
resizes the parser AND sends `ClientControl::Resize` on the wire.
- WS drop → `Reconnecting` with exponential backoff (1s → 30s, ±25% jitter).
Parser screen is **not** reset across reconnects, so the user keeps seeing
the last-known frame instead of a blank.
- `SessionHandle::end()` transitions the task to `Ended` and stops it.
SessionEmulators:
- `(SessionId → SessionHandle)` registry.
- Owns a `ResizeTracker` whose `set_pane_size(sid, pane_id, (cols, rows))`
and `session_size(sid) -> Option<(cols, rows)>` compute the per-dimension
minimum across all panes bound to the same session — the value the
dispatcher feeds to the daemon.
Tests:
- 7 unit tests for `ResizeTracker` covering per-dim min, cross-session
isolation, and pane/session drop semantics.
- 2 unit tests for `SessionState` transitions.
- 1 axum-WS round-trip integration test asserting `"hello"` lands in the
vt100 screen within 500ms.
58 lines
1.9 KiB
Rust
58 lines
1.9 KiB
Rust
//! Acceptance test for `SessionTask`: a fake WS server sends `hello\r\n` as a
|
|
//! binary frame, and we assert the `vt100` screen sees it within the
|
|
//! interval.
|
|
|
|
use std::time::Duration;
|
|
|
|
use axum::extract::ws::{Message, WebSocketUpgrade};
|
|
use axum::response::Response;
|
|
use axum::routing::any;
|
|
use axum::Router;
|
|
use maestro_tui::session_task::{SessionState, SessionTask};
|
|
|
|
async fn attach_handler(ws: WebSocketUpgrade) -> Response {
|
|
ws.on_upgrade(|mut socket| async move {
|
|
// Push initial bytes immediately.
|
|
if socket
|
|
.send(Message::Binary(b"hello\r\n".to_vec()))
|
|
.await
|
|
.is_err()
|
|
{
|
|
return;
|
|
}
|
|
// Keep the connection open so the client doesn't enter reconnect.
|
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
})
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
async fn vt100_screen_receives_pty_bytes() {
|
|
let app = Router::new().route("/api/attach/x", any(attach_handler));
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let addr = listener.local_addr().unwrap();
|
|
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
|
|
|
|
let url = format!("ws://{addr}/api/attach/x");
|
|
let handle = SessionTask::spawn(url, 24, 80);
|
|
|
|
// Poll for up to 500 ms looking for "hello" in row 0.
|
|
for _ in 0..50 {
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
if handle.state() != SessionState::Live {
|
|
continue;
|
|
}
|
|
let parser = handle.screen.lock().expect("vt100 parser mutex");
|
|
let screen = parser.screen();
|
|
let row: String = (0..5)
|
|
.map(|c| screen.cell(0, c).unwrap().contents())
|
|
.collect();
|
|
if row.starts_with("hello") {
|
|
return;
|
|
}
|
|
}
|
|
panic!(
|
|
"vt100 screen never contained 'hello'; state={:?}",
|
|
handle.state()
|
|
);
|
|
}
|