From 5bd900d88c80d95f5b145397e45bbeea398b8165 Mon Sep 17 00:00:00 2001 From: JJ Liebig Date: Wed, 21 Oct 2026 23:28:59 +0200 Subject: [PATCH] test: synchronize shell and server startup checks (#5476) * test: wait for windows cwd commands to finish * test: wait for server readiness before sigterm --- src/platform/windows.rs | 54 +++++++++++++++++++++++++++++++--------- tests/server_headless.rs | 9 +++++++ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 454af47d6..062fa4834 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -4308,6 +4308,7 @@ mod tests { #[test] fn windows_process_cwd_reads_normalized_child_launch_directory() { + use std::io::{Read, Write}; use std::path::PathBuf; let name = format!("Herdr-Cwd-Case-{}", std::process::id()); @@ -4329,37 +4330,66 @@ mod tests { .args(["/D", "/Q", "/K"]) .current_dir(super::normalize_cwd_for_launch_platform(&launch_cwd)) .stdin(Stdio::piped()) - .stdout(Stdio::null()) + .stdout(Stdio::piped()) .stderr(Stdio::null()) .spawn() .expect("spawn cmd"); - let observe = |expected: &PathBuf| { - let deadline = Instant::now() + Duration::from_secs(5); + let mut stdout = super::ChildPipe::new_reader(child.stdout.take().unwrap()) + .expect("read cmd output"); + // The inherited cwd is readable before cmd is ready to execute input. + let mut wait_for = |marker: &[u8]| { + let deadline = Instant::now() + Duration::from_secs(15); + let mut output = Vec::new(); loop { - let observed = super::process_cwd(child.id()); - if observed.as_ref() == Some(expected) || Instant::now() >= deadline { - break observed; + let mut buffer = [0; 1024]; + match stdout.read(&mut buffer) { + Ok(0) => break, + Ok(len) => { + output.extend_from_slice(&buffer[..len]); + if output.windows(marker.len()).any(|part| part == marker) { + return Ok(()); + } + } + Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {} + Err(error) => return Err(error.to_string()), + } + if Instant::now() >= deadline { + break; } thread::sleep(Duration::from_millis(20)); } + Err(format!( + "cmd did not acknowledge {}: {}", + String::from_utf8_lossy(marker), + String::from_utf8_lossy(&output) + )) }; - let initial = observe(&cwd); - use std::io::Write; + writeln!(child.stdin.as_ref().unwrap(), "echo herdr-cwd-ready\r") + .expect("check cmd readiness"); + let ready = wait_for(b"herdr-cwd-ready"); + let initial = super::process_cwd(child.id()); writeln!( child.stdin.as_ref().unwrap(), - "cd /d \"{}\"\r", + "cd /d \"{}\" 2>&1 && echo herdr-cwd-changed\r", changed.display() ) .expect("change cmd cwd"); - let after_cd = observe(&changed); + let cd_completed = wait_for(b"herdr-cwd-changed"); + let after_cd = super::process_cwd(child.id()); let pane_cwd = super::pane_process_cwd(child.id()); let _ = child.kill(); let _ = child.wait(); - observations.push((shell, initial, after_cd, pane_cwd)); + observations.push((shell, ready, initial, cd_completed, after_cd, pane_cwd)); } let _ = fs::remove_dir_all(&cwd); - for (shell, initial, after_cd, pane_cwd) in observations { + for (shell, ready, initial, cd_completed, after_cd, pane_cwd) in observations { + assert!(ready.is_ok(), "{} readiness: {ready:?}", shell.display()); assert_eq!(initial, Some(cwd.clone()), "{} launch cwd", shell.display()); + assert!( + cd_completed.is_ok(), + "{} cd completion: {cd_completed:?}", + shell.display() + ); assert_eq!( after_cd, Some(changed.clone()), diff --git a/tests/server_headless.rs b/tests/server_headless.rs index 2f5cfb31d..b8aa8d9b1 100644 --- a/tests/server_headless.rs +++ b/tests/server_headless.rs @@ -596,6 +596,15 @@ fn server_survives_hangup_and_logs_why_it_stops() { ); assert!(ping_socket(&api_socket).contains("pong")); + // API ping works before the main loop installs its signal handlers. + wait_for_file(&client_socket, Duration::from_secs(10)); + let mut client = UnixStream::connect(&client_socket).expect("connect to ready server"); + let (version, error) = client_handshake(&mut client, CURRENT_PROTOCOL, 80, 24) + .expect("server event loop must be ready before SIGTERM"); + assert_eq!(version, CURRENT_PROTOCOL); + assert!(error.is_none()); + drop(client); + assert_eq!(unsafe { libc::kill(pid, libc::SIGTERM) }, 0); assert!( wait_for_exit(&mut spawned.child, Duration::from_secs(10)),