#![allow(clippy::missing_panics_doc)]
#![cfg(unix)]
use std::os::unix::process::ExitStatusExt;
use std::time::Duration;
use kawauso_process::Invocation;
use kawauso_process::Run;
use kawauso_process::error::RunCommandError;
use tokio::time::timeout;
const BOUND: Duration = Duration::from_secs(5);
const FLOOD: usize = 256 * 1024;
const GRACE: Duration = Duration::from_secs(30);
const HOLD: u64 = 60;
async fn ready(script: &str) -> Result<Run, RunCommandError> {
let mut run = Invocation::new("sh").arg("-c").arg(script).start()?;
while let Some(line) = run.next_line().await? {
if line.text().get() == "ready" {
break;
}
}
Ok(run)
}
fn script(handler: &str) -> String {
format!("trap '{handler}' TERM; echo ready; while :; do sleep 0.05; done")
}
fn script_that_starts_a_program(handler: &str) -> String {
format!("trap '{handler}' TERM; sleep {HOLD} & echo ready; wait")
}
#[tokio::test]
async fn stop_after_a_dropped_wait_for_the_end_asks_the_command_to_end() {
let mut run = ready(&script("echo stopped; exit 0")).await.unwrap();
let dropped = timeout(Duration::from_millis(100), run.wait_for_end()).await;
let execution = run.stop(GRACE).await.unwrap();
assert_eq!(
(
dropped.is_err(),
execution.stdout().to_string_lossy().contains("stopped")
),
(true, true)
);
}
#[tokio::test]
async fn stop_after_a_wait_for_the_end_reports_the_status() {
let mut run = Invocation::new("sh")
.arg("-c")
.arg("exit 7")
.start()
.unwrap();
run.wait_for_end().await.unwrap();
let execution = run.stop(GRACE).await.unwrap();
assert_eq!(execution.status().code(), Some(7));
}
#[tokio::test]
async fn stop_asks_the_command_to_end() {
let run = ready(&script("echo stopped; exit 0")).await.unwrap();
let execution = run.stop(GRACE).await.unwrap();
assert!(execution.stdout().to_string_lossy().contains("stopped"));
}
#[tokio::test]
async fn stop_ends_while_a_program_of_the_command_holds_the_streams() {
let run = ready(&script_that_starts_a_program("exit 0"))
.await
.unwrap();
let stopped = timeout(BOUND, run.stop(GRACE)).await;
assert!(stopped.is_ok());
}
#[tokio::test]
async fn stop_kills_a_command_that_ignores_the_request() {
let run = ready(&script("")).await.unwrap();
let execution = run.stop(Duration::from_millis(100)).await.unwrap();
assert_eq!(execution.status().signal(), Some(9));
}
#[tokio::test]
async fn stop_lets_a_command_that_answers_the_request_end() {
let run = ready(&script("exit 3")).await.unwrap();
let execution = run.stop(GRACE).await.unwrap();
assert_eq!(execution.status().code(), Some(3));
}
#[tokio::test]
async fn stop_reads_the_streams_of_a_command_that_fills_both_pipes() {
let handler = format!("head -c {FLOOD} /dev/zero; head -c {FLOOD} /dev/zero >&2; exit 0");
let run = ready(&script(&handler)).await.unwrap();
let execution = run.stop(GRACE).await.unwrap();
assert_eq!(
(
execution.stdout().get().len(),
execution.stderr().get().len()
),
(FLOOD + "ready\n".len(), FLOOD)
);
}
#[tokio::test]
async fn stop_reports_the_capture_of_the_run() {
let run = ready(&script("exit 0")).await.unwrap();
let execution = run.stop(GRACE).await.unwrap();
assert_eq!(execution.stdout().to_string_lossy(), "ready\n");
}
#[tokio::test]
async fn stop_takes_the_last_output_of_a_command_whose_streams_stay_open() {
let run = ready(&script_that_starts_a_program("echo stopped; exit 0"))
.await
.unwrap();
let execution = timeout(BOUND, run.stop(GRACE)).await.unwrap().unwrap();
assert!(execution.stdout().to_string_lossy().contains("stopped"));
}
#[tokio::test]
async fn stop_of_a_command_that_already_ended_reports_its_status() {
let mut run = Invocation::new("sh")
.arg("-c")
.arg("exit 7")
.start()
.unwrap();
while run.next_line().await.unwrap().is_some() {}
let execution = run.stop(GRACE).await.unwrap();
assert_eq!(execution.status().code(), Some(7));
}