use std::path::{Path, PathBuf};
use microsandbox::Sandbox;
use test_utils::msb_test;
const TEST_INIT_PATH_ENV: &str = "MSB_TEST_INIT_PATH";
fn require_test_init() -> Option<PathBuf> {
let raw = std::env::var(TEST_INIT_PATH_ENV).ok()?;
let path = PathBuf::from(raw);
if !path.is_file() {
eprintln!(
"skipping: {TEST_INIT_PATH_ENV} points at {} which is not a regular file",
path.display()
);
return None;
}
Some(path)
}
async fn boot_with_test_init(name: &str, init_bin: &Path) -> Sandbox {
Sandbox::builder(name)
.image("mirror.gcr.io/library/alpine")
.cpus(1)
.memory(256)
.replace()
.patch(|p| p.copy_file(init_bin, "/sbin/init", Some(0o755), true))
.init("/sbin/init")
.create()
.await
.expect("create sandbox with handoff")
}
#[msb_test]
async fn pid_1_is_handed_off_to_test_init() {
let Some(init_bin) = require_test_init() else {
return;
};
let name = "init-handoff-pid1";
let sb = boot_with_test_init(name, &init_bin).await;
let out = sb
.shell("cat /proc/1/comm")
.await
.expect("read /proc/1/comm");
let comm = out.stdout().expect("utf8").trim().to_owned();
sb.stop_and_wait().await.expect("stop");
let _ = Sandbox::remove(name).await;
assert_eq!(
comm, "test-init",
"expected /proc/1/comm = 'test-init' (the handoff target), got {comm:?}"
);
}
#[msb_test]
async fn exec_session_parent_is_pid_1_post_handoff() {
let Some(init_bin) = require_test_init() else {
return;
};
let name = "init-handoff-getppid";
let sb = boot_with_test_init(name, &init_bin).await;
let out = sb
.shell("awk '/^PPid/{print $2}' /proc/$$/status")
.await
.expect("read PPid");
let agentd_pid: u32 = out
.stdout()
.expect("utf8")
.trim()
.parse()
.expect("PPid is u32");
let out2 = sb
.shell(format!(
"awk '/^PPid/{{print $2}}' /proc/{agentd_pid}/status"
))
.await
.expect("read agentd PPid");
let agentd_ppid: u32 = out2
.stdout()
.expect("utf8")
.trim()
.parse()
.expect("agentd PPid is u32");
sb.stop_and_wait().await.expect("stop");
let _ = Sandbox::remove(name).await;
assert_eq!(
agentd_ppid, 1,
"agentd's parent should be the new init (PID 1), got {agentd_ppid}"
);
}
#[msb_test]
async fn shutdown_via_signal_path_terminates_guest() {
let Some(init_bin) = require_test_init() else {
return;
};
let name = "init-handoff-shutdown";
let sb = boot_with_test_init(name, &init_bin).await;
let _ = sb.shell("true").await.expect("alive");
let status = sb.stop_and_wait().await.expect("stop_and_wait");
let _ = Sandbox::remove(name).await;
assert!(
status.success(),
"guest should have exited cleanly via the signal path, got {status:?}"
);
}