use std::io::{Read, Write};
use std::net::TcpListener;
use std::path::PathBuf;
use std::process::Command;
use std::thread;
fn fixture_manifest() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/wasi-guest/Cargo.toml")
}
fn guest_wasm() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/wasi-guest/target/wasm32-wasip2/debug/phantom-wasi-guest.wasm")
}
fn build_guest() {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let out = Command::new(&cargo)
.args([
"build",
"--manifest-path",
fixture_manifest().to_str().unwrap(),
"--target",
"wasm32-wasip2",
])
.output()
.expect("spawn cargo for wasi-guest build");
if !out.status.success() {
panic!(
"wasi-guest build failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr),
);
}
assert!(guest_wasm().exists(), "guest .wasm not produced");
}
fn echo_once(mut stream: std::net::TcpStream) {
let mut len_buf = [0u8; 4];
if stream.read_exact(&mut len_buf).is_err() {
return;
}
let len = u32::from_be_bytes(len_buf) as usize;
let mut payload = vec![0u8; len];
if stream.read_exact(&mut payload).is_err() {
return;
}
let _ = stream.write_all(&len_buf);
let _ = stream.write_all(&payload);
let _ = stream.flush();
}
fn wasi_runtime_available() -> bool {
let target_installed = Command::new("rustup")
.args(["target", "list", "--installed"])
.output()
.ok()
.map(|o| String::from_utf8_lossy(&o.stdout).contains("wasm32-wasip2"))
.unwrap_or(false);
if !target_installed {
eprintln!(
"SKIP: wasm32-wasip2 target not installed (run `rustup target add wasm32-wasip2`)"
);
return false;
}
if Command::new("wasmtime").arg("--version").output().is_err() {
eprintln!("SKIP: wasmtime not on PATH (install via `brew install wasmtime` or equivalent)");
return false;
}
true
}
fn run_guest_round_trip(mode: &str, expected_marker: &str) {
if !wasi_runtime_available() {
return;
}
build_guest();
let listener = TcpListener::bind("127.0.0.1:0").expect("bind echo server");
let port = listener.local_addr().expect("local_addr").port();
let server_thread = thread::spawn(move || {
let (stream, _) = listener.accept().expect("accept");
echo_once(stream);
});
let mut args: Vec<String> = vec![
"run".into(),
"-S".into(),
"inherit-network".into(),
"--env".into(),
format!("PHANTOM_PORT={port}"),
];
if !mode.is_empty() {
args.push("--env".into());
args.push(format!("PHANTOM_MODE={mode}"));
}
let out = Command::new("wasmtime")
.args(&args)
.arg(guest_wasm())
.output()
.expect("spawn wasmtime");
let _ = server_thread.join();
assert!(
out.status.success(),
"wasi guest (mode={mode:?}) exited with non-zero status: {:?}\nstdout:\n{}\nstderr:\n{}",
out.status,
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr),
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains(expected_marker),
"expected marker {expected_marker:?} in guest stderr (mode={mode:?}); got:\n{stderr}"
);
}
#[test]
#[ignore]
fn wasi_guest_round_trips_payload_through_wasmtime() {
run_guest_round_trip("", "OK: round-tripped");
}
#[test]
#[ignore]
fn wasi_guest_round_trips_payload_via_runtime_through_wasmtime() {
run_guest_round_trip("runtime", "OK: runtime-driven round-trip");
}