#[cfg(test)]
mod sync_tests {
use core::panic;
use std::{io, sync::Arc};
use actor_helper::Handle;
use actor_helper::{act, act_ok};
struct TestActor {
value: i32,
}
struct TestApi {
handle: Handle<TestActor, io::Error>,
}
impl TestApi {
fn new() -> Self {
Self {
handle: Handle::spawn_blocking(TestActor { value: 0 }).0,
}
}
fn stop_actor(&self) {
self.handle.shutdown();
self.handle.wait_stopped_blocking();
}
fn set_value(&self, value: i32) -> io::Result<()> {
self.handle.call_blocking(act_ok!(actor => async move {
actor.value = value;
}))
}
fn get_value(&self) -> io::Result<i32> {
self.handle.call_blocking(act_ok!(actor => async move {
actor.value
}))
}
fn increment(&self, by: i32) -> io::Result<()> {
self.handle.call_blocking(act_ok!(actor => async move {
actor.value += by;
}))
}
fn set_positive(&self, value: i32) -> io::Result<()> {
self.handle.call_blocking(act!(actor => async move {
if value <= 0 {
Err(std::io::Error::other("Value must be positive"))
} else {
actor.value = value;
Ok(())
}
}))
}
fn multiply(&self, factor: i32) -> io::Result<i32> {
self.handle.call_blocking(act_ok!(actor => async move {
actor.value *= factor;
actor.value
}))
}
fn is_running(&self) -> bool {
self.handle.state() == actor_helper::ActorState::Running
}
}
#[test]
fn test_basic_operations() {
let api = TestApi::new();
assert_eq!(api.get_value().unwrap(), 0);
api.set_value(42).unwrap();
assert_eq!(api.get_value().unwrap(), 42);
api.increment(8).unwrap();
assert_eq!(api.get_value().unwrap(), 50);
}
#[test]
fn test_error_handling() {
let api = TestApi::new();
let result = api.set_positive(-5);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("positive"));
api.set_positive(10).unwrap();
assert_eq!(api.get_value().unwrap(), 10);
}
#[test]
fn test_return_values() {
let api = TestApi::new();
api.set_value(7).unwrap();
let result = api.multiply(3).unwrap();
assert_eq!(result, 21);
assert_eq!(api.get_value().unwrap(), 21);
}
#[test]
fn test_concurrent_access() {
let api = Arc::new(TestApi::new());
let mut handles = vec![];
for i in 0..10 {
let api_clone = api.clone();
api_clone.increment(i).unwrap();
handles.push(api_clone);
}
let final_value = api.get_value().unwrap();
assert_eq!(final_value, 45);
}
#[test]
fn test_sequential_operations() {
let api = TestApi::new();
api.set_value(1).unwrap();
for _ in 0..5 {
let current = api.get_value().unwrap();
api.set_value(current * 2).unwrap();
}
assert_eq!(api.get_value().unwrap(), 32);
}
#[test]
fn test_clone_handle() {
let api1 = TestApi::new();
let api2 = TestApi {
handle: api1.handle.clone(),
};
api1.set_value(100).unwrap();
assert_eq!(api2.get_value().unwrap(), 100);
api2.increment(50).unwrap();
assert_eq!(api1.get_value().unwrap(), 150);
}
#[test]
fn test_actor_lifecycle() {
let api = TestApi::new();
assert!(api.is_running());
api.stop_actor();
assert!(!api.is_running());
assert!(api.get_value().is_err());
}
struct CounterActor {
count: i32,
}
#[test]
fn test_shared_state() {
let (handle, _) =
Handle::<CounterActor, io::Error>::spawn_blocking(CounterActor { count: 0 });
handle
.call_blocking(act_ok!(actor => async move {
actor.count += 1;
}))
.unwrap();
assert_eq!(
handle
.call_blocking(act_ok!(actor => async move { actor.count }))
.unwrap(),
1
);
}
#[test]
fn test_async_action() {
let api = TestApi::new();
api.handle
.call_blocking(act_ok!(actor => async move {
std::thread::sleep(std::time::Duration::from_millis(10));
actor.value = 999;
}))
.unwrap();
assert_eq!(api.get_value().unwrap(), 999);
}
#[test]
fn test_multiple_handles_same_actor() {
let (handle1, _) = Handle::<TestActor, io::Error>::spawn_blocking(TestActor { value: 0 });
let handle2 = handle1.clone();
let handle3 = handle1.clone();
handle1
.call_blocking(act_ok!(actor => async move { actor.value += 10; }))
.unwrap();
handle2
.call_blocking(act_ok!(actor => async move { actor.value *= 2; }))
.unwrap();
let result = handle3
.call_blocking(act_ok!(actor => async move { actor.value }))
.unwrap();
assert_eq!(result, 20);
}
struct PanicActor;
#[test]
fn test_actor_loop_panic_is_returned_as_error() {
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let (handle, join_handle) =
Handle::<PanicActor, io::Error>::spawn_blocking_with(PanicActor, |_, _| {
panic!("blocking actor panic");
});
drop(handle);
let result = join_handle.join().unwrap();
let error = result.unwrap_err().to_string();
std::panic::set_hook(prev);
assert!(error.contains("panic in actor loop"));
assert!(error.contains("blocking actor panic"));
}
}