diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index bc30325..c4556e0 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -11605,4 +11605,137 @@ fn broadcast_to_ten_actors() { let msgs: Vec> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); assert_eq!(msgs.len(), 10); assert!(msgs.iter().all(|m| m == b"broadcast")); +} + +// ── Cycle 81 ───────────────────────────────────────────────────────────────── + +// Guest that does bitwise NOT on each byte +#[test] +fn guest_bitwise_not_each_byte() { + 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) + (if (i32.lt_u (local.get $len) (i32.const 33)) (then return)) + ;; NOT each byte in the payload portion + (local.set $i (i32.const 32)) + (block $exit + (loop $loop + (br_if $exit (i32.ge_u (local.get $i) (local.get $len))) + (i32.store8 + (i32.add (local.get $ptr) (local.get $i)) + (i32.xor + (i32.load8_u (i32.add (local.get $ptr) (local.get $i))) + (i32.const 0xFF))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $loop) + ) + ) + (call $send + (local.get $ptr) + (i32.add (local.get $ptr) (i32.const 32)) + (i32.sub (local.get $len) (i32.const 32))) + ) + )"#; + 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(), &[0x00, 0xFF, 0x55, 0xAA])).unwrap(); + rt.tick(); + + let resp = inbox.try_recv().expect("NOT response"); + assert_eq!(resp.0, vec![0xFF, 0x00, 0xAA, 0x55]); +} + +// Stop actor during multi-threaded runtime (MT stop) +#[test] +fn stop_wasm_actor_on_mt_runtime() { + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); + let mut cfg = RuntimeConfig::default(); + cfg.num_threads = 2; + let rt = Runtime::new(cfg); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), b"mt")).unwrap(); + let handle = rt.run().unwrap(); + std::thread::sleep(std::time::Duration::from_millis(100)); + + // Drain inbox + let mut count = 0; + for _ in 0..20 { + count += std::iter::from_fn(|| inbox.try_recv()).count(); + if count >= 1 { break; } + std::thread::sleep(std::time::Duration::from_millis(50)); + } + handle.shutdown(); + assert_eq!(count, 1, "echo received on MT runtime"); +} + +// Guest with i64 extend operations +#[test] +fn guest_i64_extend_operations() { + 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) + ;; i64.extend_i32_s: sign-extend 32-bit -1 to 64-bit + (i64.store (local.get $ptr) + (i64.extend_i32_s (i32.const -1))) + ) + )"#; + 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; 8])).unwrap(); + rt.tick(); +} + +// 5 runtimes running sequentially, each with a WASM actor +#[test] +fn five_sequential_runtimes() { + let engine = SharedEngine::new().unwrap(); + for i in 0..5u8 { + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")).build().unwrap(); + let addr = rt.spawn(actor).unwrap(); + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + rt.tick(); + assert_eq!(inbox.try_recv().unwrap().0, vec![i]); + // runtime dropped here + } +} + +// Property: any combination of ticks and messages never panics +proptest! { + #[test] + fn prop_random_tick_message_interleaving( + ops in proptest::collection::vec( + proptest::bool::ANY, // true = send, false = tick + 1..30 + ) + ) { + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("silent")).build().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let addr = rt.spawn(actor).unwrap(); + + for send in &ops { + if *send { + let _ = rt.send_to(addr, ByteMessage(vec![1, 2, 3])); + } else { + rt.tick(); + } + } + } } \ No newline at end of file