test: Cycle 39 — static alloc overwrite, nested blocks, memory.size, spawn-stop fuzz
- static_alloc_pointer_messages_overwrite: same alloc ptr, outbox snapshots correctly - nested_blocks_in_handle: nested block/br control flow with 3 branches - guest_uses_memory_size_instruction: memory.size reports correct page count - prop_spawn_send_stop_cycle_never_panics: random msg count + payload size lifecycle fuzz - Fixed unused variable warning in prop_random_payload_sizes_never_panic All 166 tests pass (12 property tests). No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
bd089de666
commit
d542cfcc28
1 changed files with 144 additions and 1 deletions
|
|
@ -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() {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue