bin-runner #36
1 changed files with 193 additions and 0 deletions
|
|
@ -4894,3 +4894,196 @@ fn start_function_that_succeeds_allows_normal_operation() {
|
|||
let msg = inbox.try_recv().expect("should receive response after start");
|
||||
assert_eq!(msg.0[0], 42, "start function should have initialized global to 42");
|
||||
}
|
||||
|
||||
// ── Property: random payloads never cause host panic ────────────────────────
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn prop_random_payload_sizes_never_panic(
|
||||
payload in proptest::collection::vec(proptest::num::u8::ANY, 0..8192)
|
||||
) {
|
||||
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 raw payload (not framed) — echo will try to use first 32 bytes as dest
|
||||
// which will be random garbage. This should never crash the host.
|
||||
rt.send_to(addr, ByteMessage(payload)).unwrap();
|
||||
rt.tick();
|
||||
// We don't care what happens — just that it doesn't panic
|
||||
}
|
||||
}
|
||||
|
||||
// ── Module with multiple memory pages and data segments ─────────────────────
|
||||
|
||||
#[test]
|
||||
fn multi_page_data_segments_persist() {
|
||||
// Module starts with 3 pages and has data segments in each page.
|
||||
// Handle reads from each page to verify data segments initialized correctly.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 3)
|
||||
;; Data segment in page 0
|
||||
(data (i32.const 100) "\AA\BB\CC")
|
||||
;; Data segment in page 1 (offset 65536 + 100 = 65636)
|
||||
(data (i32.const 65636) "\DD\EE\FF")
|
||||
;; Data segment in page 2 (offset 131072 + 100 = 131172)
|
||||
(data (i32.const 131172) "\11\22\33")
|
||||
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 4096)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; Copy 3 bytes from each page into response buffer at 200
|
||||
(i32.store8 (i32.const 200) (i32.load8_u (i32.const 100)))
|
||||
(i32.store8 (i32.const 201) (i32.load8_u (i32.const 65636)))
|
||||
(i32.store8 (i32.const 202) (i32.load8_u (i32.const 131172)))
|
||||
(call $send (local.get $ptr) (i32.const 200) (i32.const 3))
|
||||
)
|
||||
)
|
||||
"#;
|
||||
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"read-pages")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let msg = inbox.try_recv().expect("should receive data from all pages");
|
||||
assert_eq!(msg.0, vec![0xAA, 0xDD, 0x11], "data segments should be initialized across pages");
|
||||
}
|
||||
|
||||
// ── Module that conditionally sends based on first payload byte ─────────────
|
||||
|
||||
#[test]
|
||||
fn conditional_send_fan_out_based_on_payload() {
|
||||
// Guest checks first payload byte:
|
||||
// 0x01 → send to dest from msg
|
||||
// 0x02 → send twice (double)
|
||||
// anything else → don't send
|
||||
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)
|
||||
;; Read first payload byte (after 32-byte address header)
|
||||
(if (i32.eq (i32.load8_u (i32.add (local.get $ptr) (i32.const 32))) (i32.const 1))
|
||||
(then
|
||||
;; Send payload (skip first byte) once
|
||||
(call $send
|
||||
(local.get $ptr)
|
||||
(i32.add (local.get $ptr) (i32.const 33))
|
||||
(i32.sub (local.get $len) (i32.const 33))
|
||||
)
|
||||
)
|
||||
)
|
||||
(if (i32.eq (i32.load8_u (i32.add (local.get $ptr) (i32.const 32))) (i32.const 2))
|
||||
(then
|
||||
;; Send payload twice
|
||||
(call $send
|
||||
(local.get $ptr)
|
||||
(i32.add (local.get $ptr) (i32.const 33))
|
||||
(i32.sub (local.get $len) (i32.const 33))
|
||||
)
|
||||
(call $send
|
||||
(local.get $ptr)
|
||||
(i32.add (local.get $ptr) (i32.const 33))
|
||||
(i32.sub (local.get $len) (i32.const 33))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
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();
|
||||
|
||||
// Command 0x01: send once
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), &[0x01, b'X'])).unwrap();
|
||||
// Command 0x02: send twice
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), &[0x02, b'Y'])).unwrap();
|
||||
// Command 0xFF: no send
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), &[0xFF, b'Z'])).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let mut received = Vec::new();
|
||||
while let Some(msg) = inbox.try_recv() {
|
||||
received.push(msg.0.clone());
|
||||
}
|
||||
assert_eq!(received.len(), 3, "should get 1 + 2 + 0 = 3 messages");
|
||||
assert_eq!(received[0], vec![b'X']);
|
||||
assert_eq!(received[1], vec![b'Y']);
|
||||
assert_eq!(received[2], vec![b'Y']);
|
||||
}
|
||||
|
||||
// ── Guest allocator returns different offsets per call ───────────────────────
|
||||
|
||||
#[test]
|
||||
fn guest_with_advancing_allocator_handles_multiple_messages() {
|
||||
// Guest has a proper advancing bump allocator (not static offset).
|
||||
// Each alloc call returns the next available slot. Verify messages
|
||||
// don't overwrite each other when processed in the same tick.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
(global $heap_ptr (mut i32) (i32.const 4096))
|
||||
|
||||
(func (export "alloc") (param $size i32) (result i32)
|
||||
(local $ptr i32)
|
||||
(local.set $ptr (global.get $heap_ptr))
|
||||
;; Advance heap pointer (8-byte aligned)
|
||||
(global.set $heap_ptr
|
||||
(i32.and
|
||||
(i32.add (i32.add (global.get $heap_ptr) (local.get $size)) (i32.const 7))
|
||||
(i32.const -8)
|
||||
)
|
||||
)
|
||||
(local.get $ptr)
|
||||
)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; Echo: send payload (after 32-byte header) to dest (first 32 bytes)
|
||||
(call $send
|
||||
(local.get $ptr)
|
||||
(i32.add (local.get $ptr) (i32.const 32))
|
||||
(i32.sub (local.get $len) (i32.const 32))
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
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 10 messages in one tick with distinct payloads
|
||||
for i in 0u8..10 {
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), &[i; 16])).unwrap();
|
||||
}
|
||||
rt.tick();
|
||||
|
||||
let mut received = Vec::new();
|
||||
while let Some(msg) = inbox.try_recv() {
|
||||
received.push(msg.0.clone());
|
||||
}
|
||||
assert_eq!(received.len(), 10, "all 10 messages should be echoed");
|
||||
for (i, payload) in received.iter().enumerate() {
|
||||
assert_eq!(payload, &vec![i as u8; 16], "payload {i} should be intact");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue