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 61316347e4 - Show all commits

View file

@ -8940,4 +8940,149 @@ fn stop_actor_with_pending_messages_in_mailbox() {
let count_after: usize = std::iter::from_fn(|| inbox.try_recv()).count();
assert_eq!(count_after, 0, "no more messages after stop");
assert!(count_before <= 5, "at most 5 echoes received");
}
// ── Cycle 61 ─────────────────────────────────────────────────────────────────
// Guest with unreachable instruction — handle traps but actor survives
#[test]
fn guest_handle_hits_unreachable_instruction() {
let wat = r#"(module
(memory (export "memory") 1)
(func (export "alloc") (param $len i32) (result i32)
i32.const 1024)
(func (export "handle") (param $ptr i32) (param $len i32)
unreachable)
)"#;
let engine = SharedEngine::new().unwrap();
let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap())
.build().unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let addr = rt.spawn(actor).unwrap();
// First message traps
rt.send_to(addr, ByteMessage(b"boom".to_vec())).unwrap();
rt.tick();
// Actor should still be alive — send again, same result
rt.send_to(addr, ByteMessage(b"boom2".to_vec())).unwrap();
rt.tick();
// No responses in inbox
assert!(inbox.try_recv().is_none());
}
// Alloc that returns different pointers for successive calls (proper bump)
#[test]
fn guest_bump_allocator_returns_advancing_pointers() {
// Use echo guest — its bump allocator naturally advances
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 messages of different sizes — echo returns them all faithfully
for size in [1, 10, 100, 1000] {
let payload = vec![0xAB; size];
rt.send_to(addr, framed_msg(inbox.addr(), &payload)).unwrap();
}
rt.tick();
let msgs: Vec<Vec<u8>> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect();
assert_eq!(msgs.len(), 4);
assert_eq!(msgs[0].len(), 1);
assert_eq!(msgs[1].len(), 10);
assert_eq!(msgs[2].len(), 100);
assert_eq!(msgs[3].len(), 1000);
}
// Guest with block/loop/br_if — complex control flow
#[test]
fn guest_block_loop_br_if_control_flow() {
// Count bytes equal to 0x42 using loop
let wat = r#"(module
(memory (export "memory") 1)
(global $heap (mut i32) (i32.const 65536))
(func (export "alloc") (param $len i32) (result i32)
global.get $heap
global.get $heap
local.get $len
i32.add
global.set $heap)
(func (export "handle") (param $ptr i32) (param $len i32)
(local $i i32)
(local $count i32)
(local.set $i (i32.const 0))
(local.set $count (i32.const 0))
(block $exit
(loop $loop
;; if i >= len, break
(br_if $exit (i32.ge_u (local.get $i) (local.get $len)))
;; if mem[ptr+i] == 0x42, count++
(if (i32.eq (i32.load8_u (i32.add (local.get $ptr) (local.get $i))) (i32.const 0x42))
(then (local.set $count (i32.add (local.get $count) (i32.const 1))))
)
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $loop)
)
)
;; Store count at fixed location 0
(i32.store (i32.const 0) (local.get $count))
)
)"#;
let engine = SharedEngine::new().unwrap();
let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap())
.build().unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let addr = rt.spawn(actor).unwrap();
// Send payload with some 0x42 bytes
let payload = vec![0x42, 0x00, 0x42, 0x42, 0xFF];
rt.send_to(addr, ByteMessage(payload)).unwrap();
rt.tick(); // no trap
}
// Two WASM actors spawned, one stopped immediately, the other processes normally
#[test]
fn stop_one_of_two_wasm_actors() {
let engine = SharedEngine::new().unwrap();
let a1 = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap();
let a2 = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let addr1 = rt.spawn(a1).unwrap();
let addr2 = rt.spawn(a2).unwrap();
rt.stop_actor(addr1);
rt.send_to(addr2, framed_msg(inbox.addr(), b"still-alive")).unwrap();
rt.tick();
rt.tick();
let msgs: Vec<_> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect();
assert_eq!(msgs, vec![b"still-alive".to_vec()]);
}
// Property: spawning N actors (1..20) from same engine always works
proptest! {
#[test]
fn prop_spawn_n_actors_from_same_engine(n in 1usize..20) {
let engine = SharedEngine::new().unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let mut addrs = Vec::new();
for _ in 0..n {
let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap();
addrs.push(rt.spawn(actor).unwrap());
}
for (i, addr) in addrs.iter().enumerate() {
rt.send_to(*addr, framed_msg(inbox.addr(), &[i as u8])).unwrap();
}
rt.tick();
let msgs: Vec<_> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect();
assert_eq!(msgs.len(), n, "each actor echoes one message");
}
}