diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index f51d2eb..524e418 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -9937,4 +9937,155 @@ proptest! { rt.tick(); } } +} + +// ── Cycle 68 — 300 TEST MILESTONE ──────────────────────────────────────────── + +// Guest that writes to memory offset 0 (data segment area) — valid operation +#[test] +fn guest_writes_to_offset_zero() { + 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) + (i32.store (i32.const 0) (i32.const 42)) + ) + )"#; + 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![1])).unwrap(); + rt.tick(); +} + +// Spawn 10 echo actors, send to all, verify all echo back +#[test] +fn ten_echo_actors_all_respond() { + let engine = SharedEngine::new().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + + let addrs: Vec<_> = (0..10) + .map(|_| { + let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("echo")) + .build().unwrap(); + rt.spawn(actor).unwrap() + }) + .collect(); + + for (i, addr) in addrs.iter().enumerate() { + rt.send_to(*addr, framed_msg(inbox.addr(), &[i as u8])).unwrap(); + } + rt.tick(); + + let mut msgs: Vec> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + msgs.sort(); + assert_eq!(msgs.len(), 10); + for i in 0..10 { + assert_eq!(msgs[i], vec![i as u8]); + } +} + +// Guest that does nothing in handle then sends on next message +#[test] +fn guest_alternates_between_silent_and_sending() { + let wat = r#"(module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (global $toggle (mut i32) (i32.const 0)) + (func (export "alloc") (param $len i32) (result i32) i32.const 1024) + (func (export "handle") (param $ptr i32) (param $len i32) + (if (i32.eqz (global.get $toggle)) + (then + ;; Silent on even calls + (global.set $toggle (i32.const 1)) + ) + (else + ;; Send on odd calls + (global.set $toggle (i32.const 0)) + (if (i32.ge_u (local.get $len) (i32.const 33)) + (then + (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(); + + // Send 6 messages — expect responses from messages 2, 4, 6 (1-indexed) + for i in 0..6 { + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + } + rt.tick(); + + let msgs: Vec> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + assert_eq!(msgs.len(), 3, "only odd calls send"); + assert_eq!(msgs[0], vec![1]); + assert_eq!(msgs[1], vec![3]); + assert_eq!(msgs[2], vec![5]); +} + +// Runtime with budget=1 processes exactly 1 message per actor per tick +#[test] +fn budget_one_processes_exactly_one_per_tick() { + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); + let mut cfg = RuntimeConfig::default(); + cfg.actor_message_budget = 1; + let rt = Runtime::new(cfg); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + // Queue 3 messages + for i in 0..3u8 { + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + } + + // Each tick should process exactly 1 + rt.tick(); + let t1: Vec<_> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + assert_eq!(t1.len(), 1, "budget=1 processes 1 per tick"); + assert_eq!(t1[0], vec![0]); + + rt.tick(); + let t2: Vec<_> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + assert_eq!(t2.len(), 1); + assert_eq!(t2[0], vec![1]); + + rt.tick(); + let t3: Vec<_> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + assert_eq!(t3.len(), 1); + assert_eq!(t3[0], vec![2]); +} + +// ByteMessage with exactly 32 bytes (address only, no payload for echo) +#[test] +fn echo_with_address_only_no_payload() { + 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(); + + // Exactly 32 bytes = dest address, 0 payload bytes + let msg = ByteMessage(inbox.addr().0.to_vec()); + rt.send_to(addr, msg).unwrap(); + rt.tick(); + + // Echo should send back empty payload + let resp = inbox.try_recv().expect("echo with 0-byte payload"); + assert!(resp.0.is_empty()); } \ No newline at end of file