use std::process::Stdio;
use std::time::Duration;
use microsandbox::Sandbox;
use test_utils::msb_test;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::Command;
use tokio::time::timeout;
#[msb_test]
async fn exec_stream_drives_guest_turn_by_turn() {
let name = "cli-exec-stream-turns";
let sandbox = Sandbox::builder(name)
.image("mirror.gcr.io/library/alpine")
.cpus(1)
.memory(512)
.replace()
.create()
.await
.expect("create sandbox");
let mut child = Command::new(env!("CARGO_BIN_EXE_msb"))
.args([
"exec",
"--stream",
"--quiet",
name,
"--",
"awk",
"{ print \"ack:\" $0; fflush() }",
])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("spawn msb exec --stream");
let mut stdin = child.stdin.take().expect("child stdin");
let mut lines = BufReader::new(child.stdout.take().expect("child stdout")).lines();
let driver = timeout(Duration::from_secs(60), async move {
for i in 1..=3 {
stdin
.write_all(format!("turn{i}\n").as_bytes())
.await
.expect("write turn");
stdin.flush().await.expect("flush turn");
let reply = lines
.next_line()
.await
.expect("read reply")
.expect("guest closed stdout early");
assert_eq!(reply, format!("ack:turn{i}"));
}
drop(stdin);
while lines.next_line().await.expect("drain stdout").is_some() {}
})
.await;
if driver.is_err() {
let _ = child.start_kill();
}
let status = child.wait().await.expect("wait for msb exec");
sandbox.stop().await.expect("stop sandbox");
Sandbox::remove(name).await.expect("remove sandbox");
driver.expect("`exec --stream` did not stream turn by turn (deadlocked)");
assert!(
status.success(),
"msb exec --stream exited non-zero: {status:?}"
);
}