use bytes::Bytes;
use std::process::Stdio;
use tokio::io::AsyncReadExt;
use tokio::process::Command;
pub struct RawResult {
pub stdout: Bytes,
pub stderr: Bytes,
pub stdout_truncated: bool,
pub stderr_truncated: bool,
pub exit_code: Option<i32>,
pub timed_out: bool,
}
pub async fn drain_with_cap<R: tokio::io::AsyncRead + Unpin>(r: R, cap: usize) -> (Bytes, bool) {
let mut buf = Vec::with_capacity(cap.min(8192));
let mut reader = r;
let mut truncated = false;
let mut tmp = [0u8; 8192];
loop {
match reader.read(&mut tmp).await {
Ok(0) => break,
Ok(n) => {
if !truncated && buf.len() + n <= cap {
buf.extend_from_slice(&tmp[..n]);
} else {
truncated = true;
}
}
Err(_) => break,
}
}
(Bytes::from(buf), truncated)
}
pub fn spawn(
exe: &std::path::Path,
args: &[String],
env: &std::collections::HashMap<String, String>,
cwd: &std::path::Path,
) -> std::io::Result<tokio::process::Child> {
let mut cmd = Command::new(exe);
cmd.args(args)
.current_dir(cwd)
.env_clear()
.envs(env.iter())
.kill_on_drop(true)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(unix)]
{
cmd.process_group(0);
}
cmd.spawn()
}
pub fn kill_tree(child: &tokio::process::Child) {
let Some(pid) = child.id() else { return };
#[cfg(unix)]
{
unsafe {
libc::kill(-(pid as i32), libc::SIGKILL);
}
}
#[cfg(not(unix))]
{
let _ = child.start_kill();
}
}