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 55a8ddb97a - Show all commits

View file

@ -4367,7 +4367,7 @@ fn pingpong_wasm_echoes_bounded_by_budget() {
let rt = Runtime::new(config);
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let a1 = rt.spawn(echo1).unwrap();
let a2 = rt.spawn(echo2).unwrap();
let _a2 = rt.spawn(echo2).unwrap();
// Seed: tell actor 1 to echo to actor 2, with addr of actor 1 as payload
// so actor 2's reply goes back to actor 1 (creating a loop).
@ -4626,3 +4626,144 @@ fn send_dest_one_past_memory_boundary_traps() {
// This time no OOB send, but the module always tries the OOB send, so still trapped
assert!(inbox.try_recv().is_none(), "same module always traps");
}
// ── Reference types module rejected by engine ───────────────────────────────
#[test]
fn reference_types_module_rejected() {
// Module uses externref (reference types disabled in SharedEngine).
let wat = r#"
(module
(memory (export "memory") 1)
(func (export "alloc") (param i32) (result i32) i32.const 0)
(func (export "handle") (param i32 i32))
(table 1 externref)
)
"#;
let wasm = wat::parse_str(wat).unwrap();
let engine = SharedEngine::new().unwrap();
let result = WasmActorBuilder::new(engine, wasm).build();
assert!(result.is_err(), "reference types should be rejected by sandboxed engine");
}
// ── Two actors, one traps always, one works — independent store isolation ───
#[test]
fn trapping_actor_does_not_affect_sibling() {
// Actor 1 always traps in handle. Actor 2 echos normally.
// Verify trap in actor 1 doesn't corrupt/poison actor 2.
let trap_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 i32 i32)
unreachable
)
)
"#;
let trap_wasm = wat::parse_str(trap_wat).unwrap();
let engine = SharedEngine::new().unwrap();
let trapper = WasmActorBuilder::new(engine.clone(), trap_wasm).build().unwrap();
let echoer = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let trap_addr = rt.spawn(trapper).unwrap();
let echo_addr = rt.spawn(echoer).unwrap();
// Send to both in the same tick
rt.send_to(trap_addr, framed_msg(inbox.addr(), b"trap-this")).unwrap();
rt.send_to(echo_addr, framed_msg(inbox.addr(), b"echo-this")).unwrap();
rt.tick();
// Only echo actor should deliver
let mut msgs = Vec::new();
while let Some(msg) = inbox.try_recv() {
msgs.push(msg.0);
}
assert_eq!(msgs.len(), 1, "only echo actor should deliver");
assert_eq!(&msgs[0], b"echo-this");
}
// ── Alloc returns negative for non-zero len — message dropped gracefully ────
#[test]
fn alloc_returns_negative_for_nonzero_drops_message() {
// Guest alloc always returns -42 regardless of input.
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 -42)
(func (export "handle") (param i32 i32)
;; Should never be called because alloc returns negative
unreachable
)
)
"#;
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();
// Send several messages — all should be silently dropped
for i in 0..5 {
rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap();
}
rt.tick();
assert!(inbox.try_recv().is_none(), "negative alloc should drop messages");
// Actor should still be alive — send another message, still dropped
rt.send_to(addr, framed_msg(inbox.addr(), &[99])).unwrap();
rt.tick();
assert!(inbox.try_recv().is_none(), "actor alive but still drops (negative alloc)");
}
// ── WASM actor with global state accumulates across messages ────────────────
#[test]
fn global_counter_accumulates_across_messages() {
// Guest has a mutable global counter. Each handle call increments it.
// The response payload includes the counter value.
let wat = r#"
(module
(import "swactor" "send" (func $send (param i32 i32 i32)))
(memory (export "memory") 1)
(global $counter (mut i32) (i32.const 0))
(func (export "alloc") (param i32) (result i32) i32.const 4096)
(func (export "handle") (param $ptr i32) (param $len i32)
;; Increment counter
(global.set $counter (i32.add (global.get $counter) (i32.const 1)))
;; Write counter value as single byte at offset 200
(i32.store8 (i32.const 200) (global.get $counter))
;; Send counter value back to sender (first 32 bytes of msg)
(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();
// Send 5 messages across 5 ticks
for _ in 0..5 {
rt.send_to(addr, framed_msg(inbox.addr(), b"inc")).unwrap();
rt.tick();
}
let mut counter_values = Vec::new();
while let Some(msg) = inbox.try_recv() {
counter_values.push(msg.0[0]);
}
assert_eq!(counter_values, vec![1, 2, 3, 4, 5], "global state should persist across handle calls");
}