bin-runner #36

Merged
zacheryasc merged 103 commits from bin-runner into master 2026-02-13 14:11:40 +00:00
Showing only changes of commit d542cfcc28 - Show all commits

View file

@ -4907,7 +4907,7 @@ proptest! {
.build()
.unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let _inbox = rt.new_inbox::<ByteMessage>().unwrap();
let addr = rt.spawn(actor).unwrap();
// Send raw payload (not framed) — echo will try to use first 32 bytes as dest
@ -5981,3 +5981,146 @@ fn send_to_dead_wasm_actor_silently_dropped() {
rt.tick();
assert!(inbox.try_recv().is_none(), "dead actor should not deliver");
}
// ── Guest alloc always returns same pointer — messages overwrite each other ──
#[test]
fn static_alloc_pointer_messages_overwrite() {
// Guest alloc always returns 4096. Each message overwrites the same region.
// The last message to be processed wins. Verifies outbox snapshots correctly.
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();
// Send 3 messages in same tick. Each writes to offset 4096.
// Echo reads from 4096 — should read the data written for that specific call
// because outbox snapshots at send time.
rt.send_to(addr, framed_msg(inbox.addr(), b"first")).unwrap();
rt.send_to(addr, framed_msg(inbox.addr(), b"second")).unwrap();
rt.send_to(addr, framed_msg(inbox.addr(), b"third")).unwrap();
rt.tick();
let mut payloads = Vec::new();
while let Some(msg) = inbox.try_recv() {
payloads.push(msg.0);
}
assert_eq!(payloads.len(), 3);
assert_eq!(payloads[0], b"first");
assert_eq!(payloads[1], b"second");
assert_eq!(payloads[2], b"third");
}
// ── Module with nested blocks ───────────────────────────────────────────────
#[test]
fn nested_blocks_in_handle() {
// Guest uses nested block/end to structure control flow.
let wat = r#"
(module
(import "swactor" "send" (func $send (param i32 i32 i32)))
(memory (export "memory") 1)
(func (export "alloc") (param i32) (result i32) i32.const 4096)
(func (export "handle") (param $ptr i32) (param $len i32)
(block $outer
(block $inner
;; If len <= 32 (no payload), break to outer (skip send)
(br_if $outer (i32.le_s (local.get $len) (i32.const 32)))
;; If len > 64 (big payload), break to inner (send "BIG")
(br_if $inner (i32.gt_s (local.get $len) (i32.const 64)))
;; Small payload: send "SM"
(i32.store8 (i32.const 200) (i32.const 83))
(i32.store8 (i32.const 201) (i32.const 77))
(call $send (local.get $ptr) (i32.const 200) (i32.const 2))
return
)
;; Big payload
(i32.store8 (i32.const 200) (i32.const 66))
(i32.store8 (i32.const 201) (i32.const 71))
(call $send (local.get $ptr) (i32.const 200) (i32.const 2))
)
)
)
"#;
let wasm = wat::parse_str(wat).unwrap();
let engine = SharedEngine::new().unwrap();
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let addr = rt.spawn(actor).unwrap();
// No payload (len=32) → no send
rt.send_to(addr, framed_msg(inbox.addr(), b"")).unwrap();
// Small payload (5 bytes, total len=37) → "SM"
rt.send_to(addr, framed_msg(inbox.addr(), b"hello")).unwrap();
// Big payload (50 bytes, total len=82) → "BG"
rt.send_to(addr, framed_msg(inbox.addr(), &[b'x'; 50])).unwrap();
rt.tick();
let msg1 = inbox.try_recv().unwrap();
let msg2 = inbox.try_recv().unwrap();
assert!(inbox.try_recv().is_none(), "empty payload should not send");
assert_eq!(msg1.0, b"SM");
assert_eq!(msg2.0, b"BG");
}
// ── Guest uses memory.size to check available memory ────────────────────────
#[test]
fn guest_uses_memory_size_instruction() {
// Guest checks memory.size and sends it as a response byte.
let wat = r#"
(module
(import "swactor" "send" (func $send (param i32 i32 i32)))
(memory (export "memory") 2)
(func (export "alloc") (param i32) (result i32) i32.const 4096)
(func (export "handle") (param $ptr i32) (param $len i32)
;; memory.size returns pages (should be 2)
(i32.store8 (i32.const 200) (memory.size))
(call $send (local.get $ptr) (i32.const 200) (i32.const 1))
)
)
"#;
let wasm = wat::parse_str(wat).unwrap();
let engine = SharedEngine::new().unwrap();
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let addr = rt.spawn(actor).unwrap();
rt.send_to(addr, framed_msg(inbox.addr(), b"size")).unwrap();
rt.tick();
let msg = inbox.try_recv().unwrap();
assert_eq!(msg.0[0], 2, "memory.size should report 2 pages");
}
// ── Property: spawn-send-tick-stop cycle never panics ───────────────────────
proptest! {
#[test]
fn prop_spawn_send_stop_cycle_never_panics(
n_msgs in 0u8..20,
payload_len in 0usize..128,
) {
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 payload = vec![0xAB; payload_len];
for _ in 0..n_msgs {
let _ = rt.send_to(addr, framed_msg(inbox.addr(), &payload));
}
rt.tick();
rt.stop_actor(addr);
rt.tick();
// Drain inbox
while let Some(_) = inbox.try_recv() {}
}
}