use rsactor::{spawn, Actor, ActorRef, ActorResult, ActorWeak, FailurePhase, Message};
#[derive(Debug)]
struct NoOpMsg;
#[derive(Debug)]
struct TestActor {
id: String,
value: i32,
}
impl Actor for TestActor {
type Args = (String, i32);
type Error = anyhow::Error;
async fn on_start(args: Self::Args, _actor_ref: &ActorRef<Self>) -> Result<Self, Self::Error> {
let (id, value) = args;
Ok(TestActor { id, value })
}
async fn on_run(&mut self, _actor_ref: &ActorWeak<Self>) -> Result<bool, Self::Error> {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
Ok(true)
}
}
impl Message<NoOpMsg> for TestActor {
type Reply = ();
async fn handle(&mut self, _msg: NoOpMsg, _: &ActorRef<Self>) -> Self::Reply {}
}
#[derive(Debug)]
struct FailureTestActor {
id: String,
value: i32,
}
#[derive(Debug)]
struct FailureTestArgs {
id: String,
value: i32,
fail_on_start: bool,
}
impl Actor for FailureTestActor {
type Args = FailureTestArgs;
type Error = anyhow::Error;
async fn on_start(args: Self::Args, _actor_ref: &ActorRef<Self>) -> Result<Self, Self::Error> {
if args.fail_on_start {
return Err(anyhow::anyhow!("Test failure in on_start"));
}
Ok(FailureTestActor {
id: args.id,
value: args.value,
})
}
async fn on_run(&mut self, _actor_ref: &ActorWeak<Self>) -> Result<bool, Self::Error> {
if self.id.contains("fail_on_run") {
return Err(anyhow::anyhow!("Test failure in on_run"));
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
Ok(true)
}
async fn on_stop(
&mut self,
_actor_ref: &ActorWeak<Self>,
_killed: bool,
) -> Result<(), Self::Error> {
if self.id.contains("fail_on_stop") {
return Err(anyhow::anyhow!("Test failure in on_stop"));
}
Ok(())
}
}
impl Message<NoOpMsg> for FailureTestActor {
type Reply = ();
async fn handle(&mut self, _msg: NoOpMsg, _: &ActorRef<Self>) -> Self::Reply {}
}
#[tokio::test]
async fn test_actor_result_completed_stopped_normally() {
let (actor_ref, handle) = spawn::<TestActor>(("test_normal".to_string(), 42));
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
actor_ref.stop().await.expect("Stop should succeed");
let result = handle.await.expect("Actor should complete");
assert!(result.is_completed(), "Should be completed");
assert!(!result.was_killed(), "Should not be killed");
assert!(result.stopped_normally(), "Should have stopped normally");
assert!(!result.is_startup_failed(), "Should not be startup failed");
assert!(!result.is_runtime_failed(), "Should not be runtime failed");
assert!(!result.is_stop_failed(), "Should not be stop failed");
assert!(!result.is_failed(), "Should not be failed");
assert!(result.has_actor(), "Should have actor");
assert!(result.actor().is_some(), "Should have actor reference");
if let Some(actor) = result.actor() {
assert_eq!(actor.id, "test_normal");
assert_eq!(actor.value, 42);
}
assert!(result.error().is_none(), "Should not have error");
let result_copy = ActorResult::Completed {
actor: TestActor {
id: "test_normal".to_string(),
value: 42,
},
killed: false,
};
let std_result = result_copy.to_result();
assert!(std_result.is_ok(), "to_result should be Ok");
if let Ok(actor) = std_result {
assert_eq!(actor.id, "test_normal");
assert_eq!(actor.value, 42);
}
let actor_option = result.into_actor();
assert!(actor_option.is_some(), "into_actor should return Some");
if let Some(actor) = actor_option {
assert_eq!(actor.id, "test_normal");
assert_eq!(actor.value, 42);
}
}
#[tokio::test]
async fn test_actor_result_completed_killed() {
let (actor_ref, handle) = spawn::<TestActor>(("test_killed".to_string(), 100));
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
actor_ref.kill().expect("Kill should succeed");
let result = handle.await.expect("Actor should complete");
assert!(result.is_completed(), "Should be completed");
assert!(result.was_killed(), "Should be killed");
assert!(
!result.stopped_normally(),
"Should not have stopped normally"
);
assert!(!result.is_startup_failed(), "Should not be startup failed");
assert!(!result.is_runtime_failed(), "Should not be runtime failed");
assert!(!result.is_stop_failed(), "Should not be stop failed");
assert!(!result.is_failed(), "Should not be failed");
assert!(result.has_actor(), "Should have actor");
assert!(result.actor().is_some(), "Should have actor reference");
if let Some(actor) = result.actor() {
assert_eq!(actor.id, "test_killed");
assert_eq!(actor.value, 100);
}
assert!(result.error().is_none(), "Should not have error");
}
#[tokio::test]
async fn test_actor_result_failed_on_start() {
let args = FailureTestArgs {
id: "test_start_fail".to_string(),
value: 999,
fail_on_start: true,
};
let (_actor_ref, handle) = spawn::<FailureTestActor>(args);
let result = handle.await.expect("Handle should not fail");
assert!(!result.is_completed(), "Should not be completed");
assert!(!result.was_killed(), "Should not be killed");
assert!(
!result.stopped_normally(),
"Should not have stopped normally"
);
assert!(result.is_startup_failed(), "Should be startup failed");
assert!(!result.is_runtime_failed(), "Should not be runtime failed");
assert!(!result.is_stop_failed(), "Should not be stop failed");
assert!(result.is_failed(), "Should be failed");
assert!(
!result.has_actor(),
"Should not have actor (failed on_start)"
);
assert!(result.actor().is_none(), "Should not have actor reference");
assert!(result.error().is_some(), "Should have error");
if let Some(error) = result.error() {
assert!(error.to_string().contains("Test failure in on_start"));
}
let result_copy = ActorResult::Failed::<FailureTestActor> {
actor: None,
error: anyhow::anyhow!("Test failure in on_start"),
phase: FailurePhase::OnStart,
killed: false,
};
let std_result = result_copy.to_result();
assert!(std_result.is_err(), "to_result should be Err");
if let Err(error) = std_result {
assert!(error.to_string().contains("Test failure in on_start"));
}
let actor_option = result.into_actor();
assert!(
actor_option.is_none(),
"into_actor should return None for on_start failure"
);
}
#[tokio::test]
async fn test_actor_result_failed_on_run() {
let args = FailureTestArgs {
id: "test_fail_on_run".to_string(),
value: 777,
fail_on_start: false,
};
let (_actor_ref, handle) = spawn::<FailureTestActor>(args);
let result = handle.await.expect("Handle should not fail");
assert!(!result.is_completed(), "Should not be completed");
assert!(!result.was_killed(), "Should not be killed");
assert!(
!result.stopped_normally(),
"Should not have stopped normally"
);
assert!(!result.is_startup_failed(), "Should not be startup failed");
assert!(result.is_runtime_failed(), "Should be runtime failed");
assert!(!result.is_stop_failed(), "Should not be stop failed");
assert!(result.is_failed(), "Should be failed");
assert!(
result.has_actor(),
"Should have actor (failed after on_start)"
);
assert!(result.actor().is_some(), "Should have actor reference");
if let Some(actor) = result.actor() {
assert_eq!(actor.id, "test_fail_on_run");
assert_eq!(actor.value, 777);
}
assert!(result.error().is_some(), "Should have error");
if let Some(error) = result.error() {
assert!(error.to_string().contains("Test failure in on_run"));
}
}
#[tokio::test]
async fn test_actor_result_failed_on_stop_graceful() {
let args = FailureTestArgs {
id: "test_fail_on_stop".to_string(),
value: 555,
fail_on_start: false,
};
let (actor_ref, handle) = spawn::<FailureTestActor>(args);
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
actor_ref.stop().await.expect("Stop should succeed");
let result = handle.await.expect("Handle should not fail");
assert!(!result.is_completed(), "Should not be completed");
assert!(!result.was_killed(), "Should not be killed (graceful stop)");
assert!(
!result.stopped_normally(),
"Should not have stopped normally"
);
assert!(!result.is_startup_failed(), "Should not be startup failed");
assert!(!result.is_runtime_failed(), "Should not be runtime failed");
assert!(result.is_stop_failed(), "Should be stop failed");
assert!(result.is_failed(), "Should be failed");
assert!(result.has_actor(), "Should have actor");
assert!(result.actor().is_some(), "Should have actor reference");
if let Some(actor) = result.actor() {
assert_eq!(actor.id, "test_fail_on_stop");
assert_eq!(actor.value, 555);
}
assert!(result.error().is_some(), "Should have error");
if let Some(error) = result.error() {
assert!(error.to_string().contains("Test failure in on_stop"));
}
}
#[tokio::test]
async fn test_actor_result_failed_on_stop_killed() {
let args = FailureTestArgs {
id: "test_fail_on_stop".to_string(),
value: 333,
fail_on_start: false,
};
let (actor_ref, handle) = spawn::<FailureTestActor>(args);
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
actor_ref.kill().expect("Kill should succeed");
let result = handle.await.expect("Handle should not fail");
assert!(!result.is_completed(), "Should not be completed");
assert!(result.was_killed(), "Should be killed");
assert!(
!result.stopped_normally(),
"Should not have stopped normally"
);
assert!(!result.is_startup_failed(), "Should not be startup failed");
assert!(!result.is_runtime_failed(), "Should not be runtime failed");
assert!(result.is_stop_failed(), "Should be stop failed");
assert!(result.is_failed(), "Should be failed");
assert!(result.has_actor(), "Should have actor");
assert!(result.actor().is_some(), "Should have actor reference");
if let Some(actor) = result.actor() {
assert_eq!(actor.id, "test_fail_on_stop");
assert_eq!(actor.value, 333);
}
assert!(result.error().is_some(), "Should have error");
if let Some(error) = result.error() {
assert!(error.to_string().contains("Test failure in on_stop"));
}
}
#[tokio::test]
async fn test_failure_phase_display() {
assert_eq!(FailurePhase::OnStart.to_string(), "OnStart");
assert_eq!(FailurePhase::OnRun.to_string(), "OnRun");
assert_eq!(FailurePhase::OnStop.to_string(), "OnStop");
}
#[tokio::test]
async fn test_failure_phase_equality() {
assert_eq!(FailurePhase::OnStart, FailurePhase::OnStart);
assert_eq!(FailurePhase::OnRun, FailurePhase::OnRun);
assert_eq!(FailurePhase::OnStop, FailurePhase::OnStop);
assert_ne!(FailurePhase::OnStart, FailurePhase::OnRun);
assert_ne!(FailurePhase::OnRun, FailurePhase::OnStop);
assert_ne!(FailurePhase::OnStart, FailurePhase::OnStop);
}
#[tokio::test]
async fn test_actor_result_from_completed() {
let result = ActorResult::Completed {
actor: TestActor {
id: "test".to_string(),
value: 123,
},
killed: false,
};
let (actor_option, error_option): (Option<TestActor>, Option<anyhow::Error>) = result.into();
assert!(actor_option.is_some(), "Should have actor");
assert!(error_option.is_none(), "Should not have error");
if let Some(actor) = actor_option {
assert_eq!(actor.id, "test");
assert_eq!(actor.value, 123);
}
}
#[tokio::test]
async fn test_actor_result_from_failed_with_actor() {
let result = ActorResult::Failed {
actor: Some(TestActor {
id: "test".to_string(),
value: 456,
}),
error: anyhow::anyhow!("test error"),
phase: FailurePhase::OnRun,
killed: false,
};
let (actor_option, error_option): (Option<TestActor>, Option<anyhow::Error>) = result.into();
assert!(actor_option.is_some(), "Should have actor");
assert!(error_option.is_some(), "Should have error");
if let Some(actor) = actor_option {
assert_eq!(actor.id, "test");
assert_eq!(actor.value, 456);
}
if let Some(error) = error_option {
assert_eq!(error.to_string(), "test error");
}
}
#[tokio::test]
async fn test_actor_result_from_failed_without_actor() {
let result = ActorResult::Failed::<TestActor> {
actor: None,
error: anyhow::anyhow!("startup error"),
phase: FailurePhase::OnStart,
killed: false,
};
let (actor_option, error_option): (Option<TestActor>, Option<anyhow::Error>) = result.into();
assert!(actor_option.is_none(), "Should not have actor");
assert!(error_option.is_some(), "Should have error");
if let Some(error) = error_option {
assert_eq!(error.to_string(), "startup error");
}
}
#[tokio::test]
async fn test_actor_result_into_error_completed() {
let result = ActorResult::Completed {
actor: TestActor {
id: "test".to_string(),
value: 789,
},
killed: false,
};
let error_option = result.into_error();
assert!(
error_option.is_none(),
"Completed result should not have error"
);
}
#[tokio::test]
async fn test_actor_result_into_error_failed() {
let result = ActorResult::Failed::<TestActor> {
actor: None,
error: anyhow::anyhow!("test into_error"),
phase: FailurePhase::OnStart,
killed: false,
};
let error_option = result.into_error();
assert!(error_option.is_some(), "Failed result should have error");
if let Some(error) = error_option {
assert_eq!(error.to_string(), "test into_error");
}
}
#[tokio::test]
async fn test_actor_result_debug_format() {
let completed_result = ActorResult::Completed {
actor: TestActor {
id: "debug_test".to_string(),
value: 42,
},
killed: false,
};
let debug_str = format!("{completed_result:?}");
assert!(
debug_str.contains("Completed"),
"Debug should contain variant name"
);
let failed_result = ActorResult::Failed::<TestActor> {
actor: None,
error: anyhow::anyhow!("debug error"),
phase: FailurePhase::OnStart,
killed: false,
};
let debug_str = format!("{failed_result:?}");
assert!(
debug_str.contains("Failed"),
"Debug should contain variant name"
);
}
#[tokio::test]
async fn test_all_boolean_combinations() {
let result1 = ActorResult::Completed {
actor: TestActor {
id: "test1".to_string(),
value: 1,
},
killed: false,
};
assert!(result1.is_completed() && !result1.was_killed() && result1.stopped_normally());
let result2 = ActorResult::Completed {
actor: TestActor {
id: "test2".to_string(),
value: 2,
},
killed: true,
};
assert!(result2.is_completed() && result2.was_killed() && !result2.stopped_normally());
let result3 = ActorResult::Failed::<TestActor> {
actor: None,
error: anyhow::anyhow!("error3"),
phase: FailurePhase::OnStart,
killed: false,
};
assert!(!result3.is_completed() && !result3.was_killed() && result3.is_startup_failed());
let result4 = ActorResult::Failed {
actor: Some(TestActor {
id: "test4".to_string(),
value: 4,
}),
error: anyhow::anyhow!("error4"),
phase: FailurePhase::OnRun,
killed: false,
};
assert!(!result4.is_completed() && !result4.was_killed() && result4.is_runtime_failed());
let result5 = ActorResult::Failed {
actor: Some(TestActor {
id: "test5".to_string(),
value: 5,
}),
error: anyhow::anyhow!("error5"),
phase: FailurePhase::OnStop,
killed: false,
};
assert!(!result5.is_completed() && !result5.was_killed() && result5.is_stop_failed());
let result6 = ActorResult::Failed {
actor: Some(TestActor {
id: "test6".to_string(),
value: 6,
}),
error: anyhow::anyhow!("error6"),
phase: FailurePhase::OnStop,
killed: true,
};
assert!(!result6.is_completed() && result6.was_killed() && result6.is_stop_failed());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_blocking_tell_works_outside_runtime() {
let args = (String::from("runtime_test"), 42);
let (actor_ref, handle) = spawn::<TestActor>(args);
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
let actor_ref_clone = actor_ref.clone();
let thread_handle = std::thread::spawn(move || {
let result = actor_ref_clone.blocking_tell(NoOpMsg, None);
assert!(
result.is_ok(),
"blocking_tell should succeed outside runtime context: {:?}",
result
);
});
thread_handle.join().expect("Thread should not panic");
actor_ref.stop().await.expect("Failed to stop actor");
let _result = handle.await.expect("Actor task failed");
}
#[tokio::test(flavor = "multi_thread")]
async fn test_blocking_ask_works_outside_runtime() {
let args = (String::from("runtime_test"), 42);
let (actor_ref, handle) = spawn::<TestActor>(args);
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
let actor_ref_clone = actor_ref.clone();
let thread_handle = std::thread::spawn(move || {
let result: Result<(), rsactor::Error> = actor_ref_clone.blocking_ask(NoOpMsg, None);
assert!(
result.is_ok(),
"blocking_ask should succeed outside runtime context: {:?}",
result
);
});
thread_handle.join().expect("Thread should not panic");
actor_ref.stop().await.expect("Failed to stop actor");
let _result = handle.await.expect("Actor task failed");
}
#[tokio::test]
async fn test_error_runtime_display_format() {
let identity = rsactor::Identity::new(1, "TestActor");
let error = rsactor::Error::Runtime {
identity,
details: "Test runtime error details".to_string(),
};
let display_str = format!("{error}");
assert!(
display_str.contains("Runtime error in actor"),
"Display should mention runtime error"
);
assert!(
display_str.contains("TestActor"),
"Display should contain actor name"
);
assert!(
display_str.contains("Test runtime error details"),
"Display should contain error details"
);
}