test(wasm-actor): cycle 61 — unreachable, bump alloc, control flow, N-spawn fuzz (265 tests)
Added 5 tests: unreachable instruction trap recovery, bump allocator advancing pointers, block/loop/br_if control flow, stop one of two actors, property test spawning 1..20 actors from same engine. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
77529b75eb
commit
61316347e4
1 changed files with 145 additions and 0 deletions
|
|
@ -8941,3 +8941,148 @@ fn stop_actor_with_pending_messages_in_mailbox() {
|
|||
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");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue