gbserver/gb-automation/src/process.rs

128 lines
3.5 KiB
Rust
Raw Normal View History

2024-12-22 20:56:52 -03:00
use std::{
2024-12-23 17:36:12 -03:00
path::{Path, PathBuf},
process::{Child, Command, Stdio},
2024-12-22 20:56:52 -03:00
};
use tokio::sync::Mutex;
2024-12-23 17:36:12 -03:00
use tracing::{error, instrument};
2024-12-22 20:56:52 -03:00
use uuid::Uuid;
2024-12-23 17:36:12 -03:00
use gb_core::{Error, Result};
#[derive(Debug)]
struct Process {
id: Uuid,
handle: Child,
}
2024-12-22 20:56:52 -03:00
pub struct ProcessAutomation {
working_dir: PathBuf,
processes: Mutex<Vec<Process>>,
}
impl ProcessAutomation {
2024-12-23 17:36:12 -03:00
pub fn new(working_dir: impl AsRef<Path>) -> Self {
2024-12-22 20:56:52 -03:00
Self {
2024-12-23 17:36:12 -03:00
working_dir: working_dir.as_ref().to_path_buf(),
2024-12-22 20:56:52 -03:00
processes: Mutex::new(Vec::new()),
}
}
#[instrument(skip(self, command))]
pub async fn execute(&self, command: &str, args: &[&str]) -> Result<String> {
let output = Command::new(command)
.args(args)
.current_dir(&self.working_dir)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
2024-12-23 00:54:50 -03:00
.map_err(|e| Error::internal(format!("Failed to execute command: {}", e)))?;
2024-12-22 20:56:52 -03:00
2024-12-23 17:36:12 -03:00
if !output.status.success() {
2024-12-22 20:56:52 -03:00
let error = String::from_utf8_lossy(&output.stderr);
2024-12-23 00:54:50 -03:00
return Err(Error::internal(format!("Command failed: {}", error)));
2024-12-22 20:56:52 -03:00
}
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
Ok(stdout)
}
pub async fn spawn(&self, command: &str, args: &[&str]) -> Result<Uuid> {
let child = Command::new(command)
.args(args)
.current_dir(&self.working_dir)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
2024-12-23 00:54:50 -03:00
.map_err(|e| Error::internal(format!("Failed to spawn process: {}", e)))?;
2024-12-22 20:56:52 -03:00
2024-12-23 17:36:12 -03:00
let id = Uuid::new_v4();
2024-12-22 20:56:52 -03:00
let mut processes = self.processes.lock().await;
2024-12-23 17:36:12 -03:00
processes.push(Process { id, handle: child });
Ok(id)
2024-12-22 20:56:52 -03:00
}
pub async fn kill(&self, id: Uuid) -> Result<()> {
let mut processes = self.processes.lock().await;
if let Some(index) = processes.iter().position(|p| p.id == id) {
2024-12-23 17:36:12 -03:00
let mut process = processes.remove(index);
2024-12-22 20:56:52 -03:00
process.handle.kill()
2024-12-23 00:54:50 -03:00
.map_err(|e| Error::internal(format!("Failed to kill process: {}", e)))?;
2024-12-23 17:36:12 -03:00
}
2024-12-22 20:56:52 -03:00
Ok(())
}
pub async fn cleanup(&self) -> Result<()> {
let mut processes = self.processes.lock().await;
for process in processes.iter_mut() {
if let Err(e) = process.handle.kill() {
error!("Failed to kill process {}: {}", process.id, e);
2024-12-23 17:36:12 -03:00
}
2024-12-22 20:56:52 -03:00
}
processes.clear();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
fn automation() -> ProcessAutomation {
let dir = tempdir().unwrap();
ProcessAutomation::new(dir.path())
}
#[tokio::test]
2024-12-23 17:36:12 -03:00
async fn test_execute() -> Result<()> {
let automation = automation();
2024-12-22 20:56:52 -03:00
let output = automation.execute("echo", &["Hello, World!"]).await?;
assert!(output.contains("Hello, World!"));
Ok(())
}
#[tokio::test]
2024-12-23 17:36:12 -03:00
async fn test_spawn_and_kill() -> Result<()> {
let automation = automation();
2024-12-22 20:56:52 -03:00
let id = automation.spawn("sleep", &["1"]).await?;
automation.kill(id).await?;
Ok(())
}
#[tokio::test]
2024-12-23 17:36:12 -03:00
async fn test_cleanup() -> Result<()> {
let automation = automation();
2024-12-22 20:56:52 -03:00
automation.spawn("sleep", &["1"]).await?;
automation.spawn("sleep", &["2"]).await?;
automation.cleanup().await?;
let processes = automation.processes.lock().await;
assert!(processes.is_empty());
Ok(())
2024-12-23 17:36:12 -03:00
}
2024-12-22 20:56:52 -03:00
}