bin-runner #36

Merged
zacheryasc merged 103 commits from bin-runner into master 2026-02-13 14:11:40 +00:00
2 changed files with 94 additions and 0 deletions
Showing only changes of commit e7efab4157 - Show all commits

View file

@ -10,3 +10,4 @@ wasmtime = "29"
[dev-dependencies]
swactor = { path = "../..", features = ["getrandom"] }
wat = "1"
proptest = "1"

View file

@ -2,6 +2,8 @@ use swactor::actor::{ActorAddress, ActorInterface};
use swactor::runtime::{Ctx, Runtime, RuntimeConfig};
use swactor_wasm_actor::{ByteMessage, SharedEngine, WasmActorBuilder, WasmActorError};
use proptest::prelude::*;
fn guest_wasm(name: &str) -> Vec<u8> {
let path = format!(
"{}/tests/guests/{name}/target/wasm32-unknown-unknown/release/{name}_guest.wasm",
@ -737,3 +739,94 @@ fn native_actor_communicates_with_wasm_actor() {
let received = inbox.try_recv().expect("wasm actor should have echoed");
assert_eq!(received.0, b"from native");
}
// ── Multi-worker: WASM actors across threads ─────────────────────────────────
#[test]
fn wasm_actor_works_on_multi_worker_runtime() {
// Spawn a WASM echo actor on a 2-worker runtime and verify message
// round-trip works across threads. This is a smoke test for Send safety
// of wasmtime Store<HostState>.
let engine = SharedEngine::new().unwrap();
let actor = WasmActorBuilder::new(engine, guest_wasm("echo"))
.build()
.unwrap();
let config = RuntimeConfig {
num_threads: 2,
..RuntimeConfig::default()
};
let rt = Runtime::new(config);
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let addr = rt.spawn(actor).unwrap();
let payload = b"multi-worker";
rt.send_to(addr, framed_msg(inbox.addr(), payload)).unwrap();
// Use run() to drive the runtime on background threads
let handle = rt.run().unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
let received = inbox.try_recv().expect("wasm actor should echo on MT runtime");
assert_eq!(received.0, payload);
handle.shutdown();
}
// ── Property-based: arbitrary bytes round-trip through echo ──────────────────
proptest! {
#[test]
fn prop_echo_roundtrips_arbitrary_bytes(payload in proptest::collection::vec(any::<u8>(), 0..500)) {
let engine = SharedEngine::new().unwrap();
let actor = WasmActorBuilder::new(engine, guest_wasm("echo"))
.build()
.unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let addr = rt.spawn(actor).unwrap();
let msg = framed_msg(inbox.addr(), &payload);
rt.send_to(addr, msg).unwrap();
rt.tick();
if payload.is_empty() {
// Echo guest: if total len < 32, no reply (32B addr + 0B payload = 32, but
// the framed message is 32 + 0 = 32 bytes, and echo checks `len < 32`)
// Actually: framed_msg produces 32 + payload.len() bytes. When payload
// is empty, total is 32, and echo checks `if len < 32 { return; }`.
// len == 32 passes the check! So dest_ptr = ptr, payload_ptr = ptr+32,
// payload_len = 0 → sends a 0-byte message.
// Let's just check: if we got something, it matches.
if let Some(received) = inbox.try_recv() {
prop_assert_eq!(received.0, payload);
}
} else {
let received = inbox.try_recv().expect("echo should return non-empty payload");
prop_assert_eq!(received.0, payload);
}
}
#[test]
fn prop_double_always_sends_exactly_two_copies(payload in proptest::collection::vec(any::<u8>(), 1..500)) {
let engine = SharedEngine::new().unwrap();
let actor = WasmActorBuilder::new(engine, guest_wasm("double"))
.build()
.unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let addr = rt.spawn(actor).unwrap();
let msg = framed_msg(inbox.addr(), &payload);
rt.send_to(addr, msg).unwrap();
rt.tick();
let first = inbox.try_recv().expect("double should send first copy");
let second = inbox.try_recv().expect("double should send second copy");
prop_assert_eq!(&first.0, &payload);
prop_assert_eq!(&second.0, &payload);
prop_assert!(inbox.try_recv().is_none(), "exactly two messages expected");
}
}