feat(test): own process startup and cleanup

This commit is contained in:
slhx agent
2026-07-13 10:18:01 +02:00
parent 6b4bb97aaa
commit 7259dd6e33
5 changed files with 108 additions and 28 deletions
+57
View File
@@ -2,6 +2,63 @@ use hemx_core::{
Atom, BuildFingerprint, Effect, EffectBatch, Form, GeneratedTarget, IntoEffect, KeyedSlot,
NavigateMode, Payload, ResourceId, ResourceKind, ResourceRef, ScopeKey, Slot,
};
use std::io;
use std::net::TcpStream;
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};
/// A child process owned by an integration test and proven ready over TCP.
///
/// The process is killed and reaped on every return path, including panics. Startup failures name
/// the process and address and distinguish early exit from a readiness timeout.
/// req: test/019
pub struct TestProcess {
child: Child,
}
impl TestProcess {
pub fn start(
mut command: Command,
label: impl Into<String>,
addr: &str,
timeout: Duration,
) -> io::Result<Self> {
let label = label.into();
let child = command
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map_err(|error| {
io::Error::new(error.kind(), format!("failed to spawn {label}: {error}"))
})?;
let mut process = Self { child };
let deadline = Instant::now() + timeout;
loop {
if TcpStream::connect(addr).is_ok() {
return Ok(process);
}
if let Some(status) = process.child.try_wait()? {
return Err(io::Error::other(format!(
"{label} exited with {status} before listening on {addr}"
)));
}
if Instant::now() >= deadline {
return Err(io::Error::new(
io::ErrorKind::TimedOut,
format!("timed out after {timeout:?} waiting for {label} to listen on {addr}"),
));
}
std::thread::sleep(Duration::from_millis(25));
}
}
}
impl Drop for TestProcess {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
pub fn run<I, F, R>(handler: F, input: I) -> EffectInspector
where