From 8f3710c5f4180ca699ab1948a3c68ac0bb3cc024 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 10:07:22 +0000 Subject: [PATCH] =?UTF-8?q?test:=20Cycle=2056=20=E2=80=94=20interleaved=20?= =?UTF-8?q?spawn,=20store16,=20min/max,=20runtime=20drop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - interleaved_spawn_and_message_delivery: spawn-send-spawn-send-tick delivers both - guest_uses_i32_store16: 16-bit LE store/read - guest_computes_min_max: select instruction for min/max of two bytes - drop_runtime_with_live_actors: drop runtime with 10 live WASM actors, no panic All 240 tests pass. No new bugs found. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 116 ++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index d20794c..0683108 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -8289,4 +8289,120 @@ fn guest_with_nop_instructions() { let msg = inbox.try_recv().unwrap(); assert_eq!(msg.0, vec![77], "nops should not affect behavior"); +} + +// ── Interleaved spawn and message delivery ────────────────────────────────── + +#[test] +fn interleaved_spawn_and_message_delivery() { + // Spawn actor, send message, spawn another, send message, tick once. + // Both should process in the same tick. + let engine = SharedEngine::new().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + + let a1 = rt.spawn(WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap()).unwrap(); + rt.send_to(a1, framed_msg(inbox.addr(), b"first")).unwrap(); + let a2 = rt.spawn(WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap()).unwrap(); + rt.send_to(a2, framed_msg(inbox.addr(), b"second")).unwrap(); + rt.tick(); + + let mut msgs: Vec> = Vec::new(); + while let Some(msg) = inbox.try_recv() { + msgs.push(msg.0); + } + msgs.sort(); + assert_eq!(msgs, vec![b"first".to_vec(), b"second".to_vec()]); +} + +// ── Guest with i32.store16 ───────────────────────────────────────────────── + +#[test] +fn guest_uses_i32_store16() { + 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) + ;; Store 0x1234 as 16-bit LE at offset 200 + (i32.store16 (i32.const 200) (i32.const 0x1234)) + (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::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), b"s16")).unwrap(); + rt.tick(); + + let msg = inbox.try_recv().unwrap(); + assert_eq!(msg.0, vec![0x34, 0x12], "i32.store16 should be little-endian"); +} + +// ── Guest computes min/max of two payload bytes ───────────────────────────── + +#[test] +fn guest_computes_min_max() { + 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) + (local $a i32) (local $b i32) + (local.set $a (i32.load8_u (i32.add (local.get $ptr) (i32.const 32)))) + (local.set $b (i32.load8_u (i32.add (local.get $ptr) (i32.const 33)))) + ;; min: use select + (i32.store8 (i32.const 200) + (select (local.get $a) (local.get $b) + (i32.lt_u (local.get $a) (local.get $b))) + ) + ;; max: use select + (i32.store8 (i32.const 201) + (select (local.get $a) (local.get $b) + (i32.gt_u (local.get $a) (local.get $b))) + ) + (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::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), &[42, 99])).unwrap(); + rt.tick(); + + let msg = inbox.try_recv().unwrap(); + assert_eq!(msg.0[0], 42, "min(42, 99) = 42"); + assert_eq!(msg.0[1], 99, "max(42, 99) = 99"); +} + +// ── Drop runtime with live WASM actors — no leak/panic ────────────────────── + +#[test] +fn drop_runtime_with_live_actors() { + let engine = SharedEngine::new().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + + for _ in 0..10 { + let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); + let addr = rt.spawn(actor).unwrap(); + rt.send_to(addr, ByteMessage(vec![1, 2, 3])).unwrap(); + } + rt.tick(); + + // Drop runtime without stopping actors — should not panic or leak + drop(rt); } \ No newline at end of file