test: readiness signals replace sleep-based timing (N4)
- signal tests use a marker-file readiness signal: the child's command is 'echo ready > <marker>; exec sleep 60', the test polls wait_for_file(marker, 5s) — marker exists = the shell exec'd, so the signal lands on the real target regardless of machine load. Applied in tests/pty.rs (both signal tests), tests/pipe.rs (SIGTERM), and the src/local unit tests; wait_for_file lives in tests/common. - cancel-cleanup post-action sleeps became bounded polls for the child's death (kill(pid,0) -> ESRCH, 5s deadline) — faster and flake-proof in both directions. - resize/cat-stdin tests need no readiness signal at all: the adapter's input pump processes chunks in order — the sleeps there were pure latency (integration suites now ~40ms, was 200-270ms).
This commit is contained in:
1 parent
a73484203e
commit
cdd6893046
6 files changed
+241
-61
No files matched your search
+25
-8
@@ -77,18 +77,31 @@ async fn pipe_separate_stderr() {
|
||||
let _ = server.await;
|
||||
}
|
||||
|
||||
/// 11. Signal (SIGTERM, Unix): negotiate `cmd:["sleep","60"]`, send
|
||||
/// 11. Signal (SIGTERM, Unix): negotiate `sh -c 'echo ready > <marker>;
|
||||
/// exec sleep 60'`, wait for the marker (the child has exec'd), send
|
||||
/// `signal:"TERM"`, await exit, assert signal-terminated.
|
||||
#[cfg(unix)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn pipe_signal_sigterm_kills_child() {
|
||||
let marker = std::env::temp_dir().join(format!(
|
||||
"alktty_pipe_sigterm_ready_{}_{}.txt",
|
||||
std::process::id(),
|
||||
nanos_seed()
|
||||
));
|
||||
let cmd = format!("echo ready > '{}'; exec sleep 60", marker.display());
|
||||
let backend = Arc::new(LocalTtyBackend::new());
|
||||
let (mut client, server) = spawn_session("local", backend);
|
||||
client
|
||||
.write_negotiation(negotiate_pipe_json("local", &["sleep", "60"]).as_str())
|
||||
.write_negotiation(negotiate_pipe_json("local", &["sh", "-c", cmd.as_str()]).as_str())
|
||||
.await;
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(150)).await;
|
||||
// Readiness signal (N4): the child exec'd `sleep` once the marker
|
||||
// file exists — no fixed sleep.
|
||||
assert!(
|
||||
common::wait_for_file(&marker, Duration::from_secs(5)).await,
|
||||
"child never became ready"
|
||||
);
|
||||
let _ = std::fs::remove_file(&marker);
|
||||
|
||||
client
|
||||
.write_control(br#"{"type":"signal","name":"TERM"}"#)
|
||||
@@ -133,19 +146,23 @@ async fn pipe_cancel_cleanup_kills_child_no_orphan() {
|
||||
let pid: i32 = pid_str.trim().parse().expect("pid parses");
|
||||
let _ = std::fs::remove_file(&pid_file);
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(150)).await;
|
||||
|
||||
drop(client);
|
||||
server.abort();
|
||||
let _ = server.await;
|
||||
|
||||
// Poll for the child's death (the pid file is written before exec,
|
||||
// so the kill lands on the exec'd process; bounded, no fixed sleep).
|
||||
let mut alive = true;
|
||||
for _ in 0..100 {
|
||||
let deadline = tokio::time::Instant::now() + Duration::from_secs(5);
|
||||
while alive {
|
||||
let r = unsafe { libc::kill(pid, 0) };
|
||||
if r != 0 && std::io::Error::last_os_error().raw_os_error() == Some(libc::ESRCH) {
|
||||
alive = false;
|
||||
break;
|
||||
}
|
||||
if tokio::time::Instant::now() >= deadline {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
}
|
||||
assert!(!alive, "child (pid={pid}) should be killed after cancel");
|
||||
@@ -164,8 +181,8 @@ async fn pipe_resize_noop() {
|
||||
.write_negotiation(negotiate_pipe_json("local", &["cat"]).as_str())
|
||||
.await;
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(150)).await;
|
||||
|
||||
// No readiness sleep needed (N4): the adapter's input pump
|
||||
// processes chunks in order, and PipeControl::resize is a no-op.
|
||||
client
|
||||
.write_control(br#"{"type":"resize","cols":120,"rows":40}"#)
|
||||
.await;
|
||||
|
||||
Reference in new issue
Block a user