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}