diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-01 15:48:22 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-01 15:48:22 +0300 |
| commit | 207be3c771d281baae6e5fa0fb74be9a056e97a2 (patch) | |
| tree | d7b037ecdf629224a048c841d11659efdc23507d /tests/server_headless.rs | |
| parent | 8a6d697308162874a236c84aac0b0f8c7ac01f83 (diff) | |
refactor: render the shell in the client (#3487)
* feat: add opt-in client-rendered shell
* feat: add client-owned shell mouse controls
* feat: complete client-owned shell chrome
* feat: add client-owned shell settings and notifications
* feat: run client shell custom commands
* feat: render popup terminals in client shell
* feat: add client-owned scrollback and copy mode
* fix: preserve client worktree trust defaults
* feat: render startup diagnostics in client shell
* feat: render onboarding in client shell
* feat: render product announcements in client shell
* feat: render release notes in client shell
* feat: render mobile switcher in client shell
* feat: render kitty graphics in client shell
* fix: preserve client shell lifecycle coherence
* refactor: route client shell commands through endpoint connections
* refactor: make the client-rendered shell the default
* refactor: remove monolithic launch mode
* refactor: complete client-rendered shell cutover
* perf: retain client pane surface updates
* perf: scale retained pane surface updates
* perf: remove remote pane input latency
* test: make client shell checks platform-independent
* fix: prevent client timer starvation
* test: fix client shell platform gates
* test: accept retained surfaces in cross-area checks
* test: make client mode assertions platform-neutral
* fix: badge only outdated integrations
Diffstat (limited to 'tests/server_headless.rs')
| -rw-r--r-- | tests/server_headless.rs | 205 |
1 files changed, 2 insertions, 203 deletions
diff --git a/tests/server_headless.rs b/tests/server_headless.rs index 5abb5ace..f6eb0771 100644 --- a/tests/server_headless.rs +++ b/tests/server_headless.rs @@ -1,6 +1,6 @@ //! Integration tests for headless server mode. -mod support; +pub mod support; use std::fs; use std::io::{BufRead, BufReader, Read, Write}; @@ -13,7 +13,7 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use portable_pty::{native_pty_system, Child, CommandBuilder, MasterPty, PtySize}; use support::{ - cleanup_test_base, register_runtime_dir, register_spawned_herdr_pid, + cleanup_test_base, client_handshake, register_runtime_dir, register_spawned_herdr_pid, unregister_spawned_herdr_pid, CURRENT_PROTOCOL, }; @@ -151,207 +151,6 @@ fn ping_socket(socket_path: &Path) -> String { response.trim().to_string() } -/// Sends a Hello message over the client socket and reads the Welcome response. -/// Uses bincode v2 wire format: [u32LE length][bincode payload] -/// bincode v2 standard config uses VarintEncoding: -/// - Integers < 251 are encoded as a single byte -/// - Enum variant index is encoded as u32 varint -/// - Option discriminant is always a single byte (0=None, 1=Some) -/// - String: length (varint) + UTF-8 bytes -fn client_handshake( - stream: &mut UnixStream, - version: u32, - cols: u16, - rows: u16, -) -> Result<(u32, Option<String>), String> { - stream - .set_read_timeout(Some(Duration::from_secs(5))) - .map_err(|e| e.to_string())?; - - // Encode Hello message using bincode v2 varint format. - // ClientMessage::Hello is variant 0. - let hello_payload = encode_varint_enum( - 0, - &[ - &encode_varint_u32(version), - &encode_varint_u16(cols), - &encode_varint_u16(rows), - &encode_varint_u32(8), // cell_width_px - &encode_varint_u32(16), // cell_height_px - &encode_varint_u32(0), // RenderEncoding::SemanticFrame - &encode_varint_u32(0), // ClientKeybindings::Server - &encode_varint_u32(0), // ClientLaunchMode::App - ], - ); - let framed = frame_message(&hello_payload); - stream.write_all(&framed).map_err(|e| e.to_string())?; - stream.flush().map_err(|e| e.to_string())?; - - // Read the framed response. - let mut len_buf = [0u8; 4]; - stream.read_exact(&mut len_buf).map_err(|e| e.to_string())?; - let len = u32::from_le_bytes(len_buf) as usize; - - if len > 2 * 1024 * 1024 { - return Err(format!("oversized response: {len}")); - } - - let mut payload = vec![0u8; len]; - stream.read_exact(&mut payload).map_err(|e| e.to_string())?; - - // Decode Welcome: ServerMessage variant 0 = Welcome { version: u32, error: Option<String> } - decode_welcome(&payload) -} - -/// Encode a varint u32 value according to bincode v2 VarintEncoding. -fn encode_varint_u32(v: u32) -> Vec<u8> { - if v < 251 { - vec![v as u8] - } else if v < 65536 { - let mut buf = vec![251u8]; - buf.extend_from_slice(&(v as u16).to_le_bytes()); - buf - } else { - let mut buf = vec![252u8]; - buf.extend_from_slice(&v.to_le_bytes()); - buf - } -} - -/// Encode a varint u16 value. -fn encode_varint_u16(v: u16) -> Vec<u8> { - if v < 251 { - vec![v as u8] - } else { - let mut buf = vec![251u8]; - buf.extend_from_slice(&v.to_le_bytes()); - buf - } -} - -/// Encode an enum variant with its fields. -fn encode_varint_enum(variant_idx: u32, fields: &[&[u8]]) -> Vec<u8> { - let mut buf = encode_varint_u32(variant_idx); - for field in fields { - buf.extend_from_slice(field); - } - buf -} - -/// Frame a message with u32LE length prefix. -fn frame_message(payload: &[u8]) -> Vec<u8> { - let len = payload.len() as u32; - let mut framed = len.to_le_bytes().to_vec(); - framed.extend_from_slice(payload); - framed -} - -/// Decode a varint u32 from a byte slice at the given offset. -/// Returns (value, bytes_consumed). -fn decode_varint_u32(payload: &[u8], offset: usize) -> Result<(u32, usize), String> { - if offset >= payload.len() { - return Err("payload too short for varint".into()); - } - let first_byte = payload[offset]; - match first_byte { - 0..=250 => Ok((first_byte as u32, 1)), - 251 => { - if offset + 3 > payload.len() { - return Err("payload too short for u16 varint".into()); - } - let v = u16::from_le_bytes( - payload[offset + 1..offset + 3] - .try_into() - .map_err(|e: std::array::TryFromSliceError| e.to_string())?, - ); - Ok((v as u32, 3)) - } - 252 => { - if offset + 5 > payload.len() { - return Err("payload too short for u32 varint".into()); - } - let v = u32::from_le_bytes( - payload[offset + 1..offset + 5] - .try_into() - .map_err(|e: std::array::TryFromSliceError| e.to_string())?, - ); - Ok((v, 5)) - } - _ => Err(format!("unsupported varint tag: {first_byte}")), - } -} - -/// Decode a varint u16 from a byte slice at the given offset. -#[allow(dead_code)] -fn decode_varint_u16(payload: &[u8], offset: usize) -> Result<(u16, usize), String> { - if offset >= payload.len() { - return Err("payload too short for varint".into()); - } - let first_byte = payload[offset]; - match first_byte { - 0..=250 => Ok((first_byte as u16, 1)), - 251 => { - if offset + 3 > payload.len() { - return Err("payload too short for u16 varint".into()); - } - let v = u16::from_le_bytes( - payload[offset + 1..offset + 3] - .try_into() - .map_err(|e: std::array::TryFromSliceError| e.to_string())?, - ); - Ok((v, 3)) - } - _ => Err(format!("unsupported varint tag for u16: {first_byte}")), - } -} - -/// Decode a ServerMessage::Welcome from bincode v2 payload. -fn decode_welcome(payload: &[u8]) -> Result<(u32, Option<String>), String> { - let mut offset = 0; - - // Variant index (should be 0 for Welcome) - let (variant, consumed) = decode_varint_u32(payload, offset)?; - offset += consumed; - if variant != 0 { - return Err(format!( - "expected Welcome (variant 0), got variant {variant}" - )); - } - - // version: u32 - let (version, consumed) = decode_varint_u32(payload, offset)?; - offset += consumed; - - // encoding: RenderEncoding - let (_encoding, consumed) = decode_varint_u32(payload, offset)?; - offset += consumed; - - // error: Option<String> — discriminant is always 1 byte - if offset >= payload.len() { - return Err("payload too short for Option tag".into()); - } - let option_tag = payload[offset]; - offset += 1; - - let error = if option_tag == 1 { - // Some(String) — length as varint + UTF-8 bytes - let (str_len, consumed) = decode_varint_u32(payload, offset)?; - offset += consumed; - let str_len = str_len as usize; - - if offset + str_len > payload.len() { - return Err("payload too short for string content".into()); - } - let s = String::from_utf8(payload[offset..offset + str_len].to_vec()) - .map_err(|e| e.to_string())?; - Some(s) - } else { - None - }; - - Ok((version, error)) -} - // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- |
