From fd0181291c93bb2c93d2c6cf7c16355e8dd07a5e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 09:12:12 +0000 Subject: [PATCH] =?UTF-8?q?test:=20Cycle=2016=20=E2=80=94=20spawn+send=20s?= =?UTF-8?q?ame=20tick,=2021-actor=20mixed=20runtime,=20alternating=20alloc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- crates/wasm-actor/tests/wasm_actor.rs | 139 ++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 3f9abfb..407705e 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -2875,3 +2875,142 @@ proptest! { prop_assert_eq!(received.0, payload); } } + +// ── Spawn and send in same tick: message delivered on first tick ───────────── + +#[test] +fn wasm_actor_receives_message_sent_in_spawn_tick() { + 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::().unwrap(); + + let addr = rt.spawn(actor).unwrap(); + rt.send_to(addr, framed_msg(inbox.addr(), b"first-tick")).unwrap(); + + // Single tick should spawn the actor AND deliver the message + rt.tick(); + + let received = inbox.try_recv().expect("message sent before first tick should be processed"); + assert_eq!(received.0, b"first-tick"); +} + +// ── WASM actor coexists with many native actors ───────────────────────────── + +struct Counter { + count: std::sync::Arc, +} + +#[derive(Clone)] +struct Ping; + +impl ActorInterface for Counter { + type Incoming = Ping; + type Response = (); + fn handle(&mut self, _ctx: &Ctx, _msg: Ping) { + self.count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + } +} + +#[test] +fn wasm_actor_works_alongside_many_native_actors() { + let engine = SharedEngine::new().unwrap(); + let echo = WasmActorBuilder::new(engine, guest_wasm("echo")) + .build() + .unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + + let counts: Vec<_> = (0..20) + .map(|_| std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0))) + .collect(); + + let mut native_addrs = Vec::new(); + for count in &counts { + let addr = rt.spawn(Counter { count: count.clone() }).unwrap(); + native_addrs.push(addr); + } + let echo_addr = rt.spawn(echo).unwrap(); + + // Send to all actors in same tick + for addr in &native_addrs { + rt.send_to(*addr, Ping).unwrap(); + } + rt.send_to(echo_addr, framed_msg(inbox.addr(), b"mixed")).unwrap(); + rt.tick(); + + for (i, count) in counts.iter().enumerate() { + assert_eq!( + count.load(std::sync::atomic::Ordering::SeqCst), 1, + "native actor {i} should have processed its Ping" + ); + } + + let received = inbox.try_recv().expect("WASM actor should echo in mixed runtime"); + assert_eq!(received.0, b"mixed"); +} + +// ── alloc alternates between failure and success ──────────────────────────── + +#[test] +fn alloc_alternates_between_failure_and_success() { + let wat = r#" + (module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (global $counter (mut i32) (i32.const 0)) + + (func (export "alloc") (param $size i32) (result i32) + global.get $counter + i32.const 1 + i32.add + global.set $counter + + ;; Odd calls return -1 (invalid), even calls return 256 + global.get $counter + i32.const 2 + i32.rem_u + i32.const 1 + i32.eq + if (result i32) + i32.const -1 + else + i32.const 256 + end + ) + (func (export "handle") (param $ptr i32) (param $len i32) + local.get $ptr + local.get $ptr + i32.const 32 + i32.add + local.get $len + i32.const 32 + i32.sub + call $send + ) + ) + "#; + 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(); + + // Send 6 messages: 1(fail), 2(ok), 3(fail), 4(ok), 5(fail), 6(ok) + let mut echoed = 0; + for i in 0u8..6 { + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + rt.tick(); + if inbox.try_recv().is_some() { + echoed += 1; + } + } + + assert_eq!(echoed, 3, "should echo on even-numbered alloc calls only"); +}