use std::io::Read;
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
pub type SharedBuf = Arc<Mutex<String>>;
pub fn drain_to_buffer<R: Read + Send + 'static>(mut reader: R, buffer: SharedBuf) {
let mut chunk = [0u8; 4096];
loop {
match reader.read(&mut chunk) {
Ok(0) => return, Ok(n) => {
let text = String::from_utf8_lossy(&chunk[..n]);
let mut guard = buffer.lock().expect("buffer lock poisoned");
guard.push_str(&text);
}
Err(e) => {
eprintln!("reader thread io error: {e}");
return;
}
}
}
}
pub struct KillOnDrop(pub Child);
impl Drop for KillOnDrop {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
impl Deref for KillOnDrop {
type Target = Child;
fn deref(&self) -> &Child {
&self.0
}
}
impl DerefMut for KillOnDrop {
fn deref_mut(&mut self) -> &mut Child {
&mut self.0
}
}
pub fn wait_for_marker(
child: &mut Child,
buffers: &[SharedBuf],
marker: &str,
timeout: Duration,
) -> bool {
let start = Instant::now();
let step = Duration::from_millis(25);
loop {
if buffers
.iter()
.any(|buf| buf.lock().expect("buffer lock poisoned").contains(marker))
{
return true;
}
if start.elapsed() >= timeout {
return false;
}
if let Ok(Some(_)) = child.try_wait() {
return false;
}
thread::sleep(step);
}
}
pub fn send_term(child: &Child) {
let status = Command::new("kill")
.arg("-TERM")
.arg(child.id().to_string())
.status()
.expect("failed to spawn `kill -TERM`");
assert!(
status.success(),
"`kill -TERM` returned non-zero: {status:?}"
);
}
#[allow(dead_code)]
pub fn spawn_camel_run(dir: &Path) -> KillOnDrop {
let config_path = dir.join("Camel.toml");
let child = Command::new(env!("CARGO_BIN_EXE_camel"))
.arg("run")
.arg("--config")
.arg(&config_path)
.current_dir(dir)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null())
.spawn()
.expect("failed to spawn `camel` binary"); KillOnDrop(child)
}
#[allow(dead_code)]
pub fn wait_exit_bounded(child: &mut Child, timeout: Duration) -> bool {
let start = Instant::now();
let step = Duration::from_millis(25);
loop {
match child.try_wait() {
Ok(Some(_)) => return true,
Ok(None) => {
if start.elapsed() >= timeout {
let _ = child.kill();
let _ = child.wait();
return false;
}
thread::sleep(step);
}
Err(e) => panic!("try_wait failed: {e}"),
}
}
}