diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 4dcbf5f..94807f7 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -11988,4 +11988,130 @@ fn heterogeneous_wat_actors_on_same_runtime() { // One is "hello", other is [0x42] assert!(msgs.iter().any(|m| m == b"hello")); assert!(msgs.iter().any(|m| m == &[0x42])); +} + +// ── Cycle 84 ───────────────────────────────────────────────────────────────── + +// Guest inverts bit pattern: i32.xor with 0xFFFFFFFF on 4-byte chunks +#[test] +fn guest_inverts_i32_pattern() { + 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) + (local $i i32) + ;; Need at least 36 bytes (32 addr + 4 payload) + (if (i32.lt_u (local.get $len) (i32.const 36)) (then return)) + ;; XOR 4 bytes at ptr+32 with 0xFFFFFFFF + (i32.store (i32.add (local.get $ptr) (i32.const 32)) + (i32.xor + (i32.load (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 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 inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), &[0x12, 0x34, 0x56, 0x78])).unwrap(); + rt.tick(); + + let resp = inbox.try_recv().expect("inverted pattern"); + assert_eq!(resp.0, vec![0xED, 0xCB, 0xA9, 0x87]); +} + +// Native actor that spawns WASM and sends to it +struct NativeWasmSpawner2 { + engine: SharedEngine, + inbox_addr: ActorAddress, +} +impl ActorInterface for NativeWasmSpawner2 { + type Incoming = ByteMessage; + type Response = (); + fn handle(&mut self, ctx: &Ctx, _msg: ByteMessage) { + let actor = WasmActorBuilder::new(self.engine.clone(), guest_wasm("echo")) + .build().unwrap(); + let wasm_addr = ctx.spawn(actor).unwrap(); + let _ = ctx.send(wasm_addr, framed_msg(&self.inbox_addr, b"spawned-inline")); + } +} + +#[test] +fn native_spawns_wasm_and_sends_in_handler() { + let engine = SharedEngine::new().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + + let spawner = NativeWasmSpawner2 { + engine, + inbox_addr: *inbox.addr(), + }; + let s_addr = rt.spawn(spawner).unwrap(); + rt.send_to(s_addr, ByteMessage(vec![])).unwrap(); + rt.tick(); // spawner spawns WASM + sends to it + rt.tick(); // WASM echo processes message + rt.tick(); // ensure delivery + + let resp = inbox.try_recv().expect("spawned WASM should echo"); + assert_eq!(resp.0, b"spawned-inline"); +} + +// Guest with nested loop computing factorial(5) = 120 +#[test] +fn guest_factorial_loop() { + 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) + (local $result i32) + (local $n i32) + (local.set $result (i32.const 1)) + (local.set $n (i32.const 5)) + (block $exit + (loop $loop + (br_if $exit (i32.le_u (local.get $n) (i32.const 1))) + (local.set $result (i32.mul (local.get $result) (local.get $n))) + (local.set $n (i32.sub (local.get $n) (i32.const 1))) + (br $loop) + ) + ) + (i32.store (local.get $ptr) (local.get $result)) + ) + )"#; + 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![0u8; 4])).unwrap(); + rt.tick(); +} + +// Verify echo determinism: same input always produces same output +#[test] +fn echo_determinism_ten_runs() { + let engine = SharedEngine::new().unwrap(); + let payload = b"deterministic-test-payload"; + + let mut outputs = Vec::new(); + for _ in 0..10 { + let actor = WasmActorBuilder::new(engine.clone(), 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(), payload)).unwrap(); + rt.tick(); + outputs.push(inbox.try_recv().unwrap().0); + } + + assert!(outputs.iter().all(|o| o == &outputs[0]), + "all 10 runs should produce identical output"); } \ No newline at end of file