Files
sausage/host/tests/it/harness_shutdown.rs
T
surma dd79b573f1 Print sandbox test skips and update the multicall shutdown helper
Keep the full test suite selected without new fixture derivations.

Assisted-by: SurmAgent:unknown [read] [edit] [bash]
2026-11-12 11:22:09 +00:00

136 lines
4.0 KiB
Rust

//! SIGTERM cancels a world tool and preserves its durable invocation for restart.
use sausage::{
cred::Cred,
harness::{input, run_module_with_input},
world::World,
};
use std::{
path::Path,
process::{Command, Stdio},
sync::Arc,
time::{Duration, Instant},
};
#[tokio::test(flavor = "current_thread")]
async fn shutdown_child() -> anyhow::Result<()> {
let Ok(path) = std::env::var("SHUTDOWN_WORLD") else {
return Ok(());
};
let world = World::open(Path::new(&path))?;
let processes = world.processes.clone();
let cred = Arc::new(Cred::open(&world.host_dir)?);
let exec = sausage::process::exec(World {
root: world.root.clone(),
host_dir: world.host_dir.clone(),
processes: processes.clone(),
})?;
let (sender, receiver) = input::channel();
let signal = tokio::spawn(input::stop_signal(sender)?);
run_module_with_input(
world,
exec,
cred,
receiver,
concat!(
"globalThis.foundationCase = 'shutdown';\n",
include_str!("../../js/foundation-test-bundle.js")
),
)
.await?;
signal.abort();
tokio::time::timeout(Duration::from_secs(1), async {
while !processes.lock().unwrap().is_empty() {
tokio::task::yield_now().await;
}
})
.await?;
Ok(())
}
#[test]
fn sigterm_cancels_tool_and_resumes() -> anyhow::Result<()> {
let source = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap();
assert!(
Command::new("cargo")
.args([
"build",
"--release",
"--locked",
"-p",
"sausage-coreutils",
"--bin",
"coreutils",
"--manifest-path",
])
.arg(source.join("Cargo.toml"))
.args(["--target", "wasm32-wasip2", "--target-dir"])
.arg(target)
.args(["-j", "2"])
.status()?
.success()
);
let dir = tempfile::tempdir()?;
World::open(dir.path())?;
std::fs::create_dir_all(dir.path().join("world/bin"))?;
std::fs::copy(
target.join("wasm32-wasip2/release/coreutils.wasm"),
dir.path().join("world/bin/sleep.wasm"),
)?;
let spawn = || {
Command::new(std::env::current_exe().unwrap())
.args(["--exact", "harness_shutdown::shutdown_child", "--nocapture"])
.env("SHUTDOWN_WORLD", dir.path())
.stdin(Stdio::null())
.spawn()
};
let mut child = spawn()?;
let deadline = Instant::now() + Duration::from_secs(60);
while !dir.path().join("world/sleep-ready").exists() {
if let Some(status) = child.try_wait()? {
anyhow::bail!("Tool child exited early: {status}");
}
if Instant::now() > deadline {
child.kill()?;
child.wait()?;
anyhow::bail!("World sleep did not start");
}
std::thread::yield_now();
}
assert!(
Command::new("kill")
.args(["-TERM", &child.id().to_string()])
.status()?
.success()
);
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if let Some(status) = child.try_wait()? {
assert!(status.success());
break;
}
if Instant::now() > deadline {
child.kill()?;
child.wait()?;
anyhow::bail!("SIGTERM exceeded five seconds");
}
std::thread::yield_now();
}
std::fs::write(dir.path().join("world/resume"), "yes")?;
let mut child = spawn()?;
let deadline = Instant::now() + Duration::from_secs(20);
loop {
if let Some(status) = child.try_wait()? {
assert!(status.success());
break;
}
if Instant::now() > deadline {
child.kill()?;
child.wait()?;
anyhow::bail!("Restart did not complete");
}
std::thread::yield_now();
}
Ok(())
}