use std::num::NonZeroUsize;
use hiroz::{
Builder, Result,
context::ZContextBuilder,
define_action,
qos::{QosHistory, QosProfile, QosReliability},
};
use serde::{Deserialize, Serialize};
#[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,
}
#[cfg(test)]
mod tests {
use super::*;
async fn setup_test() -> Result<(
hiroz::node::ZNode,
hiroz::action::client::ZActionClient<TestAction>,
hiroz::action::server::ZActionServer<TestAction>,
)> {
let ctx = ZContextBuilder::default().build()?;
let node = ctx.create_node("test_action_server_node").build()?;
let client = node
.create_action_client::<TestAction>("test_action_server_name")
.build()?;
let server = node
.create_action_server::<TestAction>("test_action_server_name")
.build()?;
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
Ok((node, client, server))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_server_init_fini() -> Result<()> {
let ctx = ZContextBuilder::default().build()?;
let node = ctx.create_node("test_action_server_node").build()?;
let server = node
.create_action_server::<TestAction>("test_action_server_name")
.build()?;
let _server_clone = server.clone();
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_server_is_valid() -> Result<()> {
let (_node, _client, server) = setup_test().await?;
let _server_clone = server.clone();
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_accept_new_goal() -> Result<()> {
let (_node, client, server) = setup_test().await?;
let server_clone = server.clone();
let server_task = tokio::spawn(async move {
let requested = server_clone.recv_goal().await?;
assert_eq!(requested.goal.order, 10);
let _accepted = requested.accept();
Ok::<(), zenoh::Error>(())
});
let goal_handle = client.send_goal(TestGoal { order: 10 }).await?;
let goal_id = goal_handle.id();
server_task.await??;
assert!(goal_id.is_valid());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_server_goal_exists() -> Result<()> {
let (_node, client, server) = setup_test().await?;
let server_clone = server.clone();
let server_task = tokio::spawn(async move {
let requested = server_clone.recv_goal().await?;
let _accepted = requested.accept();
Ok::<(), zenoh::Error>(())
});
let goal_handle = client.send_goal(TestGoal { order: 10 }).await?;
let goal_id = goal_handle.id();
server_task.await??;
assert!(goal_id.is_valid());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_server_notify_goal_done() -> Result<()> {
let (_node, client, server) = setup_test().await?;
let server_clone = server.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::<(), zenoh::Error>(())
});
let goal_handle = client.send_goal(TestGoal { order: 10 }).await?;
server_task.await??;
let result = goal_handle.result().await?;
assert_eq!(result.value, 42);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_server_get_goal_status_array() -> Result<()> {
let (_node, client, server) = setup_test().await?;
let server_clone = server.clone();
let server_task = tokio::spawn(async move {
let requested = server_clone.recv_goal().await?;
let _accepted = requested.accept();
Ok::<(), zenoh::Error>(())
});
let goal_handle = client.send_goal(TestGoal { order: 10 }).await?;
server_task.await??;
assert!(goal_handle.id().is_valid());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_process_cancel_request() -> Result<()> {
use hiroz::action::messages::CancelGoalServiceResponse;
let (_node, client, server) = setup_test().await?;
let server_clone = server.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
let server_task = tokio::spawn(async move {
let requested = server_clone.recv_goal().await?;
let accepted = requested.accept();
let _executing = accepted.execute();
let _ = tx.send(());
let (cancel_request, query) = server_clone.recv_cancel().await?;
let response = CancelGoalServiceResponse {
return_code: 1,
goals_canceling: vec![cancel_request.goal_info.clone()],
};
server_clone.send_cancel_response_low(&query, &response)?;
Ok::<_, zenoh::Error>(cancel_request)
});
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
let goal_handle = client.send_goal(TestGoal { order: 10 }).await?;
rx.await.expect("server task ended prematurely");
let _cancel_response = goal_handle.cancel().await?;
let cancel_request = tokio::time::timeout(tokio::time::Duration::from_secs(2), server_task)
.await
.expect("timeout waiting for server task")??;
assert_eq!(cancel_request.goal_info.goal_id, goal_handle.id());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_server_goal_timeout_configuration() -> Result<()> {
use std::time::Duration;
let ctx = ZContextBuilder::default().build()?;
let node = ctx.create_node("test_timeout_node").build()?;
let server = node
.create_action_server::<TestAction>("test_timeout_action")
.with_goal_timeout(Duration::from_secs(30))
.build()?;
let _server_clone = server.clone();
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_action_server_qos_configuration() -> Result<()> {
let ctx = ZContextBuilder::default().build()?;
let node = ctx.create_node("test_qos_node").build()?;
let custom_qos = QosProfile {
reliability: QosReliability::BestEffort,
history: QosHistory::KeepLast(NonZeroUsize::new(5).unwrap()),
..Default::default()
};
let server = node
.create_action_server::<TestAction>("test_qos_action")
.with_goal_service_qos(custom_qos)
.with_feedback_topic_qos(custom_qos)
.build()?;
let _server_clone = server.clone();
Ok(())
}
}