use std::time::{Duration, Instant};
use processkit::{Outcome, ProcessGroup, wait_any};
use crate::common::*;
#[tokio::test]
#[ignore = "spawns real subprocesses and races their exits"]
async fn wait_any_returns_first_finisher() {
let group = ProcessGroup::new().expect("create group");
let mut slow = group.start(&sleep_secs(15)).await.expect("start slow");
let mut fast = group.start(&sleep_secs(1)).await.expect("start fast");
let (idx, outcome) = tokio::time::timeout(
Duration::from_secs(10),
wait_any(&mut [&mut slow, &mut fast]),
)
.await
.expect("race finished in time")
.expect("race");
assert_eq!(idx, 1, "the 1-second sleeper should finish first");
assert_eq!(
outcome,
Outcome::Exited(0),
"the fast sleeper exits cleanly"
);
}
#[tokio::test]
#[ignore = "spawns real subprocesses; proves the race loser stays usable"]
async fn wait_any_losers_still_waitable() {
let group = ProcessGroup::new().expect("create group");
let mut slow = group.start(&sleep_secs(30)).await.expect("start slow");
let mut fast = group.start(&sleep_secs(1)).await.expect("start fast");
let (idx, _outcome) = tokio::time::timeout(
Duration::from_secs(10),
wait_any(&mut [&mut slow, &mut fast]),
)
.await
.expect("race finished in time")
.expect("race");
assert_eq!(idx, 1);
slow.start_kill().expect("kill the loser");
let start = Instant::now();
let _ = tokio::time::timeout(Duration::from_secs(10), slow.wait())
.await
.expect("loser reaped in time")
.expect("wait");
assert!(
start.elapsed() < Duration::from_secs(5),
"loser wait was not prompt (took {:?})",
start.elapsed()
);
}
#[tokio::test]
#[ignore = "spawns real subprocesses; proves a keep_stdin_open loser keeps its stdin (B15)"]
async fn wait_any_loser_keeps_its_stdin() {
let group = ProcessGroup::new().expect("create group");
let mut waiter = group
.start(&first_line_consumer().keep_stdin_open())
.await
.expect("start waiter");
let mut fast = group.start(&sleep_secs(1)).await.expect("start fast");
let (idx, _) = tokio::time::timeout(
Duration::from_secs(10),
wait_any(&mut [&mut waiter, &mut fast]),
)
.await
.expect("race finished in time")
.expect("race");
assert_eq!(idx, 1, "the fast sleeper wins the race");
let mut stdin = waiter
.take_stdin()
.expect("B15: a wait_any loser's stdin must remain usable");
let _ = stdin.write_line("hello").await;
let _ = stdin.finish().await;
let outcome = tokio::time::timeout(Duration::from_secs(10), waiter.wait())
.await
.expect("loser reaped in time")
.expect("wait");
assert!(matches!(outcome, Outcome::Exited(0)), "got {outcome:?}");
}
#[tokio::test]
#[ignore = "spawns a long-lived subprocess and kills it early"]
async fn start_kill_terminates_a_running_process() {
let mut process = sleeper().start().await.expect("start sleeper");
assert!(process.pid().is_some());
process.start_kill().expect("start_kill");
let start = Instant::now();
let _ = tokio::time::timeout(Duration::from_secs(10), process.wait())
.await
.expect("killed process should be reaped promptly")
.expect("wait");
assert!(
start.elapsed() < Duration::from_secs(5),
"kill was not prompt (took {:?})",
start.elapsed()
);
}
#[tokio::test]
#[ignore = "spawns a real subprocess; D20 start_kill is idempotent after reap"]
async fn start_kill_is_idempotent_after_the_child_is_reaped() {
let group = ProcessGroup::new().expect("create group");
let mut quick = group
.start(&two_line_echo())
.await
.expect("start quick child");
let (idx, _) = tokio::time::timeout(Duration::from_secs(10), wait_any(&mut [&mut quick]))
.await
.expect("child reaped in time")
.expect("wait_any");
assert_eq!(idx, 0);
quick
.start_kill()
.expect("start_kill on a reaped child must be an idempotent Ok (D20)");
quick.start_kill().expect("a second start_kill is also Ok");
}