diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index d1ba56f..0bddffc 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -2604,3 +2604,135 @@ fn module_with_custom_section_builds_and_works() { let received = inbox.try_recv().expect("module with custom section should work"); assert_eq!(received.0, vec![99]); } + +// ── Multiple engines: actors from different engines on same runtime ────────── + +#[test] +fn actors_from_different_engines_coexist() { + // Two actors built from separate SharedEngine instances. + // Verifies that engine isolation doesn't cause issues when actors + // share the same runtime. + let engine_a = SharedEngine::new().unwrap(); + let engine_b = SharedEngine::new().unwrap(); + let wasm_bytes = guest_wasm("echo"); + + let actor_a = WasmActorBuilder::new(engine_a, wasm_bytes.clone()).build().unwrap(); + let actor_b = WasmActorBuilder::new(engine_b, wasm_bytes).build().unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox_a = rt.new_inbox::().unwrap(); + let inbox_b = rt.new_inbox::().unwrap(); + + let addr_a = rt.spawn(actor_a).unwrap(); + let addr_b = rt.spawn(actor_b).unwrap(); + + rt.send_to(addr_a, framed_msg(inbox_a.addr(), b"engine-A")).unwrap(); + rt.send_to(addr_b, framed_msg(inbox_b.addr(), b"engine-B")).unwrap(); + rt.tick(); + + let recv_a = inbox_a.try_recv().expect("engine A actor should echo"); + let recv_b = inbox_b.try_recv().expect("engine B actor should echo"); + assert_eq!(recv_a.0, b"engine-A"); + assert_eq!(recv_b.0, b"engine-B"); +} + +// ── Error formatting: WasmActorError Display ──────────────────────────────── + +#[test] +fn error_display_formats_correctly() { + let missing = WasmActorError::MissingExport("memory"); + assert!(missing.to_string().contains("memory")); + assert!(missing.to_string().contains("missing")); + + let garbage = vec![0u8, 1, 2, 3]; + let engine = SharedEngine::new().unwrap(); + let build_result = WasmActorBuilder::new(engine, garbage).build(); + assert!(build_result.is_err()); + let wasmtime_err = build_result.err().unwrap(); + assert!(wasmtime_err.to_string().contains("wasmtime")); +} + +// ── Rapid lifecycle: spawn, process, stop, repeat ─────────────────────────── + +#[test] +fn rapid_spawn_process_stop_cycle() { + // Rapidly spawn, send, tick, stop, tick, repeat for 20 iterations. + // Tests that the runtime cleanly handles rapid WASM actor lifecycle. + let engine = SharedEngine::new().unwrap(); + let wasm_bytes = guest_wasm("echo"); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + + for i in 0u8..20 { + let actor = WasmActorBuilder::new(engine.clone(), wasm_bytes.clone()) + .build() + .unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), &[i])).unwrap(); + rt.tick(); + + let received = inbox.try_recv().expect("actor should echo before stop"); + assert_eq!(received.0, vec![i]); + + rt.stop_actor(addr).unwrap(); + rt.tick(); + rt.tick(); // cleanup + } +} + +// ── Stop-send race: send after stop_actor but before tick ─────────────────── + +#[test] +fn send_after_stop_before_tick_is_silently_dropped() { + // Stop an actor, then immediately send a message before ticking. + // The message should be silently dropped (actor is stopping). + 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(); + + // Verify it works first + rt.send_to(addr, framed_msg(inbox.addr(), b"alive")).unwrap(); + rt.tick(); + assert!(inbox.try_recv().is_some()); + + // Stop then immediately send before tick processes the stop + rt.stop_actor(addr).unwrap(); + // This send may or may not succeed depending on mailbox state + let _ = rt.send_to(addr, framed_msg(inbox.addr(), b"after-stop")); + rt.tick(); // processes stop signal, clears mailbox + rt.tick(); // cleanup + + // No response expected — either the send failed or the message was cleared + // The key assertion: no panic or corruption +} + +// ── SharedEngine Debug impl ───────────────────────────────────────────────── + +#[test] +fn shared_engine_debug_does_not_panic() { + let engine = SharedEngine::new().unwrap(); + let debug_str = format!("{:?}", engine); + assert!(debug_str.contains("SharedEngine")); +} + +// ── ByteMessage equality and clone ────────────────────────────────────────── + +#[test] +fn byte_message_traits() { + let msg1 = ByteMessage(vec![1, 2, 3]); + let msg2 = msg1.clone(); + assert_eq!(msg1, msg2); + + let msg3 = ByteMessage(vec![4, 5, 6]); + assert_ne!(msg1, msg3); + + let debug_str = format!("{:?}", msg1); + assert!(debug_str.contains("ByteMessage")); +}