feat(test): own process startup and cleanup
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
use hemx_test::TestProcess;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn process_harness_reports_early_exit_with_context() {
|
||||
// req: test/019
|
||||
let mut command = Command::new(std::env::current_exe().expect("current test executable"));
|
||||
command
|
||||
.arg("--exact")
|
||||
.arg("helper_process_exits_successfully")
|
||||
.arg("--nocapture");
|
||||
|
||||
let error = match TestProcess::start(
|
||||
command,
|
||||
"short-lived helper",
|
||||
"127.0.0.1:9",
|
||||
Duration::from_secs(2),
|
||||
) {
|
||||
Ok(_) => panic!("a process that exits before readiness must fail startup"),
|
||||
Err(error) => error,
|
||||
};
|
||||
let message = error.to_string();
|
||||
|
||||
assert!(message.contains("short-lived helper"), "{message}");
|
||||
assert!(message.contains("127.0.0.1:9"), "{message}");
|
||||
assert!(message.contains("exited with"), "{message}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn helper_process_exits_successfully() {}
|
||||
Reference in New Issue
Block a user