use std::{sync::Arc, time::Duration};
use hiroz::{Builder, Result, action::GoalStatus, context::ZContextBuilder, define_action};
use serde::{Deserialize, Serialize};
use serial_test::serial;
use tokio::time;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestGoal {
pub order: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestResult {
pub value: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestFeedback {
pub progress: i32,
}
pub struct TestAction;
define_action! {
TestAction,
action_name: "test_action",
Goal: TestGoal,
Result: TestResult,
Feedback: TestFeedback,
}
#[allow(dead_code)]
async fn setup_test_base() -> Result<(hiroz::node::ZNode,)> {
let ctx = ZContextBuilder::default().build()?;
let node = ctx.create_node("test_action_wait_node").build()?;
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
Ok((node,))
}
async fn setup_test_with_client_server() -> Result<(
hiroz::node::ZNode,
hiroz::node::ZNode,
std::sync::Arc<hiroz::action::client::ZActionClient<TestAction>>,
hiroz::action::server::ZActionServer<TestAction>,
)> {
let ctx = ZContextBuilder::default().build()?;
let client_node = ctx.create_node("test_action_client_wait_node").build()?;
let server_node = ctx.create_node("test_action_server_wait_node").build()?;
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let client = Arc::new(
client_node
.create_action_client::<TestAction>("/test_action_wait_name")
.build()?,
);
let server = server_node
.create_action_server::<TestAction>("/test_action_wait_name")
.build()?;
Ok((client_node, server_node, client, server))
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_action_client_async_wait_timeout() -> Result<()> {
let (_client_node, _server_node, client, server) = setup_test_with_client_server().await?;
let server_clone = server.clone();
tokio::spawn(async move {
if let Ok(requested) = server_clone.recv_goal().await {
let _accepted = requested.accept();
tokio::time::sleep(Duration::from_secs(10)).await;
}
});
let goal = TestGoal { order: 5 };
let goal_handle = client.send_goal(goal).await?;
let result_future = goal_handle.result();
let timeout_result = time::timeout(Duration::from_millis(100), result_future).await;
assert!(timeout_result.is_err());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_client_feedback_wait() -> Result<()> {
let (_client_node, _server_node, client, server) = setup_test_with_client_server().await?;
let server_clone = server.clone();
tokio::spawn(async move {
if let Ok(requested) = server_clone.recv_goal().await {
let accepted = requested.accept();
let executing = accepted.execute();
let _ = executing.publish_feedback(TestFeedback { progress: 50 });
let _ = executing.succeed(TestResult { value: 10 });
}
});
let goal = TestGoal { order: 5 };
let mut goal_handle = client.send_goal(goal).await?;
let mut feedback_stream = goal_handle.feedback().unwrap();
let feedback = time::timeout(Duration::from_millis(1000), feedback_stream.recv()).await?;
let feedback = feedback.unwrap();
assert_eq!(feedback.progress, 50);
let result = goal_handle.result().await?;
assert_eq!(result.value, 10);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_server_async_wait() -> Result<()> {
let (_client_node, _server_node, _client, server) = setup_test_with_client_server().await?;
let recv_future = server.recv_goal();
let timeout_result = time::timeout(Duration::from_millis(100), recv_future).await;
assert!(timeout_result.is_err());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_concurrent_async_operations() -> Result<()> {
let (_client_node, _server_node, client, server) = setup_test_with_client_server().await?;
let server_clone = server.clone();
let client_clone = client.clone();
let server_task = tokio::spawn(async move {
let requested = server_clone.recv_goal().await?;
let accepted = requested.accept();
let executing = accepted.execute();
executing.succeed(TestResult { value: 42 })?;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
});
let client_task = tokio::spawn(async move {
let goal_handle = client_clone.send_goal(TestGoal { order: 10 }).await?;
let result = goal_handle.result().await?;
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(result)
});
let (server_result, client_result) = tokio::try_join!(server_task, client_task)?;
server_result?;
let result = client_result?;
assert_eq!(result.value, 42);
Ok(())
}
#[serial]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_action_status_async_wait() -> Result<()> {
use tokio::sync::oneshot;
let (_client_node, _server_node, client, server) = setup_test_with_client_server().await?;
let (ready_tx, ready_rx) = oneshot::channel();
let server_clone = server.clone();
tokio::spawn(async move {
if let Ok(requested) = server_clone.recv_goal().await {
let accepted = requested.accept();
let _ = ready_rx.await;
let executing = accepted.execute();
tokio::task::yield_now().await;
let _ = executing.succeed(TestResult { value: 100 });
}
});
let goal = TestGoal { order: 5 };
let mut goal_handle = client.send_goal(goal).await?;
let mut status_watch = goal_handle.status_watch().unwrap();
let initial_status = *status_watch.borrow_and_update();
tracing::debug!("Initial status: {:?}", initial_status);
let _ = ready_tx.send(());
loop {
time::timeout(Duration::from_secs(5), status_watch.changed())
.await
.expect("timeout waiting for status change")?;
let status = *status_watch.borrow_and_update();
tracing::debug!("Status: {:?}", status);
if matches!(
status,
GoalStatus::Succeeded | GoalStatus::Aborted | GoalStatus::Canceled
) {
break;
}
}
Ok(())
}
}