use std::ffi::OsStr;
use std::io::Read;
use std::os::unix::process::CommandExt;
use std::process::{Child, Command, Stdio};
use anyhow::{Result, bail};
pub struct Process {
pid: u32,
child: Option<Child>,
}
impl Process {
pub fn spawn<S: AsRef<OsStr>>(cmd: S) -> Result<Self> {
let mut command = Self::build_command::<S, _, S>(cmd, []);
Self::spawn_child_process(&mut command)
}
pub fn spawn_with_args<S, I, T>(cmd: S, args: I) -> Result<Self>
where
T: AsRef<OsStr>,
I: IntoIterator<Item = T>,
S: AsRef<OsStr>,
{
let mut command = Self::build_command(cmd, args);
Self::spawn_child_process(&mut command)
}
fn build_command<S, I, T>(cmd: S, args: I) -> Command
where
T: AsRef<OsStr>,
I: IntoIterator<Item = T>,
S: AsRef<OsStr>,
{
let mut command = Command::new(cmd);
command.args(args);
command
}
fn spawn_child_process(cmd: &mut Command) -> Result<Self> {
let mut child = cmd
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
unsafe {
child = child.pre_exec(|| {
libc::setsid();
Ok(())
});
}
let child_process = child.spawn()?;
let pid = child_process.id();
Ok(Self {
pid,
child: Some(child_process),
})
}
pub fn pid(&self) -> u32 {
self.pid
}
pub fn stdout(&mut self) -> Result<String> {
if let Some(ref mut child) = self.child
&& let Some(ref mut stdout) = child.stdout
{
let mut output = String::new();
stdout.read_to_string(&mut output)?;
return Ok(output);
}
Ok(String::new())
}
pub fn stderr(&mut self) -> Result<String> {
if let Some(ref mut child) = self.child
&& let Some(ref mut stderr) = child.stderr
{
let mut output = String::new();
stderr.read_to_string(&mut output)?;
return Ok(output);
}
Ok(String::new())
}
pub fn kill(&self) -> Result<()> {
match Command::new("kill").arg(self.pid().to_string()).status() {
Ok(status) => {
if status.success() {
return Ok(());
}
bail!("Failed to kill process with PID: {}", self.pid());
}
Err(e) => {
bail!("Error executing kill command: {}", e);
}
}
}
}
#[cfg(test)]
mod tests {
use std::thread;
use std::time::Duration;
use super::*;
#[test]
fn spawn_process() {
let process = Process::spawn("sleep").expect("Failed to spawn process");
assert!(process.pid() > 0);
thread::sleep(Duration::from_millis(100));
let result = process.kill();
assert!(result.is_ok(), "Failed to kill the process");
}
#[test]
fn spawn_process_with_args() {
let process = Process::spawn_with_args("sleep", ["1"]).expect("Failed to spawn process");
assert!(process.pid() > 0);
thread::sleep(Duration::from_millis(100));
let result = process.kill();
assert!(result.is_ok(), "Failed to kill the process");
}
#[test]
fn spawn_process_with_args_different_types() {
let process = Process::spawn_with_args("sleep", [String::from("1")])
.expect("Failed to spawn process");
assert!(process.pid() > 0);
thread::sleep(Duration::from_millis(100));
let result = process.kill();
assert!(result.is_ok(), "Failed to kill the process");
}
#[test]
fn capture_stdout() {
let mut process =
Process::spawn_with_args("echo", ["hello world"]).expect("Failed to spawn process");
thread::sleep(Duration::from_millis(100));
let stdout = process.stdout().expect("Failed to read stdout");
assert_eq!(stdout.trim(), "hello world");
process.kill().expect("Failed to kill process");
}
#[test]
fn capture_stderr() {
let mut process = Process::spawn_with_args("sh", ["-c", "echo error message >&2"])
.expect("Failed to spawn process");
thread::sleep(Duration::from_millis(100));
let stderr = process.stderr().expect("Failed to read stderr");
assert_eq!(stderr.trim(), "error message");
process.kill().expect("Failed to kill process");
}
#[test]
fn capture_both_stdout_and_stderr() {
let mut process =
Process::spawn_with_args("sh", ["-c", "echo stdout message; echo stderr message >&2"])
.expect("Failed to spawn process");
thread::sleep(Duration::from_millis(100));
let stdout = process.stdout().expect("Failed to read stdout");
let stderr = process.stderr().expect("Failed to read stderr");
assert_eq!(stdout.trim(), "stdout message");
assert_eq!(stderr.trim(), "stderr message");
process.kill().expect("Failed to kill process");
}
#[test]
fn capture_empty_stdout() {
let mut process = Process::spawn_with_args("true", Vec::<String>::new())
.expect("Failed to spawn process");
thread::sleep(Duration::from_millis(100));
let stdout = process.stdout().expect("Failed to read stdout");
assert_eq!(stdout, "");
process.kill().ok(); }
}