From 0fa9ff0e77ae39a8940ed1fef186c159921836a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 09:37:00 +0000 Subject: [PATCH] =?UTF-8?q?test:=20Cycle=2031=20=E2=80=94=20zero-length=20?= =?UTF-8?q?send,=20separate=20engines,=2050-round=20stress,=20start=20func?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- crates/wasm-actor/tests/wasm_actor.rs | 127 ++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 9277029..25e76af 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -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"); } + +// ── 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::().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::().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::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::().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 = (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::().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"); +}