diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 0290749..f46c96e 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -11377,4 +11377,100 @@ fn guest_block_with_result_value() { let addr = rt.spawn(actor).unwrap(); rt.send_to(addr, ByteMessage(vec![0u8; 4])).unwrap(); rt.tick(); +} + +// ── Cycle 79 ───────────────────────────────────────────────────────────────── + +// Echo actor handles binary pattern: all zeros then all ones +#[test] +fn echo_zeros_then_ones() { + 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(); + + let zeros = vec![0u8; 256]; + let ones = vec![0xFF; 256]; + rt.send_to(addr, framed_msg(inbox.addr(), &zeros)).unwrap(); + rt.send_to(addr, framed_msg(inbox.addr(), &ones)).unwrap(); + rt.tick(); + + let r1 = inbox.try_recv().unwrap(); + let r2 = inbox.try_recv().unwrap(); + assert!(r1.0.iter().all(|&b| b == 0)); + assert!(r2.0.iter().all(|&b| b == 0xFF)); +} + +// Guest with 3 exported functions (only alloc and handle required) +#[test] +fn guest_with_extra_exported_function_and_init() { + 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)) + (func (export "init") (result i32) i32.const 42) + (func (export "cleanup")) + )"#; + 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(); +} + +// Property: double actor always sends exactly 2x copies +proptest! { + #[test] + fn prop_double_always_sends_two_copies( + payload in proptest::collection::vec(0u8..=255, 1..200) + ) { + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("double")).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(); + + let msgs: Vec> = std::iter::from_fn(|| inbox.try_recv().map(|m| m.0)).collect(); + assert_eq!(msgs.len(), 2, "double always produces 2 copies"); + assert_eq!(msgs[0], payload); + assert_eq!(msgs[1], payload); + } +} + +// Mix of echo, double, and silent actors — 10 of each +#[test] +fn thirty_mixed_actors_simultaneous() { + let engine = SharedEngine::new().unwrap(); + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + + // 10 echo actors + for _ in 0..10 { + 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(), b"e")).unwrap(); + } + // 10 double actors + for _ in 0..10 { + let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("double")).build().unwrap(); + let addr = rt.spawn(actor).unwrap(); + rt.send_to(addr, framed_msg(inbox.addr(), b"d")).unwrap(); + } + // 10 silent actors + for _ in 0..10 { + let actor = WasmActorBuilder::new(engine.clone(), guest_wasm("silent")).build().unwrap(); + let addr = rt.spawn(actor).unwrap(); + rt.send_to(addr, ByteMessage(b"s".to_vec())).unwrap(); + } + rt.tick(); + + let total = std::iter::from_fn(|| inbox.try_recv()).count(); + // 10 echo + 10*2 double + 0 silent = 30 + assert_eq!(total, 30); } \ No newline at end of file