#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::sync::Arc;
use std::time::{Duration, Instant};
use kaish_kernel::{Kernel, KernelConfig};
async fn setup() -> Arc<Kernel> {
Kernel::new(KernelConfig::isolated())
.expect("failed to create kernel")
.into_arc()
}
async fn wait_done(kernel: &Kernel, job_id: u64) -> String {
let deadline = Instant::now() + Duration::from_secs(10);
loop {
let status = kernel
.execute(&format!("cat /v/jobs/{job_id}/status"))
.await
.expect("status check")
.text_out()
.trim()
.to_string();
if !status.starts_with("running") {
return status;
}
assert!(Instant::now() < deadline, "job {job_id} never finished");
tokio::time::sleep(Duration::from_millis(20)).await;
}
}
async fn job_stderr(kernel: &Kernel, job_id: u64) -> String {
kernel
.execute(&format!("cat /v/jobs/{job_id}/stderr"))
.await
.expect("read job stderr")
.text_out()
.into_owned()
}
#[tokio::test]
async fn substitution_stderr_reaches_the_job_stream() {
let k = setup().await;
let r = k.execute("echo $(cat /nope) &").await.expect("execute");
let job_id = parse_job_id(&r);
let status = wait_done(&k, job_id).await;
assert!(status.starts_with("done"), "job status: {status}");
let stderr = job_stderr(&k, job_id).await;
assert!(
stderr.contains("/nope"),
"the substitution's stderr must reach the job's stderr stream, got {stderr:?}"
);
}
#[tokio::test]
async fn intermediate_stage_stderr_reaches_the_job_stream() {
let k = setup().await;
let r = k.execute("cat /nope | head &").await.expect("execute");
let job_id = parse_job_id(&r);
wait_done(&k, job_id).await;
let stderr = job_stderr(&k, job_id).await;
assert!(
stderr.contains("/nope"),
"the first stage's stderr must reach the job's stderr stream, got {stderr:?}"
);
}
#[tokio::test]
async fn announcement_rides_stderr_and_ends_its_line() {
let k = setup().await;
let r = k.execute("echo hi &").await.expect("execute");
assert!(
r.text_out().is_empty(),
"the announcement is not command output, got stdout {:?}",
r.text_out()
);
assert!(
r.err.starts_with('[') && r.err.ends_with('\n') && !r.err.ends_with("\n\n"),
"the announcement must ride stderr and end exactly one line, got {:?}",
r.err
);
}
fn parse_job_id(result: &kaish_kernel::interpreter::ExecResult) -> u64 {
let text = result.err.trim();
assert!(
text.starts_with('[') && text.ends_with(']'),
"expected a `[N]` announcement on stderr, got {text:?}"
);
text[1..text.len() - 1].parse().expect("job id is a number")
}