mod sync_vec;
use speare::{Actor, Ctx, Node, RegistryError};
use sync_vec::SyncVec;
use tokio::task;
struct Worker;
impl Actor for Worker {
type Props = SyncVec<String>;
type Msg = String;
type Err = ();
async fn init(_: &mut Ctx<Self>) -> Result<Self, Self::Err> {
Ok(Worker)
}
async fn handle(&mut self, msg: Self::Msg, ctx: &mut Ctx<Self>) -> Result<(), Self::Err> {
ctx.props().push(msg).await;
Ok(())
}
}
struct LookupByType;
impl Actor for LookupByType {
type Props = SyncVec<bool>;
type Msg = ();
type Err = ();
async fn init(ctx: &mut Ctx<Self>) -> Result<Self, Self::Err> {
let found = ctx.get_handle_for::<Worker>().is_ok();
ctx.props().push(found).await;
Ok(LookupByType)
}
}
struct LookupByName;
impl Actor for LookupByName {
type Props = (String, SyncVec<bool>);
type Msg = ();
type Err = ();
async fn init(ctx: &mut Ctx<Self>) -> Result<Self, Self::Err> {
let (name, recvd) = ctx.props();
let found = ctx.get_handle::<String>(name).is_ok();
recvd.push(found).await;
Ok(LookupByName)
}
}
#[tokio::test]
async fn spawn_registered_returns_handle() {
let mut node = Node::default();
let recvd = SyncVec::<String>::default();
let result = node.actor::<Worker>(recvd).spawn_registered();
assert!(result.is_ok());
}
#[tokio::test]
async fn spawn_registered_returns_name_taken_on_duplicate() {
let mut node = Node::default();
let _ = node
.actor::<Worker>(SyncVec::default())
.spawn_registered()
.unwrap();
let result = node.actor::<Worker>(SyncVec::default()).spawn_registered();
assert!(matches!(result, Err(RegistryError::NameTaken(_))));
}
#[tokio::test]
async fn spawn_registered_actor_is_functional() {
let mut node = Node::default();
let recvd = SyncVec::<String>::default();
let handle = node
.actor::<Worker>(recvd.clone())
.spawn_registered()
.unwrap();
handle.send("hello".to_string());
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec!["hello".to_string()]);
}
#[tokio::test]
async fn spawn_named_returns_handle() {
let mut node = Node::default();
let recvd = SyncVec::<String>::default();
let result = node.actor::<Worker>(recvd).spawn_named("my-worker");
assert!(result.is_ok());
}
#[tokio::test]
async fn spawn_named_returns_name_taken_on_duplicate() {
let mut node = Node::default();
let _ = node
.actor::<Worker>(SyncVec::default())
.spawn_named("my-worker")
.unwrap();
let result = node
.actor::<Worker>(SyncVec::default())
.spawn_named("my-worker");
assert!(matches!(result, Err(RegistryError::NameTaken(_))));
}
#[tokio::test]
async fn spawn_named_allows_different_names() {
let mut node = Node::default();
let first = node
.actor::<Worker>(SyncVec::default())
.spawn_named("worker-a");
let second = node
.actor::<Worker>(SyncVec::default())
.spawn_named("worker-b");
assert!(first.is_ok());
assert!(second.is_ok());
}
#[tokio::test]
async fn spawn_named_actor_is_functional() {
let mut node = Node::default();
let recvd = SyncVec::<String>::default();
let handle = node
.actor::<Worker>(recvd.clone())
.spawn_named("worker")
.unwrap();
handle.send("world".to_string());
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec!["world".to_string()]);
}
#[tokio::test]
async fn get_handle_for_returns_none_when_not_registered() {
let mut node = Node::default();
let recvd = SyncVec::<bool>::default();
node.actor::<LookupByType>(recvd.clone()).spawn();
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec![false]);
}
#[tokio::test]
async fn get_handle_for_finds_registered_actor() {
let mut node = Node::default();
let recvd = SyncVec::<bool>::default();
let _ = node
.actor::<Worker>(SyncVec::default())
.spawn_registered()
.unwrap();
node.actor::<LookupByType>(recvd.clone()).spawn();
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec![true]);
}
struct LookupByNameWrongType;
impl Actor for LookupByNameWrongType {
type Props = (String, SyncVec<bool>);
type Msg = ();
type Err = ();
async fn init(ctx: &mut Ctx<Self>) -> Result<Self, Self::Err> {
let (name, recvd) = ctx.props();
let found = ctx.get_handle::<u32>(name).is_ok();
recvd.push(found).await;
Ok(LookupByNameWrongType)
}
}
#[tokio::test]
async fn get_handle_returns_none_for_wrong_msg_type() {
let mut node = Node::default();
let recvd = SyncVec::<bool>::default();
let _ = node
.actor::<Worker>(SyncVec::default())
.spawn_named("my-worker")
.unwrap();
node.actor::<LookupByNameWrongType>(("my-worker".to_string(), recvd.clone()))
.spawn();
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec![false]);
}
#[tokio::test]
async fn get_handle_returns_none_when_not_registered() {
let mut node = Node::default();
let recvd = SyncVec::<bool>::default();
node.actor::<LookupByName>(("nonexistent".to_string(), recvd.clone()))
.spawn();
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec![false]);
}
#[tokio::test]
async fn get_handle_finds_named_actor() {
let mut node = Node::default();
let recvd = SyncVec::<bool>::default();
let _ = node
.actor::<Worker>(SyncVec::default())
.spawn_named("my-worker")
.unwrap();
node.actor::<LookupByName>(("my-worker".to_string(), recvd.clone()))
.spawn();
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec![true]);
}
#[tokio::test]
async fn get_handle_returns_none_for_wrong_name() {
let mut node = Node::default();
let recvd = SyncVec::<bool>::default();
let _ = node
.actor::<Worker>(SyncVec::default())
.spawn_named("my-worker")
.unwrap();
node.actor::<LookupByName>(("wrong-name".to_string(), recvd.clone()))
.spawn();
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec![false]);
}
struct SendByType;
impl Actor for SendByType {
type Props = ();
type Msg = ();
type Err = ();
async fn init(ctx: &mut Ctx<Self>) -> Result<Self, Self::Err> {
let _ = ctx.send::<Worker>("via-send".to_string());
Ok(SendByType)
}
}
#[tokio::test]
async fn send_delivers_msg_to_registered_actor() {
let mut node = Node::default();
let recvd = SyncVec::<String>::default();
let _ = node
.actor::<Worker>(recvd.clone())
.spawn_registered()
.unwrap();
node.actor::<SendByType>(()).spawn();
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec!["via-send".to_string()]);
}
struct SendByTypeNotFound;
impl Actor for SendByTypeNotFound {
type Props = SyncVec<bool>;
type Msg = ();
type Err = ();
async fn init(ctx: &mut Ctx<Self>) -> Result<Self, Self::Err> {
let result = ctx.send::<Worker>("msg".to_string());
ctx.props()
.push(matches!(result, Err(RegistryError::NotFound(_))))
.await;
Ok(SendByTypeNotFound)
}
}
#[tokio::test]
async fn send_returns_not_found_when_not_registered() {
let mut node = Node::default();
let recvd = SyncVec::<bool>::default();
node.actor::<SendByTypeNotFound>(recvd.clone()).spawn();
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec![true]);
}
struct SendByName;
impl Actor for SendByName {
type Props = String;
type Msg = ();
type Err = ();
async fn init(ctx: &mut Ctx<Self>) -> Result<Self, Self::Err> {
let name = ctx.props().clone();
let _ = ctx.send_to::<<Worker as Actor>::Msg>(&name, "via-send-to".to_string());
Ok(SendByName)
}
}
#[tokio::test]
async fn send_to_delivers_msg_to_named_actor() {
let mut node = Node::default();
let recvd = SyncVec::<String>::default();
let _ = node
.actor::<Worker>(recvd.clone())
.spawn_named("target")
.unwrap();
node.actor::<SendByName>("target".to_string()).spawn();
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec!["via-send-to".to_string()]);
}
struct SendByNameNotFound;
impl Actor for SendByNameNotFound {
type Props = SyncVec<bool>;
type Msg = ();
type Err = ();
async fn init(ctx: &mut Ctx<Self>) -> Result<Self, Self::Err> {
let result = ctx.send_to::<<Worker as Actor>::Msg>("nope", "msg".to_string());
ctx.props()
.push(matches!(result, Err(RegistryError::NotFound(_))))
.await;
Ok(SendByNameNotFound)
}
}
#[tokio::test]
async fn send_to_returns_not_found_when_not_registered() {
let mut node = Node::default();
let recvd = SyncVec::<bool>::default();
node.actor::<SendByNameNotFound>(recvd.clone()).spawn();
task::yield_now().await;
assert_eq!(recvd.clone_vec().await, vec![true]);
}
#[tokio::test]
async fn named_actor_deregisters_on_stop() {
let mut node = Node::default();
let handle = node
.actor::<Worker>(SyncVec::default())
.spawn_named("worker")
.unwrap();
handle.stop();
task::yield_now().await;
let result = node
.actor::<Worker>(SyncVec::default())
.spawn_named("worker");
assert!(result.is_ok());
}
#[tokio::test]
async fn registered_actor_deregisters_on_stop() {
let mut node = Node::default();
let handle = node
.actor::<Worker>(SyncVec::default())
.spawn_registered()
.unwrap();
handle.stop();
task::yield_now().await;
let result = node.actor::<Worker>(SyncVec::default()).spawn_registered();
assert!(result.is_ok());
}
#[tokio::test]
async fn spawn_named_with_type_name_collides_with_spawn_registered() {
let mut node = Node::default();
let type_name = std::any::type_name::<Worker>();
let _ = node
.actor::<Worker>(SyncVec::default())
.spawn_named(type_name)
.unwrap();
let result = node.actor::<Worker>(SyncVec::default()).spawn_registered();
assert!(matches!(result, Err(RegistryError::NameTaken(_))));
}
#[tokio::test]
async fn spawn_registered_collides_with_spawn_named_using_type_name() {
let mut node = Node::default();
let _ = node
.actor::<Worker>(SyncVec::default())
.spawn_registered()
.unwrap();
let type_name = std::any::type_name::<Worker>();
let result = node
.actor::<Worker>(SyncVec::default())
.spawn_named(type_name);
assert!(matches!(result, Err(RegistryError::NameTaken(_))));
}
#[tokio::test]
async fn spawn_registered_and_spawn_named_coexist_with_different_keys() {
let mut node = Node::default();
let first = node.actor::<Worker>(SyncVec::default()).spawn_registered();
let second = node
.actor::<Worker>(SyncVec::default())
.spawn_named("custom-worker");
assert!(first.is_ok());
assert!(second.is_ok());
}