test: Cycle 53 — repeated build independence, stale memory, guest module fuzz, 3-deep calls
- repeated_build_same_bytes_independent: 3 actors from same bytes, stop one, others work - guest_reads_stale_memory_region: reading uninitialized memory is safe - prop_any_guest_module_processes_safely: echo/double/silent × random msg count - three_level_nested_function_calls: inc→double_inc→transform chain (x+2)*3 All 228 tests pass (15 property tests). No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
parent
af5e0644bc
commit
246f957721
1 changed files with 138 additions and 0 deletions
|
|
@ -7914,3 +7914,141 @@ fn guest_reads_i16_from_payload() {
|
|||
assert_eq!(msg.0[0], 0x02, "low byte of 0x0102");
|
||||
assert_eq!(msg.0[1], 0x01, "high byte of 0x0102");
|
||||
}
|
||||
|
||||
// ── Repeated build from same bytes yields independent actors ────────────────
|
||||
|
||||
#[test]
|
||||
fn repeated_build_same_bytes_independent() {
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let wasm = guest_wasm("echo");
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
|
||||
// Build 3 actors from exact same bytes
|
||||
let a1 = rt.spawn(WasmActorBuilder::new(engine.clone(), wasm.clone()).build().unwrap()).unwrap();
|
||||
let a2 = rt.spawn(WasmActorBuilder::new(engine.clone(), wasm.clone()).build().unwrap()).unwrap();
|
||||
let a3 = rt.spawn(WasmActorBuilder::new(engine, wasm).build().unwrap()).unwrap();
|
||||
|
||||
// Stop a2 — a1 and a3 should still work
|
||||
rt.stop_actor(a2);
|
||||
rt.tick();
|
||||
rt.tick();
|
||||
|
||||
rt.send_to(a1, framed_msg(inbox.addr(), b"a1")).unwrap();
|
||||
rt.send_to(a3, framed_msg(inbox.addr(), b"a3")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let mut msgs: Vec<Vec<u8>> = Vec::new();
|
||||
while let Some(msg) = inbox.try_recv() {
|
||||
msgs.push(msg.0);
|
||||
}
|
||||
msgs.sort();
|
||||
assert_eq!(msgs, vec![b"a1".to_vec(), b"a3".to_vec()]);
|
||||
}
|
||||
|
||||
// ── Guest writes beyond alloc region (within memory) — reads stale data ─────
|
||||
|
||||
#[test]
|
||||
fn guest_reads_stale_memory_region() {
|
||||
// Guest's alloc returns 4096, but the handle reads from offset 0 (outside alloc region).
|
||||
// Memory at offset 0 was never written by the host for this message,
|
||||
// but may have been written by a previous message. Tests that reading
|
||||
// arbitrary memory is safe (no crash, just potentially stale data).
|
||||
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)
|
||||
;; Send 4 bytes from offset 0 (stale/zero memory)
|
||||
(call $send (local.get $ptr) (i32.const 0) (i32.const 4))
|
||||
)
|
||||
)
|
||||
"#;
|
||||
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"trigger")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let msg = inbox.try_recv().expect("reading stale memory is valid");
|
||||
assert_eq!(msg.0.len(), 4, "should receive 4 bytes");
|
||||
// Content is zero (fresh memory) — but we don't assert exact values
|
||||
// since they could be anything in theory
|
||||
}
|
||||
|
||||
// ── Property: any combination of guest modules processes without panic ──────
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn prop_any_guest_module_processes_safely(
|
||||
guest_idx in 0usize..3,
|
||||
n_msgs in 0u8..8,
|
||||
) {
|
||||
let guests = ["echo", "double", "silent"];
|
||||
let guest_name = guests[guest_idx];
|
||||
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, guest_wasm(guest_name)).build().unwrap();
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
for i in 0..n_msgs {
|
||||
let _ = rt.send_to(addr, framed_msg(inbox.addr(), &[i]));
|
||||
}
|
||||
rt.tick();
|
||||
while let Some(_) = inbox.try_recv() {}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Guest with nested function calls (3 deep) ──────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn three_level_nested_function_calls() {
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
(func $inc (param $x i32) (result i32)
|
||||
(i32.add (local.get $x) (i32.const 1))
|
||||
)
|
||||
(func $double_inc (param $x i32) (result i32)
|
||||
(call $inc (call $inc (local.get $x)))
|
||||
)
|
||||
(func $transform (param $x i32) (result i32)
|
||||
(i32.mul (call $double_inc (local.get $x)) (i32.const 3))
|
||||
)
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 4096)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; Read first byte, transform: (x+2)*3
|
||||
(i32.store8 (i32.const 200)
|
||||
(call $transform
|
||||
(i32.load8_u (i32.add (local.get $ptr) (i32.const 32)))
|
||||
)
|
||||
)
|
||||
(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();
|
||||
|
||||
// input=10: (10+2)*3 = 36
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), &[10])).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let msg = inbox.try_recv().unwrap();
|
||||
assert_eq!(msg.0[0], 36, "(10+2)*3 = 36");
|
||||
}
|
||||
Loading…
Reference in a new issue