diff --git a/crates/pty_terminal/src/terminal.rs b/crates/pty_terminal/src/terminal.rs index 9e44a10c..98fb11e7 100644 --- a/crates/pty_terminal/src/terminal.rs +++ b/crates/pty_terminal/src/terminal.rs @@ -10,6 +10,8 @@ use portable_pty::{ChildKiller, ExitStatus, MasterPty}; use crate::geo::ScreenSize; +type ChildWaitResult = Result>; + /// The read half of a PTY connection. Implements [`Read`]. /// /// Reading feeds data through an internal vt100 parser (shared with [`PtyWriter`]), @@ -32,7 +34,7 @@ pub struct PtyWriter { /// A cloneable handle to a child process spawned in a PTY. pub struct ChildHandle { child_killer: Box, - exit_status: Arc>, + exit_status: Arc>, } impl Clone for ChildHandle { @@ -221,9 +223,15 @@ impl PtyWriter { impl ChildHandle { /// Blocks until the child process has exited and returns its exit status. - #[must_use] - pub fn wait(&self) -> ExitStatus { - self.exit_status.wait().clone() + /// + /// # Errors + /// + /// Returns an error if waiting for the child process exit status fails. + pub fn wait(&self) -> anyhow::Result { + match self.exit_status.wait() { + Ok(status) => Ok(status.clone()), + Err(error) => Err(anyhow::Error::new(Arc::clone(error))), + } } /// Kills the child process. @@ -263,17 +271,14 @@ impl Terminal { let child_killer = child.clone_killer(); drop(pty_pair.slave); // Critical: drop slave so EOF is signaled when child exits let master = pty_pair.master; - let exit_status: Arc> = Arc::new(OnceLock::new()); + let exit_status: Arc> = Arc::new(OnceLock::new()); // Background thread: wait for child to exit, set exit status, then close writer to trigger EOF thread::spawn({ let writer = Arc::clone(&writer); let exit_status = Arc::clone(&exit_status); move || { - // Wait for child and set exit status - if let Ok(status) = child.wait() { - let _ = exit_status.set(status); - } + let _ = exit_status.set(child.wait().map_err(Arc::new)); // Close writer to signal EOF to the reader *writer.lock().unwrap() = None; } diff --git a/crates/pty_terminal/tests/terminal.rs b/crates/pty_terminal/tests/terminal.rs index 76435d91..44489124 100644 --- a/crates/pty_terminal/tests/terminal.rs +++ b/crates/pty_terminal/tests/terminal.rs @@ -20,7 +20,7 @@ fn is_terminal() { Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); let mut discard = Vec::new(); pty_reader.read_to_end(&mut discard).unwrap(); - let _ = child_handle.wait(); + let _ = child_handle.wait().unwrap(); let output = pty_reader.screen_contents(); assert_eq!(output.trim(), "true true true"); } @@ -47,7 +47,7 @@ fn write_basic_echo() { let mut discard = Vec::new(); pty_reader.read_to_end(&mut discard).unwrap(); - let _ = child_handle.wait(); + let _ = child_handle.wait().unwrap(); let output = pty_reader.screen_contents(); // PTY echoes the input, so we see "hello world\nhello world" @@ -98,7 +98,7 @@ fn write_multiple_lines() { let mut discard = Vec::new(); pty_reader.read_to_end(&mut discard).unwrap(); - let _ = child_handle.wait(); + let _ = child_handle.wait().unwrap(); let output = pty_reader.screen_contents(); // PTY echoes input, then child prints "Echo: {line}\n" for each @@ -119,7 +119,7 @@ fn write_after_exit() { // Read all output - this blocks until child exits and EOF is reached let mut discard = Vec::new(); pty_reader.read_to_end(&mut discard).unwrap(); - let _ = child_handle.wait(); + let _ = child_handle.wait().unwrap(); // Writer shutdown is done by a background thread after child wait returns. // Poll briefly for the writer state to flip to closed before asserting write failure. @@ -165,7 +165,7 @@ fn write_interactive_prompt() { let mut discard = Vec::new(); pty_reader.read_to_end(&mut discard).unwrap(); - let _ = child_handle.wait(); + let _ = child_handle.wait().unwrap(); let output = pty_reader.screen_contents(); assert_eq!(output.trim(), "Name: Alice\nHello, Alice"); @@ -346,7 +346,7 @@ fn read_to_end_returns_exit_status_success() { Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); let mut discard = Vec::new(); pty_reader.read_to_end(&mut discard).unwrap(); - let status = child_handle.wait(); + let status = child_handle.wait().unwrap(); assert!(status.success()); assert_eq!(status.exit_code(), 0); } @@ -362,7 +362,7 @@ fn read_to_end_returns_exit_status_nonzero() { Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); let mut discard = Vec::new(); pty_reader.read_to_end(&mut discard).unwrap(); - let status = child_handle.wait(); + let status = child_handle.wait().unwrap(); assert!(!status.success()); assert_eq!(status.exit_code(), 42); } diff --git a/crates/pty_terminal_test/src/lib.rs b/crates/pty_terminal_test/src/lib.rs index 3f53c69d..187cbac3 100644 --- a/crates/pty_terminal_test/src/lib.rs +++ b/crates/pty_terminal_test/src/lib.rs @@ -91,10 +91,14 @@ impl Reader { /// Reads all remaining PTY output until the child exits, then returns the exit status. /// + /// # Errors + /// + /// Returns an error if waiting for the child process exit status fails. + /// /// # Panics /// /// Panics if reading from the PTY fails. - pub fn wait_for_exit(&mut self) -> ExitStatus { + pub fn wait_for_exit(&mut self) -> anyhow::Result { let mut discard = Vec::new(); self.pty.read_to_end(&mut discard).expect("PTY read_to_end failed"); self.child_handle.wait() diff --git a/crates/pty_terminal_test/tests/milestone.rs b/crates/pty_terminal_test/tests/milestone.rs index 234519bf..e878e069 100644 --- a/crates/pty_terminal_test/tests/milestone.rs +++ b/crates/pty_terminal_test/tests/milestone.rs @@ -67,7 +67,7 @@ fn milestone_raw_mode_keystrokes() { // Write 'q' to quit and wait for the child to exit writer.write_all(b"q").unwrap(); writer.flush().unwrap(); - let status = reader.wait_for_exit(); + let status = reader.wait_for_exit().unwrap(); assert!(status.success()); } @@ -122,6 +122,6 @@ fn milestone_does_not_pollute_screen() { writer.write_all(b"q").unwrap(); writer.flush().unwrap(); - let status = reader.wait_for_exit(); + let status = reader.wait_for_exit().unwrap(); assert!(status.success()); } diff --git a/crates/vite_task_bin/tests/e2e_snapshots/main.rs b/crates/vite_task_bin/tests/e2e_snapshots/main.rs index 07e65543..b9c88af5 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/main.rs +++ b/crates/vite_task_bin/tests/e2e_snapshots/main.rs @@ -391,7 +391,7 @@ fn run_case_inner(tmpdir: &AbsolutePath, fixture_path: &std::path::Path, fixture } } - let status = terminal.reader.wait_for_exit(); + let status = terminal.reader.wait_for_exit().unwrap(); let screen = terminal.reader.screen_contents(); {