diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 1c6a13f..afd6868 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -9622,4 +9622,137 @@ fn two_actors_from_cloned_wasm_bytes() { assert_eq!(inbox1.try_recv().unwrap().0, b"one"); assert_eq!(inbox2.try_recv().unwrap().0, b"two"); +} + +// ── Cycle 66 ───────────────────────────────────────────────────────────────── + +// Guest uses i32.shr_u (logical right shift) +#[test] +fn guest_uses_logical_right_shift() { + let wat = r#"(module + (memory (export "memory") 1) + (func (export "alloc") (param $len i32) (result i32) i32.const 1024) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Load 4 bytes from ptr, shift right by 4, store back + (i32.store (local.get $ptr) + (i32.shr_u (i32.load (local.get $ptr)) (i32.const 4)) + ) + ) + )"#; + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()) + .build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let addr = rt.spawn(actor).unwrap(); + rt.send_to(addr, ByteMessage(vec![0xFF, 0x00, 0x00, 0x00])).unwrap(); + rt.tick(); +} + +// Long-lived actor: 100 messages across 100 ticks +#[test] +fn hundred_messages_across_hundred_ticks() { + 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..100 { + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + rt.tick(); + let resp = inbox.try_recv().expect("each tick should echo"); + assert_eq!(resp.0, vec![i], "tick {i} content mismatch"); + } +} + +// Guest with nested if/else chains +#[test] +fn guest_nested_if_else_three_deep() { + let wat = r#"(module + (memory (export "memory") 1) + (func (export "alloc") (param $len i32) (result i32) i32.const 1024) + (func (export "handle") (param $ptr i32) (param $len i32) + (if (i32.gt_u (local.get $len) (i32.const 10)) + (then + (if (i32.gt_u (local.get $len) (i32.const 20)) + (then + (if (i32.gt_u (local.get $len) (i32.const 30)) + (then (i32.store (local.get $ptr) (i32.const 3))) + (else (i32.store (local.get $ptr) (i32.const 2))) + ) + ) + (else (i32.store (local.get $ptr) (i32.const 1))) + ) + ) + (else (i32.store (local.get $ptr) (i32.const 0))) + ) + ) + )"#; + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()) + .build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let addr = rt.spawn(actor).unwrap(); + + // len=5 → branch 0, len=15 → branch 1, len=25 → branch 2, len=35 → branch 3 + for len in [5, 15, 25, 35] { + rt.send_to(addr, ByteMessage(vec![0u8; len])).unwrap(); + } + rt.tick(); // no trap in any branch +} + +// Multiple sends in one handle with increasing payload sizes +#[test] +fn guest_sends_increasing_payloads_in_one_handle() { + let wat = r#"(module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (func (export "alloc") (param $len i32) (result i32) i32.const 1024) + (func (export "handle") (param $ptr i32) (param $len i32) + ;; Send 1 byte, 2 bytes, 3 bytes from offset 0 (with dest at ptr) + (if (i32.ge_u (local.get $len) (i32.const 32)) + (then + (call $send (local.get $ptr) (i32.add (local.get $ptr) (i32.const 32)) (i32.const 1)) + (call $send (local.get $ptr) (i32.add (local.get $ptr) (i32.const 32)) (i32.const 2)) + (call $send (local.get $ptr) (i32.add (local.get $ptr) (i32.const 32)) (i32.const 3)) + ) + ) + ) + )"#; + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wat::parse_str(wat).unwrap()) + .build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + let mut msg = Vec::new(); + msg.extend_from_slice(&inbox.addr().0); + msg.extend_from_slice(b"ABCDE"); + rt.send_to(addr, ByteMessage(msg)).unwrap(); + rt.tick(); + + let msgs: Vec> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + assert_eq!(msgs.len(), 3); + assert_eq!(msgs[0].len(), 1); + assert_eq!(msgs[1].len(), 2); + assert_eq!(msgs[2].len(), 3); +} + +// Engine and builder are independent — dropping engine after build still works +#[test] +fn engine_dropped_after_build_actor_still_works() { + let actor = { + let engine = SharedEngine::new().unwrap(); + WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap() + // engine dropped here + }; + 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"after-drop")).unwrap(); + rt.tick(); + + let resp = inbox.try_recv().expect("should echo after engine dropped"); + assert_eq!(resp.0, b"after-drop"); } \ No newline at end of file