use rsactor::{
message_handlers, spawn, Actor, ActorRef, AskHandler, TellHandler, WeakAskHandler,
WeakTellHandler,
};
fn init_test_logger() {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_test_writer()
.try_init();
}
#[derive(Actor)]
struct ActorA {
name: String,
}
#[derive(Actor)]
struct ActorB {
id: u32,
}
struct Ping;
struct GetName;
#[derive(Debug, Clone, PartialEq)]
struct Status {
name: String,
alive: bool,
}
#[message_handlers]
impl ActorA {
#[handler]
async fn handle_ping(&mut self, _msg: Ping, _: &ActorRef<Self>) {
}
#[handler]
async fn handle_get_name(&mut self, _msg: GetName, _: &ActorRef<Self>) -> Status {
Status {
name: self.name.clone(),
alive: true,
}
}
}
#[message_handlers]
impl ActorB {
#[handler]
async fn handle_ping(&mut self, _msg: Ping, _: &ActorRef<Self>) {
}
#[handler]
async fn handle_get_name(&mut self, _msg: GetName, _: &ActorRef<Self>) -> Status {
Status {
name: format!("ActorB-{}", self.id),
alive: true,
}
}
}
#[tokio::test]
async fn test_tell_handler_from_actor_ref() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "test".to_string(),
});
let handler: Box<dyn TellHandler<Ping>> = (&actor_ref).into();
handler.tell(Ping).await.expect("tell should succeed");
assert_eq!(handler.as_control().identity(), actor_ref.identity());
assert!(handler.as_control().is_alive());
actor_ref.stop().await.expect("stop should succeed");
let result = handle.await.expect("actor should complete");
assert!(result.stopped_normally());
}
#[tokio::test]
async fn test_tell_handler_multiple_actors_same_message() {
init_test_logger();
let (actor_a, handle_a) = spawn::<ActorA>(ActorA {
name: "A".to_string(),
});
let (actor_b, handle_b) = spawn::<ActorB>(ActorB { id: 42 });
let handlers: Vec<Box<dyn TellHandler<Ping>>> = vec![(&actor_a).into(), (&actor_b).into()];
for handler in &handlers {
handler.tell(Ping).await.expect("tell should succeed");
}
for handler in &handlers {
assert!(handler.as_control().is_alive());
}
actor_a.stop().await.expect("stop should succeed");
actor_b.stop().await.expect("stop should succeed");
let result_a = handle_a.await.expect("actor A should complete");
let result_b = handle_b.await.expect("actor B should complete");
assert!(result_a.stopped_normally());
assert!(result_b.stopped_normally());
}
#[tokio::test]
async fn test_tell_handler_clone_boxed() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "clone_test".to_string(),
});
let handler: Box<dyn TellHandler<Ping>> = (&actor_ref).into();
let handler_clone = handler.clone_boxed();
assert_eq!(
handler.as_control().identity(),
handler_clone.as_control().identity()
);
handler
.tell(Ping)
.await
.expect("original handler tell should succeed");
handler_clone
.tell(Ping)
.await
.expect("cloned handler tell should succeed");
actor_ref.stop().await.expect("stop should succeed");
let result = handle.await.expect("actor should complete");
assert!(result.stopped_normally());
}
#[tokio::test]
async fn test_tell_handler_clone_trait() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "clone_trait_test".to_string(),
});
let handlers: Vec<Box<dyn TellHandler<Ping>>> = vec![(&actor_ref).into()];
let handlers_clone = handlers.clone();
assert_eq!(
handlers[0].as_control().identity(),
handlers_clone[0].as_control().identity()
);
handlers[0].tell(Ping).await.expect("should work");
handlers_clone[0].tell(Ping).await.expect("should work");
actor_ref.stop().await.expect("stop should succeed");
let result = handle.await.expect("actor should complete");
assert!(result.stopped_normally());
}
#[tokio::test]
async fn test_tell_handler_debug() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "debug_test".to_string(),
});
let handler: Box<dyn TellHandler<Ping>> = (&actor_ref).into();
let debug_str = format!("{:?}", handler);
assert!(debug_str.contains("TellHandler"));
assert!(debug_str.contains("identity"));
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_tell_handler_kill() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "kill_test".to_string(),
});
let _handler: Box<dyn TellHandler<Ping>> = (&actor_ref).into();
actor_ref.kill().expect("kill should succeed");
let result = handle.await.expect("actor should complete");
assert!(result.was_killed());
}
#[tokio::test]
async fn test_ask_handler_from_actor_ref() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "ask_test".to_string(),
});
let handler: Box<dyn AskHandler<GetName, Status>> = (&actor_ref).into();
let status = handler.ask(GetName).await.expect("ask should succeed");
assert_eq!(status.name, "ask_test");
assert!(status.alive);
actor_ref.stop().await.expect("stop should succeed");
let result = handle.await.expect("actor should complete");
assert!(result.stopped_normally());
}
#[tokio::test]
async fn test_ask_handler_multiple_actors_same_reply_type() {
init_test_logger();
let (actor_a, handle_a) = spawn::<ActorA>(ActorA {
name: "ActorA".to_string(),
});
let (actor_b, handle_b) = spawn::<ActorB>(ActorB { id: 100 });
let handlers: Vec<Box<dyn AskHandler<GetName, Status>>> =
vec![(&actor_a).into(), (&actor_b).into()];
let mut responses = Vec::new();
for handler in &handlers {
let status = handler.ask(GetName).await.expect("ask should succeed");
responses.push(status);
}
assert_eq!(responses[0].name, "ActorA");
assert_eq!(responses[1].name, "ActorB-100");
actor_a.stop().await.expect("stop should succeed");
actor_b.stop().await.expect("stop should succeed");
let _ = handle_a.await;
let _ = handle_b.await;
}
#[tokio::test]
async fn test_ask_handler_clone() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "clone_ask".to_string(),
});
let handler: Box<dyn AskHandler<GetName, Status>> = (&actor_ref).into();
let handler_clone = handler.clone();
let status1 = handler.ask(GetName).await.expect("should work");
let status2 = handler_clone.ask(GetName).await.expect("should work");
assert_eq!(status1, status2);
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_weak_tell_handler_from_actor_weak() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "weak_test".to_string(),
});
let actor_weak = ActorRef::downgrade(&actor_ref);
let weak_handler: Box<dyn WeakTellHandler<Ping>> = actor_weak.into();
assert_eq!(
weak_handler.as_weak_control().identity(),
actor_ref.identity()
);
assert!(weak_handler.as_weak_control().is_alive());
let strong = weak_handler
.upgrade()
.expect("upgrade should succeed while actor is alive");
strong.tell(Ping).await.expect("tell should succeed");
actor_ref.stop().await.expect("stop should succeed");
let result = handle.await.expect("actor should complete");
assert!(result.stopped_normally());
}
#[tokio::test]
async fn test_weak_tell_handler_upgrade_after_stop() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "weak_upgrade_test".to_string(),
});
let actor_weak = ActorRef::downgrade(&actor_ref);
let weak_handler: Box<dyn WeakTellHandler<Ping>> = actor_weak.into();
actor_ref.stop().await.expect("stop should succeed");
drop(actor_ref);
let _ = handle.await;
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
assert!(weak_handler.upgrade().is_none());
assert!(!weak_handler.as_weak_control().is_alive());
}
#[tokio::test]
async fn test_weak_tell_handler_multiple_actors() {
init_test_logger();
let (actor_a, handle_a) = spawn::<ActorA>(ActorA {
name: "WeakA".to_string(),
});
let (actor_b, handle_b) = spawn::<ActorB>(ActorB { id: 50 });
let weak_handlers: Vec<Box<dyn WeakTellHandler<Ping>>> = vec![
ActorRef::downgrade(&actor_a).into(),
ActorRef::downgrade(&actor_b).into(),
];
for weak_handler in &weak_handlers {
if let Some(strong) = weak_handler.upgrade() {
strong.tell(Ping).await.expect("tell should succeed");
}
}
actor_a.stop().await.expect("stop should succeed");
actor_b.stop().await.expect("stop should succeed");
let _ = handle_a.await;
let _ = handle_b.await;
}
#[tokio::test]
async fn test_weak_tell_handler_batch_operations() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "batch_test".to_string(),
});
let weak_handler: Box<dyn WeakTellHandler<Ping>> = ActorRef::downgrade(&actor_ref).into();
if let Some(strong) = weak_handler.upgrade() {
strong.tell(Ping).await.expect("should work");
strong.tell(Ping).await.expect("should work");
strong.tell(Ping).await.expect("should work");
}
actor_ref.stop().await.expect("stop should succeed");
let result = handle.await.expect("actor should complete");
assert!(result.stopped_normally());
}
#[tokio::test]
async fn test_weak_tell_handler_clone() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "weak_clone".to_string(),
});
let weak_handler: Box<dyn WeakTellHandler<Ping>> = ActorRef::downgrade(&actor_ref).into();
let weak_clone = weak_handler.clone();
assert_eq!(
weak_handler.as_weak_control().identity(),
weak_clone.as_weak_control().identity()
);
assert!(weak_handler.upgrade().is_some());
assert!(weak_clone.upgrade().is_some());
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_weak_ask_handler_from_actor_weak() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "weak_ask".to_string(),
});
let weak_handler: Box<dyn WeakAskHandler<GetName, Status>> =
ActorRef::downgrade(&actor_ref).into();
let strong = weak_handler.upgrade().expect("upgrade should succeed");
let status = strong.ask(GetName).await.expect("ask should succeed");
assert_eq!(status.name, "weak_ask");
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_weak_ask_handler_multiple_actors() {
init_test_logger();
let (actor_a, handle_a) = spawn::<ActorA>(ActorA {
name: "WeakAskA".to_string(),
});
let (actor_b, handle_b) = spawn::<ActorB>(ActorB { id: 77 });
let weak_handlers: Vec<Box<dyn WeakAskHandler<GetName, Status>>> = vec![
ActorRef::downgrade(&actor_a).into(),
ActorRef::downgrade(&actor_b).into(),
];
let mut responses = Vec::new();
for weak_handler in &weak_handlers {
if let Some(strong) = weak_handler.upgrade() {
let status = strong.ask(GetName).await.expect("ask should succeed");
responses.push(status);
}
}
assert_eq!(responses.len(), 2);
assert_eq!(responses[0].name, "WeakAskA");
assert_eq!(responses[1].name, "ActorB-77");
actor_a.stop().await.expect("stop should succeed");
actor_b.stop().await.expect("stop should succeed");
let _ = handle_a.await;
let _ = handle_b.await;
}
#[tokio::test]
async fn test_tell_handler_as_control() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "as_control_test".to_string(),
});
let handler: Box<dyn TellHandler<Ping>> = (&actor_ref).into();
let control = handler.as_control();
assert_eq!(actor_ref.identity(), control.identity());
assert!(control.is_alive());
control.stop().await.expect("stop should succeed");
let result = handle.await.expect("actor should complete");
assert!(result.stopped_normally());
}
#[tokio::test]
async fn test_ask_handler_as_control() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "ask_as_control".to_string(),
});
let handler: Box<dyn AskHandler<GetName, Status>> = (&actor_ref).into();
let control = handler.as_control();
assert_eq!(actor_ref.identity(), control.identity());
let status = handler.ask(GetName).await.expect("ask should succeed");
assert_eq!(status.name, "ask_as_control");
control.kill().expect("kill should succeed");
let result = handle.await.expect("actor should complete");
assert!(result.was_killed());
}
#[tokio::test]
async fn test_handler_as_control_multiple_actors() {
init_test_logger();
let (actor_a, handle_a) = spawn::<ActorA>(ActorA {
name: "ControlA".to_string(),
});
let (actor_b, handle_b) = spawn::<ActorB>(ActorB { id: 99 });
let tell_handler_a: Box<dyn TellHandler<Ping>> = (&actor_a).into();
let tell_handler_b: Box<dyn TellHandler<Ping>> = (&actor_b).into();
let controls: Vec<Box<dyn rsactor::ActorControl>> = vec![
tell_handler_a.as_control().clone_boxed(),
tell_handler_b.as_control().clone_boxed(),
];
for control in &controls {
control.stop().await.expect("stop should succeed");
}
let result_a = handle_a.await.expect("actor A should complete");
let result_b = handle_b.await.expect("actor B should complete");
assert!(result_a.stopped_normally());
assert!(result_b.stopped_normally());
}
#[tokio::test]
async fn test_downgrade_from_tell_handler() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "downgrade_test".to_string(),
});
let strong_handler: Box<dyn TellHandler<Ping>> = (&actor_ref).into();
let weak_handler: Box<dyn WeakTellHandler<Ping>> = strong_handler.downgrade();
assert_eq!(
strong_handler.as_control().identity(),
weak_handler.as_weak_control().identity()
);
assert!(weak_handler.as_weak_control().is_alive());
let upgraded = weak_handler.upgrade().expect("should be able to upgrade");
upgraded.tell(Ping).await.expect("tell should work");
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_downgrade_from_ask_handler() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "ask_downgrade".to_string(),
});
let strong_handler: Box<dyn AskHandler<GetName, Status>> = (&actor_ref).into();
let weak_handler: Box<dyn WeakAskHandler<GetName, Status>> = strong_handler.downgrade();
assert_eq!(
strong_handler.as_control().identity(),
weak_handler.as_weak_control().identity()
);
let upgraded = weak_handler.upgrade().expect("should be able to upgrade");
let status = upgraded.ask(GetName).await.expect("ask should work");
assert_eq!(status.name, "ask_downgrade");
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_roundtrip_strong_weak_strong() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "roundtrip".to_string(),
});
let strong1: Box<dyn TellHandler<Ping>> = (&actor_ref).into();
let weak: Box<dyn WeakTellHandler<Ping>> = strong1.downgrade();
let strong2 = weak.upgrade().expect("should upgrade");
assert_eq!(
strong1.as_control().identity(),
weak.as_weak_control().identity()
);
assert_eq!(
weak.as_weak_control().identity(),
strong2.as_control().identity()
);
strong1.tell(Ping).await.expect("should work");
strong2.tell(Ping).await.expect("should work");
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_from_actor_ref_ownership() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "ownership".to_string(),
});
let actor_ref_clone = actor_ref.clone();
let handler: Box<dyn TellHandler<Ping>> = actor_ref_clone.into();
handler.tell(Ping).await.expect("should work");
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_from_actor_ref_reference() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "reference".to_string(),
});
let handler: Box<dyn TellHandler<Ping>> = (&actor_ref).into();
handler.tell(Ping).await.expect("should work");
actor_ref.tell(Ping).await.expect("should work");
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_from_actor_weak_ownership() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "weak_ownership".to_string(),
});
let actor_weak = ActorRef::downgrade(&actor_ref);
let handler: Box<dyn WeakTellHandler<Ping>> = actor_weak.into();
assert!(handler.upgrade().is_some());
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_from_actor_weak_reference() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "weak_reference".to_string(),
});
let actor_weak = ActorRef::downgrade(&actor_ref);
let handler: Box<dyn WeakTellHandler<Ping>> = (&actor_weak).into();
assert!(handler.upgrade().is_some());
assert!(actor_weak.upgrade().is_some());
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_tell_handler_with_timeout() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "timeout_test".to_string(),
});
let handler: Box<dyn TellHandler<Ping>> = (&actor_ref).into();
handler
.tell_with_timeout(Ping, std::time::Duration::from_secs(5))
.await
.expect("tell_with_timeout should succeed");
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_ask_handler_with_timeout() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "ask_timeout".to_string(),
});
let handler: Box<dyn AskHandler<GetName, Status>> = (&actor_ref).into();
let status = handler
.ask_with_timeout(GetName, std::time::Duration::from_secs(5))
.await
.expect("ask_with_timeout should succeed");
assert_eq!(status.name, "ask_timeout");
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
#[tokio::test]
async fn test_all_handlers_debug_formatting() {
init_test_logger();
let (actor_ref, handle) = spawn::<ActorA>(ActorA {
name: "debug_all".to_string(),
});
let tell_handler: Box<dyn TellHandler<Ping>> = (&actor_ref).into();
let ask_handler: Box<dyn AskHandler<GetName, Status>> = (&actor_ref).into();
let weak_tell_handler: Box<dyn WeakTellHandler<Ping>> = ActorRef::downgrade(&actor_ref).into();
let weak_ask_handler: Box<dyn WeakAskHandler<GetName, Status>> =
ActorRef::downgrade(&actor_ref).into();
let tell_debug = format!("{:?}", tell_handler);
let ask_debug = format!("{:?}", ask_handler);
let weak_tell_debug = format!("{:?}", weak_tell_handler);
let weak_ask_debug = format!("{:?}", weak_ask_handler);
assert!(tell_debug.contains("TellHandler"));
assert!(ask_debug.contains("AskHandler"));
assert!(weak_tell_debug.contains("WeakTellHandler"));
assert!(weak_ask_debug.contains("WeakAskHandler"));
actor_ref.stop().await.expect("stop should succeed");
let _ = handle.await;
}
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn test_handlers_are_send_sync() {
assert_send_sync::<Box<dyn TellHandler<Ping>>>();
assert_send_sync::<Box<dyn AskHandler<GetName, Status>>>();
assert_send_sync::<Box<dyn WeakTellHandler<Ping>>>();
assert_send_sync::<Box<dyn WeakAskHandler<GetName, Status>>>();
}