From ddc6de435599024df05efe349d3ade9af578983a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 09:15:49 +0000 Subject: [PATCH] =?UTF-8?q?test:=20Cycle=2019=20=E2=80=94=20integer=20over?= =?UTF-8?q?flow=20wrapping,=20multi-msg=20per=20tick,=20hot-swap=20lifecyc?= =?UTF-8?q?le?= 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 | 113 ++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 317e6f5..958f79b 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -3339,3 +3339,116 @@ proptest! { prop_assert!(result.is_ok(), "actor must survive any operation sequence"); } } + +// ── Integer overflow in guest: wrapping arithmetic doesn't trap ───────────── + +#[test] +fn guest_integer_overflow_wraps_silently() { + // WASM integers wrap on overflow (no trap). This guest adds i32::MAX + 1 + // and uses the result as a send offset. The wrapping result (0) should + // produce a valid send. + 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 256) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; i32::MAX + 1 wraps to i32::MIN (-2147483648) + ;; Use it as... nothing, just verify no trap + i32.const 2147483647 + i32.const 1 + i32.add + drop + + ;; Send normally + (i32.store8 (i32.const 200) (i32.const 33)) + local.get $ptr + i32.const 200 + i32.const 1 + 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(); + + rt.send_to(addr, framed_msg(inbox.addr(), b"wrap")).unwrap(); + rt.tick(); + + let received = inbox.try_recv().expect("wrapping overflow should not trap"); + assert_eq!(received.0, vec![33]); +} + +// ── Multiple messages in one tick to same WASM actor ──────────────────────── + +#[test] +fn multiple_messages_in_one_tick_all_processed() { + // Send 5 messages to a WASM actor before ticking. All should be + // processed in the same tick (within the default budget of 64). + 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(); + + for i in 0u8..5 { + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + } + rt.tick(); + + let mut received = Vec::new(); + while let Some(msg) = inbox.try_recv() { + received.push(msg.0[0]); + } + assert_eq!(received, vec![0, 1, 2, 3, 4]); +} + +// ── Swap actors: stop WASM, spawn new WASM at conceptually same role ──────── + +#[test] +fn hot_swap_wasm_actor_works() { + // Stop an echo actor, spawn a double actor in its place, verify the new + // one works correctly. Tests clean handover of actor lifecycle. + let engine = SharedEngine::new().unwrap(); + + // Phase 1: echo actor + let echo = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")) + .build() + .unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + + let echo_addr = rt.spawn(echo).unwrap(); + rt.send_to(echo_addr, framed_msg(inbox.addr(), b"echo-phase")).unwrap(); + rt.tick(); + let recv = inbox.try_recv().expect("echo should work"); + assert_eq!(recv.0, b"echo-phase"); + + // Stop echo + rt.stop_actor(echo_addr).unwrap(); + rt.tick(); + rt.tick(); + + // Phase 2: double actor + let double = WasmActorBuilder::new(engine, guest_wasm("double")) + .build() + .unwrap(); + let double_addr = rt.spawn(double).unwrap(); + + rt.send_to(double_addr, framed_msg(inbox.addr(), b"double-phase")).unwrap(); + rt.tick(); + + let first = inbox.try_recv().expect("double should send first"); + let second = inbox.try_recv().expect("double should send second"); + assert_eq!(first.0, b"double-phase"); + assert_eq!(second.0, b"double-phase"); + assert!(inbox.try_recv().is_none()); +}