use std::sync::Arc; use std::thread; use std::time::Duration; use swactor::{ actor::{ActorAddress, ActorInterface}, runtime::{Context, Inbox, Runtime, RuntimeFlavor}, }; // ============================================================================ // Test Helpers // ============================================================================ #[derive(Clone)] struct PingMessage { reply_to: ActorAddress, } #[derive(Clone)] struct PongMessage; struct PongActor; impl ActorInterface for PongActor { type Incoming = PingMessage; type Response = PongMessage; fn handle(&mut self, ctx: &Context, msg: PingMessage) { let _ = ctx.send_to(msg.reply_to, PongMessage); } } /// An actor that forwards messages to another address struct ForwarderActor { target: ActorAddress, } #[derive(Clone)] struct ForwardMessage(usize); impl ActorInterface for ForwarderActor { type Incoming = ForwardMessage; type Response = (); fn handle(&mut self, ctx: &Context, msg: ForwardMessage) { let _ = ctx.send_to(self.target, msg); } } #[test] fn test_single_threaded_ping_pong() { let rt = Runtime::new(100, RuntimeFlavor::SingleThreaded); let inbox: Inbox = rt.new_inbox(); let pong_addr = rt.spawn(PongActor).expect("spawn pong"); // Send ping rt.send_to( pong_addr, PingMessage { reply_to: *inbox.addr(), }, ) .unwrap(); // Tick until we get a response for _ in 0..10 { rt.tick(); if inbox.try_recv().is_some() { return; // Success! } } panic!("Did not receive pong response"); } #[test] fn test_single_threaded_message_chain() { let rt = Runtime::new(100, RuntimeFlavor::SingleThreaded); let inbox: Inbox = rt.new_inbox(); // Create a chain: A -> B -> C -> inbox let c_addr = rt .spawn(ForwarderActor { target: *inbox.addr(), }) .unwrap(); let b_addr = rt.spawn(ForwarderActor { target: c_addr }).unwrap(); let a_addr = rt.spawn(ForwarderActor { target: b_addr }).unwrap(); // Send message to start of chain rt.send_to(a_addr, ForwardMessage(42)).unwrap(); // Tick until message arrives for _ in 0..20 { rt.tick(); if let Some(ForwardMessage(val)) = inbox.try_recv() { assert_eq!(val, 42); return; } } panic!("Message did not traverse the chain"); } #[test] fn test_multithreaded_message_passing() { let rt = Arc::new(Runtime::new( 1000, RuntimeFlavor::Multithreaded { workers: 4 }, )); let inbox: Inbox = rt.new_inbox(); // Create a longer chain to exercise multi-threading let mut target = *inbox.addr(); for _ in 0..20 { target = rt.spawn(ForwarderActor { target }).unwrap(); } let start_addr = target; // Send message rt.send_to(start_addr, ForwardMessage(999)).unwrap(); // Spawn thread to check for result and shutdown let rt_clone = Arc::clone(&rt); let inbox_check = thread::spawn(move || { for _ in 0..100 { thread::sleep(Duration::from_millis(10)); if let Some(ForwardMessage(val)) = inbox.try_recv() { rt_clone.shutdown(); return Some(val); } } rt_clone.shutdown(); None }); rt.run(); let result = inbox_check.join().unwrap(); assert_eq!(result, Some(999)); } #[test] fn test_is_running_flag() { let rt = Runtime::new(100, RuntimeFlavor::SingleThreaded); // Before run(), is_running should be false assert!(!rt.is_running()); // After shutdown before run, still false rt.shutdown(); assert!(!rt.is_running()); }