diff options
Diffstat (limited to 'tests/client_mode.rs')
| -rw-r--r-- | tests/client_mode.rs | 527 |
1 files changed, 255 insertions, 272 deletions
diff --git a/tests/client_mode.rs b/tests/client_mode.rs index 5112182d..c6c76c34 100644 --- a/tests/client_mode.rs +++ b/tests/client_mode.rs @@ -2,7 +2,7 @@ #![cfg(unix)] -mod support; +pub mod support; use std::fs; use std::io::{BufRead, BufReader, Read, Write}; @@ -13,12 +13,13 @@ use std::thread; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use portable_pty::{native_pty_system, Child, CommandBuilder, MasterPty, PtySize}; -use serde::Deserialize; use serde_json::Value; use support::{ - cleanup_test_base, client_handshake, encode_varint_u32, frame_message, read_server_message, - register_runtime_dir, register_spawned_herdr_pid, unregister_spawned_herdr_pid, - wait_for_message_variant, wait_for_socket, wait_until, CURRENT_PROTOCOL, + cleanup_test_base, client_shell_handshake, read_server_message, register_runtime_dir, + register_spawned_herdr_pid, unregister_spawned_herdr_pid, wait_for_client_shell_bootstrap, + wait_for_message_variant, wait_for_message_variants, wait_for_socket, wait_until, + CURRENT_PROTOCOL, SERVER_MESSAGE_PANE_SURFACE, SERVER_MESSAGE_PANE_SURFACE_PATCH, + SERVER_MESSAGE_SEMANTIC_NOTIFICATION, SERVER_MESSAGE_SERVER_SHUTDOWN, }; fn unique_test_dir() -> PathBuf { @@ -86,12 +87,30 @@ fn spawn_client_process( spawn_client_process_with_args(config_home, runtime_dir, api_socket_path, &["client"]) } +fn spawn_client_shell_process( + config_home: &PathBuf, + runtime_dir: &PathBuf, + api_socket_path: &PathBuf, +) -> SpawnedHerdr { + spawn_client_process_with_args(config_home, runtime_dir, api_socket_path, &["client"]) +} + fn spawn_client_process_with_args( config_home: &PathBuf, runtime_dir: &PathBuf, api_socket_path: &PathBuf, args: &[&str], ) -> SpawnedHerdr { + spawn_client_process_with_args_and_env(config_home, runtime_dir, api_socket_path, args, &[]) +} + +fn spawn_client_process_with_args_and_env( + config_home: &PathBuf, + runtime_dir: &PathBuf, + api_socket_path: &PathBuf, + args: &[&str], + extra_env: &[(&str, &str)], +) -> SpawnedHerdr { register_runtime_dir(runtime_dir); let pair = native_pty_system() .openpty(PtySize { @@ -111,47 +130,10 @@ fn spawn_client_process_with_args( cmd.env_remove("HERDR_CLIENT_SOCKET_PATH"); cmd.env("SHELL", "/bin/sh"); cmd.env_remove("HERDR_ENV"); - - let child = pair.slave.spawn_command(cmd).unwrap(); - register_spawned_herdr_pid(child.process_id()); - drop(pair.slave); - - SpawnedHerdr { - _master: Some(pair.master), - child, + for (key, value) in extra_env { + cmd.env(key, value); } -} -fn spawn_no_session_process(config_home: &PathBuf, runtime_dir: &PathBuf) -> SpawnedHerdr { - fs::create_dir_all(config_home.join(app_dir_name())).unwrap(); - fs::create_dir_all(runtime_dir).unwrap(); - register_runtime_dir(runtime_dir); - fs::write( - config_home.join(app_dir_name()).join("config.toml"), - "onboarding = false\n[ui]\nwindow_title = \"monolithic\"\n", - ) - .unwrap(); - - let pair = native_pty_system() - .openpty(PtySize { - rows: 24, - cols: 80, - pixel_width: 0, - pixel_height: 0, - }) - .unwrap(); - let mut cmd = CommandBuilder::new(env!("CARGO_BIN_EXE_herdr")); - cmd.arg("--no-session"); - cmd.env("XDG_CONFIG_HOME", config_home); - cmd.env("XDG_RUNTIME_DIR", runtime_dir); - cmd.env("SHELL", "/bin/sh"); - cmd.env_remove("HERDR_ENV"); - cmd.env_remove("HERDR_SOCKET_PATH"); - cmd.env_remove("HERDR_CLIENT_SOCKET_PATH"); - cmd.env_remove("HERDR_SESSION"); - cmd.env_remove("HERDR_WORKSPACE_ID"); - cmd.env_remove("HERDR_TAB_ID"); - cmd.env_remove("HERDR_PANE_ID"); let child = pair.slave.spawn_command(cmd).unwrap(); register_spawned_herdr_pid(child.process_id()); drop(pair.slave); @@ -266,100 +248,12 @@ fn app_dir_name() -> &'static str { } } -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -struct FrameWire { - cells: Vec<CellWire>, - width: u16, - height: u16, - cursor: Option<CursorWire>, - hyperlinks: Vec<String>, - graphics: Vec<u8>, -} - -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -struct CellWire { - symbol: String, - fg: u32, - bg: u32, - modifier: u16, - skip: bool, - hyperlink: Option<u32>, -} - -#[derive(Debug, Deserialize)] -struct CursorWire { - x: u16, - y: u16, - visible: bool, - shape: u8, -} - -fn decode_frame_payload(payload: &[u8]) -> std::io::Result<FrameWire> { - bincode::serde::decode_from_slice(payload, bincode::config::standard()) - .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err.to_string())) - .and_then(|(frame, consumed): (FrameWire, usize)| { - if consumed != payload.len() { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!( - "frame payload had trailing bytes: consumed={}, len={}", - consumed, - payload.len() - ), - )); - } - Ok(frame) - }) -} - -fn read_next_frame_payload(stream: &mut UnixStream, timeout: Duration) -> Result<Vec<u8>, String> { - stream - .set_read_timeout(Some(Duration::from_millis(200))) - .map_err(|e| e.to_string())?; - let deadline = Instant::now() + timeout; - while Instant::now() < deadline { - match read_server_message(stream) { - Ok((1, payload)) => return Ok(payload), - Ok(_) => continue, - Err(_) => continue, - } - } - Err("timed out waiting for Frame message".into()) -} - -fn frame_text(frame: &FrameWire) -> String { - if frame.cells.is_empty() { - return String::new(); - } - - let width = frame.width.max(1) as usize; - let mut text = String::new(); - for row in frame.cells.chunks(width) { - for cell in row { - let _ = (cell.fg, cell.bg, cell.modifier, cell.skip); - text.push_str(&cell.symbol); - } - text.push('\n'); - } - let _ = (frame.height, frame.graphics.len()); - if let Some(cursor) = frame.cursor.as_ref() { - let _ = (cursor.x, cursor.y, cursor.visible, cursor.shape); - } - - text -} - // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- #[test] -fn client_connects_and_receives_frame() { - // Client connects to server and handshake completes. - // Client receives Frame messages. - // Server sends rendered frames to connected clients. +fn client_connects_and_receives_pane_surface() { let _lock = test_lock(); let base = unique_test_dir(); let config_home = base.join("config"); @@ -371,22 +265,13 @@ fn client_connects_and_receives_frame() { wait_for_socket(&api_socket, Duration::from_secs(10)); wait_for_socket(&client_socket, Duration::from_secs(10)); - // Connect and handshake. let mut stream = UnixStream::connect(&client_socket).expect("should connect to client socket"); - let (version, error) = - client_handshake(&mut stream, CURRENT_PROTOCOL, 80, 24).expect("handshake should succeed"); - assert_eq!( - version, CURRENT_PROTOCOL, - "server should report current protocol version" - ); - assert!( - error.is_none(), - "handshake should not have error: {:?}", - error - ); - - read_next_frame_payload(&mut stream, Duration::from_secs(10)) - .expect("should receive a frame from server"); + let (version, error) = client_shell_handshake(&mut stream, CURRENT_PROTOCOL, 54, 23) + .expect("handshake should succeed"); + assert_eq!(version, CURRENT_PROTOCOL); + assert!(error.is_none(), "{error:?}"); + wait_for_client_shell_bootstrap(&mut stream, Duration::from_secs(10)) + .expect("should receive the shell snapshot and pane surface"); cleanup_spawned_herdr(spawned, base); } @@ -560,41 +445,26 @@ fn client_sees_headless_startup_config_diagnostic() { wait_for_socket(&api_socket, Duration::from_secs(10)); wait_for_socket(&client_socket, Duration::from_secs(10)); - let mut stream = UnixStream::connect(&client_socket).expect("should connect to client socket"); - let (version, error) = - client_handshake(&mut stream, CURRENT_PROTOCOL, 80, 24).expect("handshake should succeed"); - assert_eq!(version, CURRENT_PROTOCOL); - assert!(error.is_none(), "{:?}", error); - - stream - .set_read_timeout(Some(Duration::from_secs(5))) - .unwrap(); - let deadline = Instant::now() + Duration::from_secs(5); - let mut found_diagnostic = false; - let mut last_frame_text = String::new(); - while Instant::now() < deadline { - match read_server_message(&mut stream) { - Ok((1, payload)) => { - let frame = decode_frame_payload(&payload).expect("decode frame"); - last_frame_text = frame_text(&frame); - if last_frame_text.contains("config.toml") - && last_frame_text.contains("herdr config check") - { - found_diagnostic = true; - break; - } - } - Ok(_) => {} - Err(_) => break, - } - } - + let client = spawn_client_shell_process(&config_home, &runtime_dir, &api_socket); + let output = spawn_pty_drain( + client + ._master + .as_ref() + .expect("client shell master") + .try_clone_reader() + .expect("clone client shell reader"), + ); assert!( - found_diagnostic, - "attached client should see startup config parse diagnostic; last frame:\n{last_frame_text}" + wait_until(Duration::from_secs(8), Duration::from_millis(20), || { + let output = read_output(&output); + output.contains("config.toml") && output.contains("herdr config check") + }), + "client shell should render startup config diagnostic; output: {:?}", + read_output(&output) ); - cleanup_spawned_herdr(spawned, base); + drop(spawned); + cleanup_spawned_herdr(client, base); } #[test] @@ -881,6 +751,120 @@ fn attach_thin_client_with_config( (spawned_server, thin_client, output) } +#[test] +fn client_shell_detaches_restores_and_freshly_reattaches_to_current_state() { + let _lock = test_lock(); + let base = unique_test_dir(); + let config_home = base.join("config"); + let runtime_dir = base.join("runtime"); + let api_socket = runtime_dir.join("herdr.sock"); + let client_socket = runtime_dir.join("herdr-client.sock"); + + let mut server = spawn_server(&config_home, &runtime_dir, &api_socket, &client_socket); + wait_for_socket(&api_socket, Duration::from_secs(10)); + wait_for_socket(&client_socket, Duration::from_secs(10)); + + let created = send_json_request( + &api_socket, + &serde_json::json!({ + "id": "client-shell-lifecycle-workspace", + "method": "workspace.create", + "params": {"cwd": base, "focus": true, "label": "shell-lifecycle"}, + }) + .to_string(), + ); + assert_eq!(created["result"]["type"], "workspace_created", "{created}"); + let pane_id = created["result"]["root_pane"]["pane_id"] + .as_str() + .expect("root pane id") + .to_string(); + send_pane_shell_command(&api_socket, &pane_id, "printf 'SHELL_LIFECYCLE_INITIAL\\n'"); + + let mut client_a = spawn_client_shell_process(&config_home, &runtime_dir, &api_socket); + let output_a = spawn_pty_drain( + client_a + ._master + .as_ref() + .expect("first client shell PTY") + .try_clone_reader() + .expect("clone first client shell reader"), + ); + assert!( + wait_until(Duration::from_secs(8), Duration::from_millis(20), || { + let output = read_output(&output_a); + output.contains("shell-lifecycle") && output.contains("SHELL_LIFECYCLE_INITIAL") + }), + "client shell should compose one coherent snapshot and pane surface; output: {:?}", + read_output(&output_a) + ); + + let detach_watermark = output_len(&output_a); + client_a + ._master + .as_ref() + .expect("first client shell PTY") + .take_writer() + .expect("first client shell writer") + .write_all(b"\x02q") + .expect("detach first client shell"); + let detach_output = drain_until_client_exits(&mut client_a, &output_a, detach_watermark); + assert!( + output_has_mouse_teardown(&detach_output), + "client shell should restore the host terminal after detach; output: {detach_output:?}" + ); + assert!( + ping_socket(&api_socket).contains("pong"), + "server should remain alive after client shell detach" + ); + drop(client_a); + + send_pane_shell_command( + &api_socket, + &pane_id, + "printf 'SHELL_LIFECYCLE_DETACHED\\n'", + ); + let mut client_b = spawn_client_shell_process(&config_home, &runtime_dir, &api_socket); + let output_b = spawn_pty_drain( + client_b + ._master + .as_ref() + .expect("reattached client shell PTY") + .try_clone_reader() + .expect("clone reattached client shell reader"), + ); + assert!( + wait_until(Duration::from_secs(8), Duration::from_millis(20), || { + let output = read_output(&output_b); + output.contains("shell-lifecycle") && output.contains("SHELL_LIFECYCLE_DETACHED") + }), + "fresh client shell should receive current state and detached-period output; output: {:?}", + read_output(&output_b) + ); + + let disconnect_watermark = output_len(&output_b); + if let Some(pid) = server.child.process_id() { + unsafe { + libc::kill(pid as libc::pid_t, libc::SIGKILL); + } + } + server.close_master(); + let disconnect_output = + drain_until_client_exits(&mut client_b, &output_b, disconnect_watermark); + assert!( + output_has_mouse_teardown(&disconnect_output), + "client shell should restore the host terminal after endpoint loss; output: {disconnect_output:?}" + ); + assert!( + disconnect_output + .to_lowercase() + .contains("lost connection to server"), + "client shell should explain endpoint loss; output: {disconnect_output:?}" + ); + + drop(server); + cleanup_spawned_herdr(client_b, base); +} + fn captured_window_titles(output: &SharedOutput) -> Vec<String> { read_output(output) .split("\x1b]0;") @@ -943,26 +927,6 @@ fn send_pane_shell_command(socket_path: &PathBuf, pane_id: &str, command: &str) } #[test] -fn configured_window_title_is_emitted_in_no_session_mode() { - let _lock = test_lock(); - let base = unique_test_dir(); - let config_home = base.join("config"); - let runtime_dir = base.join("runtime"); - let no_session = spawn_no_session_process(&config_home, &runtime_dir); - let reader = no_session - ._master - .as_ref() - .expect("no-session PTY master") - .try_clone_reader() - .expect("clone no-session PTY reader"); - let output = spawn_pty_drain(reader); - - wait_for_window_title(&output, "monolithic"); - - cleanup_spawned_herdr(no_session, base); -} - -#[test] fn configured_window_title_tracks_all_tokens_and_focused_osc_only() { let _lock = test_lock(); let base = unique_test_dir(); @@ -1029,7 +993,7 @@ fn configured_window_title_tracks_all_tokens_and_focused_osc_only() { "hostname token was empty: {renamed}" ); - send_pane_shell_command(&api_socket, &pane_id, r"printf '\033]0;⠋ building\007'"); + send_pane_shell_command(&api_socket, &pane_id, r"printf '\033]0;building\007'"); wait_for_window_title(&output, "|W=space-a|T=tab-a|P=pane-a|O=building"); let second_tab = send_json_request( @@ -1308,14 +1272,7 @@ fn client_exits_cleanly_when_terminal_hangs_up() { } #[test] -fn client_receives_frame_after_pane_output() { - // End-to-end test: server renders, client receives Frame. - // This test verifies the full flow: - // 1. Start server - // 2. Connect client, handshake - // 3. Send input to pane (echo command) - // 4. Wait for a new frame from the server - // 5. Verify the frame contains the pane output +fn client_receives_pane_surface_after_pane_output() { let _lock = test_lock(); let base = unique_test_dir(); let config_home = base.join("config"); @@ -1327,33 +1284,55 @@ fn client_receives_frame_after_pane_output() { wait_for_socket(&api_socket, Duration::from_secs(10)); wait_for_socket(&client_socket, Duration::from_secs(10)); - // Connect and handshake. let mut stream = UnixStream::connect(&client_socket).expect("should connect to client socket"); - let (version, error) = - client_handshake(&mut stream, CURRENT_PROTOCOL, 80, 24).expect("handshake should succeed"); + let (version, error) = client_shell_handshake(&mut stream, CURRENT_PROTOCOL, 54, 23) + .expect("handshake should succeed"); assert_eq!(version, CURRENT_PROTOCOL); - assert!(error.is_none(), "{:?}", error); - - read_next_frame_payload(&mut stream, Duration::from_secs(10)) - .expect("should receive initial frame"); - - // Send input to trigger a state change and re-render. - let input_data = b"echo test-output\n".to_vec(); - let input_payload = { - let mut buf = encode_varint_u32(1); // Input variant - buf.extend_from_slice(&encode_varint_u32(input_data.len() as u32)); - buf.extend_from_slice(&input_data); - buf - }; - let framed = frame_message(&input_payload); - stream.write_all(&framed).expect("send input"); - stream.flush().expect("flush"); + assert!(error.is_none(), "{error:?}"); + wait_for_client_shell_bootstrap(&mut stream, Duration::from_secs(10)) + .expect("initial client shell bootstrap"); - // Read subsequent frames — the server should have re-rendered after - // the input was processed. - let received_frame = wait_for_message_variant(&mut stream, Duration::from_secs(2), 1) - .expect("wait for post-output frame"); - assert!(received_frame, "should receive a Frame after pane output"); + let created = send_json_request( + &api_socket, + &serde_json::json!({ + "id": "create-output-workspace", + "method": "workspace.create", + "params": {"label": "output", "focus": true} + }) + .to_string(), + ); + let pane_id = created["result"]["root_pane"]["pane_id"] + .as_str() + .expect("root pane id"); + assert!(wait_for_message_variant( + &mut stream, + Duration::from_secs(5), + SERVER_MESSAGE_PANE_SURFACE, + ) + .expect("wait for created workspace surface")); + + let sent = send_json_request( + &api_socket, + &serde_json::json!({ + "id": "send-output", + "method": "pane.send_text", + "params": {"pane_id": pane_id, "text": "printf 'test-output\\n'\\n"} + }) + .to_string(), + ); + assert!(sent.get("error").is_none(), "{sent}"); + assert!( + wait_for_message_variants( + &mut stream, + Duration::from_secs(5), + &[ + SERVER_MESSAGE_PANE_SURFACE, + SERVER_MESSAGE_PANE_SURFACE_PATCH, + ], + ) + .expect("wait for post-output pane surface"), + "should receive a pane surface update after pane output" + ); cleanup_spawned_herdr(spawned, base); } @@ -1424,7 +1403,25 @@ fn pane_spawn_cwd_fallback_in_server() { "fallback cwd should exist: {cwd}" ); - cleanup_spawned_herdr(spawned, base); + let client_shell = spawn_client_shell_process(&config_home, &runtime_dir, &api_socket); + let output = spawn_pty_drain( + client_shell + ._master + .as_ref() + .expect("restored client shell PTY") + .try_clone_reader() + .expect("clone restored client shell reader"), + ); + assert!( + wait_until(Duration::from_secs(8), Duration::from_millis(20), || { + read_output(&output).contains("missing-cwd") + }), + "client shell should render the restored session; output: {:?}", + read_output(&output) + ); + + drop(spawned); + cleanup_spawned_herdr(client_shell, base); } #[test] @@ -1442,18 +1439,13 @@ fn graceful_shutdown_sends_server_shutdown_to_client() { wait_for_socket(&api_socket, Duration::from_secs(10)); wait_for_socket(&client_socket, Duration::from_secs(10)); - // Connect and handshake. let mut stream = UnixStream::connect(&client_socket).expect("should connect to client socket"); - let (version, error) = - client_handshake(&mut stream, CURRENT_PROTOCOL, 80, 24).expect("handshake should succeed"); + let (version, error) = client_shell_handshake(&mut stream, CURRENT_PROTOCOL, 54, 23) + .expect("handshake should succeed"); assert_eq!(version, CURRENT_PROTOCOL); - assert!(error.is_none(), "{:?}", error); - - // Drain initial frame(s). - stream - .set_read_timeout(Some(Duration::from_secs(2))) - .unwrap(); - while read_server_message(&mut stream).is_ok() {} + assert!(error.is_none(), "{error:?}"); + wait_for_client_shell_bootstrap(&mut stream, Duration::from_secs(5)) + .expect("client shell bootstrap"); // Send SIGINT to the server process to trigger graceful shutdown. if let Some(pid) = spawned.child.process_id() { @@ -1462,7 +1454,7 @@ fn graceful_shutdown_sends_server_shutdown_to_client() { } } - // The client should receive a ServerShutdown message (variant 4) + // The client should receive a ServerShutdown message // before the connection is closed, not just an abrupt EOF. stream .set_read_timeout(Some(Duration::from_secs(5))) @@ -1471,8 +1463,8 @@ fn graceful_shutdown_sends_server_shutdown_to_client() { match result { Ok((variant, _payload)) => { assert_eq!( - variant, 4, - "expected ServerShutdown (variant 4), got variant {variant}" + variant, SERVER_MESSAGE_SERVER_SHUTDOWN, + "expected ServerShutdown, got variant {variant}" ); } Err(e) => { @@ -1501,9 +1493,9 @@ fn client_receives_notify_on_agent_state_change() { let client_socket = runtime_dir.join("herdr-client.sock"); // Enable toast and sound in config so the server produces notifications. - fs::create_dir_all(config_home.join("herdr")).unwrap(); + fs::create_dir_all(config_home.join(app_dir_name())).unwrap(); fs::write( - config_home.join("herdr/config.toml"), + config_home.join(app_dir_name()).join("config.toml"), "onboarding = false\n[ui.toast]\nenabled = true\n[ui.sound]\nenabled = true\n", ) .unwrap(); @@ -1541,18 +1533,13 @@ fn client_receives_notify_on_agent_state_change() { wait_for_socket(&api_socket, Duration::from_secs(10)); wait_for_socket(&client_socket, Duration::from_secs(10)); - // Connect as a client and perform handshake. let mut stream = UnixStream::connect(&client_socket).expect("should connect"); - let (version, error) = - client_handshake(&mut stream, CURRENT_PROTOCOL, 80, 24).expect("handshake should succeed"); + let (version, error) = client_shell_handshake(&mut stream, CURRENT_PROTOCOL, 54, 23) + .expect("handshake should succeed"); assert_eq!(version, CURRENT_PROTOCOL); - assert!(error.is_none(), "{:?}", error); - - // Drain initial frame(s). - stream - .set_read_timeout(Some(Duration::from_secs(2))) - .unwrap(); - while read_server_message(&mut stream).is_ok() {} + assert!(error.is_none(), "{error:?}"); + wait_for_client_shell_bootstrap(&mut stream, Duration::from_secs(5)) + .expect("client shell bootstrap"); // Create a workspace via the API. let mut ws_stream = UnixStream::connect(&api_socket).expect("connect to API"); @@ -1596,8 +1583,7 @@ fn client_receives_notify_on_agent_state_change() { let mut report_response = String::new(); report_reader.read_line(&mut report_response).unwrap(); - // Read messages from the client stream and look for Notify (variant 5). - // Notify = ServerMessage variant index 5. + // Read messages from the client stream and look for the semantic notification. stream .set_read_timeout(Some(Duration::from_secs(5))) .unwrap(); @@ -1606,12 +1592,11 @@ fn client_receives_notify_on_agent_state_change() { while Instant::now() < deadline { match read_server_message(&mut stream) { Ok((variant, _payload)) => { - if variant == 5 { - // ServerMessage::Notify — found it! + if variant == SERVER_MESSAGE_SEMANTIC_NOTIFICATION { found_notify = true; break; } - // Continue reading — Frame messages (variant 1) will come first. + // Snapshot and pane-surface messages may arrive first. } Err(_) => { break; @@ -1621,7 +1606,7 @@ fn client_receives_notify_on_agent_state_change() { assert!( found_notify, - "client should receive a ServerMessage::Notify after pane.report_agent" + "client should receive a semantic notification after pane.report_agent" ); // Now report Idle from Working — this should trigger a Done sound @@ -1683,7 +1668,7 @@ fn client_receives_notify_on_agent_state_change() { let mut idle_response = String::new(); idle_reader.read_line(&mut idle_response).unwrap(); - // Read messages and look for Done sound notify. + // Read messages and look for the done semantic notification. stream .set_read_timeout(Some(Duration::from_secs(5))) .unwrap(); @@ -1692,16 +1677,14 @@ fn client_receives_notify_on_agent_state_change() { while Instant::now() < deadline { match read_server_message(&mut stream) { Ok((variant, _payload)) => { - if variant == 5 { - // Found a Notify message — that's good enough. - // The test already verified the Blocked→Notify path above. + if variant == SERVER_MESSAGE_SEMANTIC_NOTIFICATION { found_done_notify = true; break; } - // Continue reading — Frame messages will come first. + // Snapshot and pane-surface messages may arrive first. } Err(e) => { - eprintln!("read error while looking for Done Notify: {e}"); + eprintln!("read error while looking for done notification: {e}"); break; } } @@ -1709,7 +1692,7 @@ fn client_receives_notify_on_agent_state_change() { assert!( found_done_notify, - "client should receive a Sound Notify with 'agent done' when background pane transitions Working→Idle" + "client should receive a semantic notification when a background pane transitions Working→Idle" ); cleanup_spawned_herdr(spawned, base); |
