#![allow(clippy::expect_used)]
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use kaish_kernel::{ExecuteOptions, Kernel, KernelConfig};
fn kernel() -> Arc<Kernel> {
Kernel::new(KernelConfig::isolated())
.expect("kernel")
.into_arc()
}
#[tokio::test]
async fn interrupt_stops_loop_preserving_state() {
let k = kernel();
k.execute("BEFORE=kept").await.expect("set var");
let polls = Arc::new(AtomicU32::new(0));
let seen = polls.clone();
let opts = ExecuteOptions::new()
.with_interrupt(Arc::new(move || seen.fetch_add(1, Ordering::Relaxed) >= 3));
let r = k
.execute_with_options("while true; do DURING=also-kept; done", opts)
.await
.expect("interrupted execute returns a result, not an error");
assert_eq!(r.code, 130, "interrupt maps to SIGINT-style exit 130");
assert!(polls.load(Ordering::Relaxed) >= 3, "check was actually polled");
let r = k.execute("echo \"$BEFORE/$DURING\"").await.expect("echo");
assert_eq!(r.text_out().trim(), "kept/also-kept");
}
#[tokio::test]
async fn inert_interrupt_is_invisible() {
let k = kernel();
let opts = ExecuteOptions::new().with_interrupt(Arc::new(|| false));
let r = k.execute_with_options("echo hello", opts).await.expect("echo");
assert_eq!(r.code, 0);
assert_eq!(r.text_out().trim(), "hello");
}
#[tokio::test]
async fn interrupt_does_not_leak_into_later_calls() {
let k = kernel();
let opts = ExecuteOptions::new().with_interrupt(Arc::new(|| true));
let r = k
.execute_with_options("while true; do true; done", opts)
.await
.expect("interrupted");
assert_eq!(r.code, 130);
let r = k
.execute("N=0; while test $N -lt 3; do N=$((N + 1)); done; echo $N")
.await
.expect("clean run");
assert_eq!(r.code, 0, "stale interrupt leaked: {}", r.err);
assert_eq!(r.text_out().trim(), "3");
}