use rsactor::{spawn, Actor, ActorRef, Error, Identity};
use std::time::Duration;
#[test]
fn test_is_retryable_for_all_variants() {
let identity = Identity::new(1, "TestActor");
let timeout_err = Error::Timeout {
identity,
timeout: Duration::from_secs(1),
operation: "ask".into(),
};
assert!(timeout_err.is_retryable());
let send_err = Error::Send {
identity,
details: "channel closed".into(),
};
assert!(!send_err.is_retryable());
let receive_err = Error::Receive {
identity,
details: "channel closed".into(),
};
assert!(!receive_err.is_retryable());
let downcast_err = Error::Downcast {
identity,
expected_type: "String".into(),
};
assert!(!downcast_err.is_retryable());
let runtime_err = Error::Runtime {
identity,
details: "test error".into(),
};
assert!(!runtime_err.is_retryable());
let mailbox_err = Error::MailboxCapacity {
message: "invalid capacity".into(),
};
assert!(!mailbox_err.is_retryable());
}
#[test]
fn test_all_errors_have_debugging_tips() {
let identity = Identity::new(1, "TestActor");
let errors: Vec<Error> = vec![
Error::Send {
identity,
details: "test".into(),
},
Error::Receive {
identity,
details: "test".into(),
},
Error::Timeout {
identity,
timeout: Duration::from_secs(1),
operation: "ask".into(),
},
Error::Downcast {
identity,
expected_type: "String".into(),
},
Error::Runtime {
identity,
details: "test".into(),
},
Error::MailboxCapacity {
message: "test".into(),
},
];
for err in &errors {
let tips = err.debugging_tips();
assert!(!tips.is_empty(), "Missing tips for: {:?}", err);
for tip in tips {
assert!(tip.len() > 10, "Tip too short to be useful: {}", tip);
}
}
}
#[test]
fn test_runtime_error_tips_are_specific() {
let identity = Identity::new(1, "TestActor");
let err = Error::Runtime {
identity,
details: "test".into(),
};
let tips = err.debugging_tips();
let tips_text = tips.join(" ");
assert!(
tips_text.contains("on_start") || tips_text.contains("on_run"),
"Runtime tips should mention lifecycle methods"
);
assert!(
tips_text.contains("ActorResult"),
"Runtime tips should mention ActorResult"
);
}
#[test]
fn test_downcast_error_debugging_tips() {
let identity = Identity::new(1, "TestActor");
let err = Error::Downcast {
identity,
expected_type: "String".into(),
};
let tips = err.debugging_tips();
assert!(!tips.is_empty(), "Downcast error should have tips");
let tips_text = tips.join(" ");
assert!(
tips_text.contains("Message") || tips_text.contains("handler"),
"Downcast tips should mention Message trait or handler"
);
}
#[test]
fn test_mailbox_capacity_error_tips() {
let err = Error::MailboxCapacity {
message: "capacity must be greater than 0".into(),
};
let tips = err.debugging_tips();
assert!(
!tips.is_empty(),
"MailboxCapacity should have debugging tips"
);
let tips_text = tips.join(" ");
assert!(
tips_text.contains("greater than 0") || tips_text.contains("capacity"),
"Tips should mention capacity requirements"
);
assert!(
tips_text.contains("set_default_mailbox_capacity") || tips_text.contains("once"),
"Tips should mention set_default_mailbox_capacity behavior"
);
}
#[test]
fn test_error_display_all_variants() {
let identity = Identity::new(1, "TestActor");
let errors = vec![
Error::Send {
identity,
details: "channel closed".into(),
},
Error::Receive {
identity,
details: "reply dropped".into(),
},
Error::Timeout {
identity,
timeout: Duration::from_secs(5),
operation: "ask".into(),
},
Error::Downcast {
identity,
expected_type: "String".into(),
},
Error::Runtime {
identity,
details: "panic in handler".into(),
},
Error::MailboxCapacity {
message: "capacity must be > 0".into(),
},
];
for err in &errors {
let display = format!("{}", err);
assert!(!display.is_empty(), "Display should not be empty");
assert!(display.len() > 5, "Display should be descriptive");
}
}
#[derive(Actor)]
struct TestActor;
struct Ping;
#[rsactor::message_handlers]
impl TestActor {
#[handler]
async fn handle(&mut self, _: Ping, _: &ActorRef<Self>) {}
}
#[tokio::test]
async fn test_dead_letter_on_stopped_actor() {
let initial = rsactor::dead_letter_count();
let (actor_ref, handle) = spawn::<TestActor>(TestActor);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
let result = actor_ref.tell(Ping).await;
assert!(result.is_err());
assert!(
rsactor::dead_letter_count() > initial,
"Dead letter should have been recorded"
);
}
#[tokio::test]
async fn test_dead_letter_count_increments() {
let initial = rsactor::dead_letter_count();
let (actor_ref, handle) = spawn::<TestActor>(TestActor);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
for _ in 0..3 {
let _ = actor_ref.tell(Ping).await;
}
assert!(
rsactor::dead_letter_count() - initial >= 3,
"Should have recorded at least 3 dead letters"
);
}
#[tokio::test]
async fn test_dead_letter_after_full_stop() {
let initial = rsactor::dead_letter_count();
let (actor_ref, handle) = spawn::<TestActor>(TestActor);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
let result = actor_ref.tell(Ping).await;
assert!(result.is_err());
assert!(
rsactor::dead_letter_count() > initial,
"Dead letter should be recorded after actor stopped"
);
}
#[tokio::test]
async fn test_dead_letter_multiple_actors_isolation() {
let initial = rsactor::dead_letter_count();
let (actor1, handle1) = spawn::<TestActor>(TestActor);
let (actor2, handle2) = spawn::<TestActor>(TestActor);
actor1.stop().await.unwrap();
handle1.await.unwrap();
let before_sends = rsactor::dead_letter_count();
let _ = actor1.tell(Ping).await;
let result = actor2.tell(Ping).await;
assert!(result.is_ok(), "Message to running actor should succeed");
assert!(
rsactor::dead_letter_count() - before_sends >= 1,
"Stopped actor should generate at least 1 dead letter"
);
actor2.stop().await.unwrap();
handle2.await.unwrap();
assert!(
rsactor::dead_letter_count() > initial,
"Should have recorded dead letters"
);
}
#[tokio::test]
async fn test_dead_letter_with_timeout() {
let initial = rsactor::dead_letter_count();
#[derive(Actor)]
struct SlowActor;
struct SlowPing;
#[rsactor::message_handlers]
impl SlowActor {
#[handler]
async fn handle(&mut self, _: SlowPing, _: &ActorRef<Self>) {
tokio::time::sleep(Duration::from_secs(10)).await;
}
}
let (actor_ref, handle) = rsactor::spawn_with_mailbox_capacity::<SlowActor>(SlowActor, 1);
let _ = actor_ref.tell(SlowPing).await;
let result = actor_ref
.tell_with_timeout(SlowPing, Duration::from_millis(10))
.await;
if result.is_err() {
assert!(
rsactor::dead_letter_count() > initial,
"Timeout should record dead letter"
);
}
actor_ref.kill().unwrap();
let _ = handle.await;
}
#[tokio::test]
async fn test_dead_letter_blocking_tell() {
let initial = rsactor::dead_letter_count();
let (actor_ref, handle) = spawn::<TestActor>(TestActor);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
let actor_ref_clone = actor_ref.clone();
let result = tokio::task::spawn_blocking(move || actor_ref_clone.blocking_tell(Ping, None))
.await
.unwrap();
assert!(result.is_err());
assert!(
rsactor::dead_letter_count() > initial,
"blocking_tell should record dead letter"
);
}
#[tokio::test]
async fn test_dead_letter_ask_with_timeout() {
let initial = rsactor::dead_letter_count();
#[derive(Actor)]
struct SlowAskActor;
struct SlowAskPing;
#[rsactor::message_handlers]
impl SlowAskActor {
#[handler]
async fn handle(&mut self, _: SlowAskPing, _: &ActorRef<Self>) -> String {
tokio::time::sleep(Duration::from_secs(10)).await;
"pong".to_string()
}
}
let (actor_ref, handle) = spawn::<SlowAskActor>(SlowAskActor);
let result: Result<String, _> = actor_ref
.ask_with_timeout(SlowAskPing, Duration::from_millis(10))
.await;
assert!(result.is_err());
assert!(
rsactor::dead_letter_count() > initial,
"ask_with_timeout should record dead letter on timeout"
);
actor_ref.kill().unwrap();
let _ = handle.await;
}
#[tokio::test]
async fn test_dead_letter_blocking_ask() {
let initial = rsactor::dead_letter_count();
#[derive(Actor)]
struct BlockingAskActor;
struct BlockingPing;
#[rsactor::message_handlers]
impl BlockingAskActor {
#[handler]
async fn handle(&mut self, _: BlockingPing, _: &ActorRef<Self>) -> String {
"pong".to_string()
}
}
let (actor_ref, handle) = spawn::<BlockingAskActor>(BlockingAskActor);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
let actor_ref_clone = actor_ref.clone();
let result: Result<String, _> =
tokio::task::spawn_blocking(move || actor_ref_clone.blocking_ask(BlockingPing, None))
.await
.unwrap();
assert!(result.is_err());
assert!(
rsactor::dead_letter_count() > initial,
"blocking_ask should record dead letter"
);
}
#[tokio::test]
async fn test_dead_letter_ask_on_stopped_actor() {
let initial = rsactor::dead_letter_count();
#[derive(Actor)]
struct AskActor;
struct AskPing;
#[rsactor::message_handlers]
impl AskActor {
#[handler]
async fn handle(&mut self, _: AskPing, _: &ActorRef<Self>) -> String {
"pong".to_string()
}
}
let (actor_ref, handle) = spawn::<AskActor>(AskActor);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
let result: Result<String, _> = actor_ref.ask(AskPing).await;
assert!(result.is_err());
assert!(
rsactor::dead_letter_count() > initial,
"ask on stopped actor should record dead letter"
);
}
#[tokio::test]
async fn test_dead_letter_concurrent_asks_to_stopped_actor() {
#[derive(Actor)]
struct ConcurrentActor;
struct ConcurrentPing;
#[rsactor::message_handlers]
impl ConcurrentActor {
#[handler]
async fn handle(&mut self, _: ConcurrentPing, _: &ActorRef<Self>) -> String {
"pong".to_string()
}
}
let (actor_ref, handle) = spawn::<ConcurrentActor>(ConcurrentActor);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
let before_sends = rsactor::dead_letter_count();
let mut handles = vec![];
for _ in 0..5 {
let actor = actor_ref.clone();
handles.push(tokio::spawn(async move {
let _: Result<String, _> = actor.ask(ConcurrentPing).await;
}));
}
for h in handles {
h.await.unwrap();
}
assert!(
rsactor::dead_letter_count() - before_sends >= 5,
"All 5 concurrent asks should record dead letters"
);
}
#[tokio::test]
async fn test_retry_pattern_with_is_retryable() {
#[derive(Actor)]
struct RetryActor {
call_count: u32,
}
struct RetryPing;
#[rsactor::message_handlers]
impl RetryActor {
#[handler]
async fn handle(&mut self, _: RetryPing, _: &ActorRef<Self>) -> u32 {
self.call_count += 1;
self.call_count
}
}
let (actor_ref, handle) = spawn::<RetryActor>(RetryActor { call_count: 0 });
async fn send_with_retry(
actor: &ActorRef<RetryActor>,
max_attempts: usize,
) -> Result<u32, rsactor::Error> {
let mut attempts = 0;
loop {
match actor
.ask_with_timeout(RetryPing, Duration::from_millis(100))
.await
{
Ok(result) => return Ok(result),
Err(e) if e.is_retryable() && attempts < max_attempts => {
attempts += 1;
tokio::time::sleep(Duration::from_millis(10)).await;
}
Err(e) => return Err(e),
}
}
}
let result = send_with_retry(&actor_ref, 3).await;
assert!(result.is_ok());
actor_ref.stop().await.unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn test_dead_letter_race_condition() {
let initial = rsactor::dead_letter_count();
let (actor_ref, handle) = spawn::<TestActor>(TestActor);
actor_ref.stop().await.unwrap();
let result = actor_ref.tell(Ping).await;
handle.await.unwrap();
if result.is_err() {
assert!(
rsactor::dead_letter_count() > initial,
"Dead letter should be recorded when send fails"
);
}
}
#[tokio::test]
async fn test_dead_letter_reply_dropped() {
let initial = rsactor::dead_letter_count();
#[derive(Actor)]
struct PanicActor;
struct PanicPing;
#[rsactor::message_handlers]
impl PanicActor {
#[handler]
async fn handle(&mut self, _: PanicPing, _: &ActorRef<Self>) -> String {
panic!("intentional panic before reply");
}
}
let (actor_ref, handle) = spawn::<PanicActor>(PanicActor);
let result: Result<String, _> = actor_ref.ask(PanicPing).await;
assert!(result.is_err());
let _ = handle.await;
assert!(
rsactor::dead_letter_count() > initial,
"Reply dropped scenario should record dead letter"
);
}
#[tokio::test]
async fn test_dead_letter_timeout_while_stopping() {
let initial = rsactor::dead_letter_count();
#[derive(Actor)]
struct RaceActor;
struct RacePing;
#[rsactor::message_handlers]
impl RaceActor {
#[handler]
async fn handle(&mut self, _: RacePing, _: &ActorRef<Self>) {
tokio::time::sleep(Duration::from_secs(10)).await;
}
}
let (actor_ref, handle) = spawn::<RaceActor>(RaceActor);
let actor_clone = actor_ref.clone();
let timeout_handle = tokio::spawn(async move {
actor_clone
.tell_with_timeout(RacePing, Duration::from_millis(50))
.await
});
tokio::time::sleep(Duration::from_millis(10)).await;
actor_ref.stop().await.unwrap();
let result = timeout_handle.await.unwrap();
let _ = handle.await;
if result.is_err() {
assert!(
rsactor::dead_letter_count() > initial,
"Dead letter should be recorded when operation fails"
);
}
}
#[tokio::test]
async fn test_dead_letter_blocking_ask_reply_dropped() {
let initial = rsactor::dead_letter_count();
#[derive(Actor)]
struct PanicBlockingActor;
struct PanicBlockingPing;
#[rsactor::message_handlers]
impl PanicBlockingActor {
#[handler]
async fn handle(&mut self, _: PanicBlockingPing, _: &ActorRef<Self>) -> String {
panic!("intentional panic in blocking_ask test");
}
}
let (actor_ref, handle) = spawn::<PanicBlockingActor>(PanicBlockingActor);
let actor_ref_clone = actor_ref.clone();
let result: Result<String, _> =
tokio::task::spawn_blocking(move || actor_ref_clone.blocking_ask(PanicBlockingPing, None))
.await
.unwrap();
assert!(result.is_err());
let _ = handle.await;
assert!(
rsactor::dead_letter_count() > initial,
"blocking_ask with panic should record dead letter"
);
}
#[test]
fn test_dead_letter_reason_display() {
use rsactor::DeadLetterReason;
assert_eq!(
format!("{}", DeadLetterReason::ActorStopped),
"actor stopped"
);
assert_eq!(format!("{}", DeadLetterReason::Timeout), "timeout");
assert_eq!(
format!("{}", DeadLetterReason::ReplyDropped),
"reply dropped"
);
}
#[tokio::test]
async fn test_error_join_display() {
use std::error::Error as StdError;
let handle = tokio::spawn(async {
panic!("test panic for JoinError");
});
let join_error = handle.await.unwrap_err();
let identity = Identity::new(1, "TestActor");
let error = Error::Join {
identity,
source: join_error,
};
let display = format!("{}", error);
assert!(display.contains("Failed to join"));
assert!(display.contains("TestActor"));
assert!(error.source().is_some(), "Join error should have a source");
}
#[test]
fn test_error_source_returns_none_for_non_join() {
use std::error::Error as StdError;
let identity = Identity::new(1, "TestActor");
let errors: Vec<Error> = vec![
Error::Send {
identity,
details: "test".into(),
},
Error::Receive {
identity,
details: "test".into(),
},
Error::Timeout {
identity,
timeout: Duration::from_secs(1),
operation: "ask".into(),
},
Error::Downcast {
identity,
expected_type: "String".into(),
},
Error::Runtime {
identity,
details: "test".into(),
},
Error::MailboxCapacity {
message: "test".into(),
},
];
for err in &errors {
assert!(
err.source().is_none(),
"Non-Join error {:?} should have no source",
err
);
}
}
#[tokio::test]
async fn test_error_join_debugging_tips() {
let handle = tokio::spawn(async {
panic!("test panic for debugging tips");
});
let join_error = handle.await.unwrap_err();
let identity = Identity::new(1, "TestActor");
let error = Error::Join {
identity,
source: join_error,
};
let tips = error.debugging_tips();
assert!(!tips.is_empty(), "Join error should have debugging tips");
let tips_text = tips.join(" ");
assert!(
tips_text.contains("panic") || tips_text.contains("cancelled"),
"Join tips should mention panic or cancellation"
);
assert!(
tips_text.contains("RUST_BACKTRACE"),
"Join tips should mention RUST_BACKTRACE"
);
}
#[tokio::test]
async fn test_error_join_is_not_retryable() {
let handle = tokio::spawn(async {
panic!("test panic for retryable check");
});
let join_error = handle.await.unwrap_err();
let identity = Identity::new(1, "TestActor");
let error = Error::Join {
identity,
source: join_error,
};
assert!(!error.is_retryable(), "Join error should not be retryable");
}
#[tokio::test]
async fn test_kill_when_terminate_channel_full() {
#[derive(Actor)]
struct SlowStopActor;
struct SlowMessage;
#[rsactor::message_handlers]
impl SlowStopActor {
#[handler]
async fn handle(&mut self, _: SlowMessage, _: &ActorRef<Self>) {
tokio::time::sleep(Duration::from_secs(10)).await;
}
}
let (actor_ref, handle) = spawn::<SlowStopActor>(SlowStopActor);
let _ = actor_ref.tell(SlowMessage).await;
let result1 = actor_ref.kill();
assert!(result1.is_ok(), "First kill should succeed");
let result2 = actor_ref.kill();
assert!(
result2.is_ok(),
"Second kill should succeed (terminate channel full case)"
);
let _ = handle.await;
}
#[tokio::test]
async fn test_kill_when_actor_already_stopped() {
let (actor_ref, handle) = spawn::<TestActor>(TestActor);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
let result = actor_ref.kill();
assert!(
result.is_ok(),
"Kill on stopped actor should succeed (terminate channel closed case)"
);
}
#[tokio::test]
async fn test_default_on_run_behavior() {
#[derive(Actor)]
struct DefaultRunActor {
received: bool,
}
struct CheckMessage;
#[rsactor::message_handlers]
impl DefaultRunActor {
#[handler]
async fn handle(&mut self, _: CheckMessage, _: &ActorRef<Self>) -> bool {
self.received = true;
self.received
}
}
let (actor_ref, handle) = spawn::<DefaultRunActor>(DefaultRunActor { received: false });
let result: bool = actor_ref.ask(CheckMessage).await.unwrap();
assert!(result, "Actor with default on_run should process messages");
actor_ref.stop().await.unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn test_deprecated_tell_blocking_method() {
#[derive(Actor)]
struct DeprecatedTestActor {
count: u32,
}
struct DeprecatedMessage;
#[rsactor::message_handlers]
impl DeprecatedTestActor {
#[handler]
async fn handle(&mut self, _: DeprecatedMessage, _: &ActorRef<Self>) {
self.count += 1;
}
}
let (actor_ref, handle) = spawn::<DeprecatedTestActor>(DeprecatedTestActor { count: 0 });
let actor_ref_clone = actor_ref.clone();
#[allow(deprecated)]
let result = tokio::task::spawn_blocking(move || {
actor_ref_clone.tell_blocking(DeprecatedMessage, Some(Duration::from_secs(1)))
})
.await
.unwrap();
assert!(result.is_ok(), "Deprecated tell_blocking should work");
actor_ref.stop().await.unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn test_deprecated_ask_blocking_method() {
#[derive(Actor)]
struct DeprecatedAskActor {
value: String,
}
struct GetValue;
#[rsactor::message_handlers]
impl DeprecatedAskActor {
#[handler]
async fn handle(&mut self, _: GetValue, _: &ActorRef<Self>) -> String {
self.value.clone()
}
}
let (actor_ref, handle) = spawn::<DeprecatedAskActor>(DeprecatedAskActor {
value: "test_value".to_string(),
});
let actor_ref_clone = actor_ref.clone();
#[allow(deprecated)]
let result: Result<String, _> = tokio::task::spawn_blocking(move || {
actor_ref_clone.ask_blocking(GetValue, Some(Duration::from_secs(1)))
})
.await
.unwrap();
assert!(result.is_ok(), "Deprecated ask_blocking should work");
assert_eq!(result.unwrap(), "test_value");
actor_ref.stop().await.unwrap();
handle.await.unwrap();
}
#[test]
#[should_panic(expected = "Mailbox capacity must be greater than 0")]
fn test_spawn_with_zero_mailbox_capacity_panics() {
#[derive(Actor)]
struct ZeroCapacityActor;
let _ = rsactor::spawn_with_mailbox_capacity::<ZeroCapacityActor>(ZeroCapacityActor, 0);
}
#[tokio::test]
async fn test_weak_handler_clone() {
use rsactor::{AskHandler, TellHandler};
#[derive(Actor)]
struct CloneTestActor;
struct CloneTestMessage;
#[rsactor::message_handlers]
impl CloneTestActor {
#[handler]
async fn handle(&mut self, _: CloneTestMessage, _: &ActorRef<Self>) -> String {
"response".to_string()
}
}
let (actor_ref, handle) = spawn::<CloneTestActor>(CloneTestActor);
let tell_handler: Box<dyn TellHandler<CloneTestMessage>> = Box::new(actor_ref.clone());
let weak_tell = tell_handler.downgrade();
let weak_tell_clone = weak_tell.clone();
assert!(
weak_tell.upgrade().is_some(),
"Original weak tell should upgrade"
);
assert!(
weak_tell_clone.upgrade().is_some(),
"Cloned weak tell should upgrade"
);
let ask_handler: Box<dyn AskHandler<CloneTestMessage, String>> = Box::new(actor_ref.clone());
let weak_ask = ask_handler.downgrade();
let weak_ask_clone = weak_ask.clone();
assert!(
weak_ask.upgrade().is_some(),
"Original weak ask should upgrade"
);
assert!(
weak_ask_clone.upgrade().is_some(),
"Cloned weak ask should upgrade"
);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn test_tell_handler_blocking_tell() {
use rsactor::TellHandler;
#[derive(Actor)]
struct BlockingTellActor {
received: std::sync::Arc<std::sync::atomic::AtomicBool>,
}
struct BlockingTellMessage;
#[rsactor::message_handlers]
impl BlockingTellActor {
#[handler]
async fn handle(&mut self, _: BlockingTellMessage, _: &ActorRef<Self>) {
self.received
.store(true, std::sync::atomic::Ordering::SeqCst);
}
}
let received = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let (actor_ref, handle) = spawn::<BlockingTellActor>(BlockingTellActor {
received: received.clone(),
});
let tell_handler: Box<dyn TellHandler<BlockingTellMessage>> = Box::new(actor_ref.clone());
let result =
tokio::task::spawn_blocking(move || tell_handler.blocking_tell(BlockingTellMessage, None))
.await
.unwrap();
assert!(result.is_ok(), "blocking_tell via trait should succeed");
tokio::time::sleep(Duration::from_millis(50)).await;
assert!(
received.load(std::sync::atomic::Ordering::SeqCst),
"Message should have been received"
);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn test_ask_handler_blocking_ask() {
use rsactor::AskHandler;
#[derive(Actor)]
struct BlockingAskTraitActor;
struct BlockingAskTraitMessage;
#[rsactor::message_handlers]
impl BlockingAskTraitActor {
#[handler]
async fn handle(&mut self, _: BlockingAskTraitMessage, _: &ActorRef<Self>) -> String {
"blocking_ask_response".to_string()
}
}
let (actor_ref, handle) = spawn::<BlockingAskTraitActor>(BlockingAskTraitActor);
let ask_handler: Box<dyn AskHandler<BlockingAskTraitMessage, String>> =
Box::new(actor_ref.clone());
let result: Result<String, _> = tokio::task::spawn_blocking(move || {
ask_handler.blocking_ask(BlockingAskTraitMessage, None)
})
.await
.unwrap();
assert!(result.is_ok(), "blocking_ask via trait should succeed");
assert_eq!(result.unwrap(), "blocking_ask_response");
actor_ref.stop().await.unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn test_actor_control_from_conversions() {
use rsactor::ActorControl;
#[derive(Actor)]
struct FromConversionActor;
let (actor_ref, handle) = spawn::<FromConversionActor>(FromConversionActor);
let control: Box<dyn ActorControl> = actor_ref.clone().into();
assert!(control.is_alive(), "Actor should be alive");
control.stop().await.unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn test_weak_actor_control_from_reference() {
use rsactor::WeakActorControl;
#[derive(Actor)]
struct WeakFromRefActor;
let (actor_ref, handle) = spawn::<WeakFromRefActor>(WeakFromRefActor);
let weak_ref = ActorRef::downgrade(&actor_ref);
let weak_control: Box<dyn WeakActorControl> = (&weak_ref).into();
assert!(
weak_control.upgrade().is_some(),
"Weak control should upgrade"
);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn test_ask_handler_from_actor_ref() {
use rsactor::AskHandler;
#[derive(Actor)]
struct AskHandlerFromActor;
struct AskHandlerFromMessage;
#[rsactor::message_handlers]
impl AskHandlerFromActor {
#[handler]
async fn handle(&mut self, _: AskHandlerFromMessage, _: &ActorRef<Self>) -> i32 {
42
}
}
let (actor_ref, handle) = spawn::<AskHandlerFromActor>(AskHandlerFromActor);
let ask_handler: Box<dyn AskHandler<AskHandlerFromMessage, i32>> = actor_ref.clone().into();
let result: i32 = ask_handler.ask(AskHandlerFromMessage).await.unwrap();
assert_eq!(result, 42);
actor_ref.stop().await.unwrap();
handle.await.unwrap();
}
#[tokio::test]
async fn test_weak_ask_handler_from_reference() {
use rsactor::WeakAskHandler;
#[derive(Actor)]
struct WeakAskFromRefActor;
struct WeakAskFromRefMessage;
#[rsactor::message_handlers]
impl WeakAskFromRefActor {
#[handler]
async fn handle(&mut self, _: WeakAskFromRefMessage, _: &ActorRef<Self>) -> String {
"weak_response".to_string()
}
}
let (actor_ref, handle) = spawn::<WeakAskFromRefActor>(WeakAskFromRefActor);
let weak_ref = ActorRef::downgrade(&actor_ref);
let weak_handler: Box<dyn WeakAskHandler<WeakAskFromRefMessage, String>> = (&weak_ref).into();
if let Some(strong) = weak_handler.upgrade() {
let result: String = strong.ask(WeakAskFromRefMessage).await.unwrap();
assert_eq!(result, "weak_response");
} else {
panic!("Weak handler should be upgradeable");
}
actor_ref.stop().await.unwrap();
handle.await.unwrap();
}