test: Cycle 31 — zero-length send, separate engines, 50-round stress, start function

- send_import_zero_length_payload_delivers_empty: payload_len=0 delivers empty msg
- actors_from_separate_engines_coexist: two engines on same runtime work independently
- rapid_spawn_send_stop_50_rounds: 50 sequential spawn-send-stop cycles all deliver
- start_function_that_succeeds_allows_normal_operation: start initializes global, handle uses it

All 135 tests pass. No new bugs found.

Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
This commit is contained in:
Claude 2026-02-13 09:37:00 +00:00
parent 55a8ddb97a
commit 0fa9ff0e77

View file

@ -4767,3 +4767,130 @@ fn global_counter_accumulates_across_messages() {
} }
assert_eq!(counter_values, vec![1, 2, 3, 4, 5], "global state should persist across handle calls"); assert_eq!(counter_values, vec![1, 2, 3, 4, 5], "global state should persist across handle calls");
} }
// ── Send import: payload_len = 0 is valid zero-copy send ────────────────────
#[test]
fn send_import_zero_length_payload_delivers_empty() {
// Guest calls swactor.send with payload_len=0. This should deliver an
// empty payload (not a trap, not dropped).
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 with payload_len=0
(call $send (local.get $ptr) (i32.const 0) (i32.const 0))
)
)
"#;
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("zero-length send should deliver");
assert!(msg.0.is_empty(), "payload should be empty");
}
// ── Multiple engines with different configurations ──────────────────────────
#[test]
fn actors_from_separate_engines_coexist() {
// Build two separate engines and spawn one actor from each.
// They should work independently on the same runtime.
let engine1 = SharedEngine::new().unwrap();
let engine2 = SharedEngine::new().unwrap();
let echo1 = WasmActorBuilder::new(engine1, guest_wasm("echo")).build().unwrap();
let echo2 = WasmActorBuilder::new(engine2, guest_wasm("echo")).build().unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
let addr1 = rt.spawn(echo1).unwrap();
let addr2 = rt.spawn(echo2).unwrap();
rt.send_to(addr1, framed_msg(inbox.addr(), b"from-engine1")).unwrap();
rt.send_to(addr2, framed_msg(inbox.addr(), b"from-engine2")).unwrap();
rt.tick();
let mut payloads: Vec<Vec<u8>> = Vec::new();
while let Some(msg) = inbox.try_recv() {
payloads.push(msg.0);
}
payloads.sort();
assert_eq!(payloads, vec![b"from-engine1".to_vec(), b"from-engine2".to_vec()]);
}
// ── Rapid spawn-send-stop stress test (50 rounds) ───────────────────────────
#[test]
fn rapid_spawn_send_stop_50_rounds() {
let engine = SharedEngine::new().unwrap();
let rt = Runtime::new(RuntimeConfig::default());
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
for round in 0u8..50 {
let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("echo"))
.build()
.unwrap();
let addr = rt.spawn(actor).unwrap();
rt.send_to(addr, framed_msg(inbox.addr(), &[round])).unwrap();
rt.tick();
rt.stop_actor(addr);
rt.tick(); // process stop
}
let mut received = Vec::new();
while let Some(msg) = inbox.try_recv() {
received.push(msg.0[0]);
}
let expected: Vec<u8> = (0..50).collect();
assert_eq!(received, expected, "all 50 rounds should deliver");
}
// ── Module with start function that succeeds ────────────────────────────────
#[test]
fn start_function_that_succeeds_allows_normal_operation() {
// Module has a start function that initializes a global.
// After start, normal handle should work.
let wat = r#"
(module
(import "swactor" "send" (func $send (param i32 i32 i32)))
(memory (export "memory") 1)
(global $initialized (mut i32) (i32.const 0))
(func $init
(global.set $initialized (i32.const 42))
)
(start $init)
(func (export "alloc") (param i32) (result i32) i32.const 4096)
(func (export "handle") (param $ptr i32) (param $len i32)
;; Write initialized value as response
(i32.store8 (i32.const 200) (global.get $initialized))
(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"check-init")).unwrap();
rt.tick();
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");
}