agtrace_testing/process.rs
1//! Background process management for long-running commands.
2//!
3//! Provides utilities for:
4//! - Starting watch commands in the background
5//! - Monitoring process output
6//! - Gracefully terminating processes
7
8use std::process::{Child, ChildStdout, Command, Stdio};
9use std::time::Duration;
10
11/// A background process handle.
12pub struct BackgroundProcess {
13 child: Child,
14}
15
16impl BackgroundProcess {
17 /// Spawn a new background process.
18 pub fn spawn(mut command: Command) -> std::io::Result<Self> {
19 let child = command.spawn()?;
20 Ok(Self { child })
21 }
22
23 /// Spawn a new background process with piped stdout and stderr.
24 ///
25 /// This allows reading the process output while it's running.
26 ///
27 /// # Example
28 /// ```no_run
29 /// # use std::process::Command;
30 /// # use agtrace_testing::process::BackgroundProcess;
31 /// # use std::io::{BufRead, BufReader};
32 /// let mut cmd = Command::new("agtrace");
33 /// cmd.args(&["watch"]);
34 ///
35 /// let mut proc = BackgroundProcess::spawn_piped(cmd).unwrap();
36 ///
37 /// // Read output
38 /// if let Some(stdout) = proc.stdout() {
39 /// let reader = BufReader::new(stdout);
40 /// for line in reader.lines() {
41 /// println!("Output: {:?}", line);
42 /// }
43 /// }
44 /// ```
45 pub fn spawn_piped(mut command: Command) -> std::io::Result<Self> {
46 command.stdout(Stdio::piped());
47 command.stderr(Stdio::piped());
48 let child = command.spawn()?;
49 Ok(Self { child })
50 }
51
52 /// Wait for the process to exit with a timeout.
53 pub fn wait_timeout(
54 &mut self,
55 timeout: Duration,
56 ) -> std::io::Result<Option<std::process::ExitStatus>> {
57 // Simple polling implementation
58 let start = std::time::Instant::now();
59 loop {
60 match self.child.try_wait()? {
61 Some(status) => return Ok(Some(status)),
62 None => {
63 if start.elapsed() > timeout {
64 return Ok(None);
65 }
66 std::thread::sleep(Duration::from_millis(100));
67 }
68 }
69 }
70 }
71
72 /// Kill the process.
73 pub fn kill(&mut self) -> std::io::Result<()> {
74 self.child.kill()
75 }
76
77 /// Get the process ID.
78 pub fn id(&self) -> u32 {
79 self.child.id()
80 }
81
82 /// Get mutable access to the process's stdout.
83 ///
84 /// Returns `None` if stdout was not captured (process must be spawned with `spawn_piped`).
85 ///
86 /// # Example
87 /// ```no_run
88 /// # use std::process::Command;
89 /// # use agtrace_testing::process::BackgroundProcess;
90 /// # use std::io::{BufRead, BufReader};
91 /// let mut cmd = Command::new("agtrace");
92 /// let mut proc = BackgroundProcess::spawn_piped(cmd).unwrap();
93 ///
94 /// if let Some(stdout) = proc.stdout() {
95 /// let reader = BufReader::new(stdout);
96 /// for line in reader.lines().take(5) {
97 /// println!("{:?}", line);
98 /// }
99 /// }
100 /// ```
101 pub fn stdout(&mut self) -> Option<&mut ChildStdout> {
102 self.child.stdout.as_mut()
103 }
104
105 /// Get mutable access to the process's stderr.
106 ///
107 /// Returns `None` if stderr was not captured (process must be spawned with `spawn_piped`).
108 pub fn stderr(&mut self) -> Option<&mut std::process::ChildStderr> {
109 self.child.stderr.as_mut()
110 }
111}
112
113impl Drop for BackgroundProcess {
114 fn drop(&mut self) {
115 // Ensure process is killed when dropped
116 let _ = self.child.kill();
117 }
118}