use std::time::Duration;
use hiroz::{
Builder, Result,
context::ZContextBuilder,
entity::{EndpointKind, NodeKey},
};
use hiroz_msgs::{example_interfaces::srv::AddTwoInts, std_msgs::String as RosString};
async fn setup_test_node(
node_name: &str,
) -> Result<(hiroz::context::ZContext, hiroz::node::ZNode)> {
let ctx = ZContextBuilder::default().build()?;
let node = ctx.create_node(node_name).build()?;
tokio::time::sleep(Duration::from_millis(100)).await;
Ok((ctx, node))
}
async fn wait_for_publishers(
node: &hiroz::node::ZNode,
topic: &str,
expected_count: usize,
timeout_ms: u64,
) -> Result<bool> {
let start = std::time::Instant::now();
let timeout = Duration::from_millis(timeout_ms);
loop {
let count = node.graph().count(EndpointKind::Publisher, topic);
if count >= expected_count {
return Ok(true);
}
if start.elapsed() >= timeout {
return Ok(false);
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
async fn wait_for_subscribers(
node: &hiroz::node::ZNode,
topic: &str,
expected_count: usize,
timeout_ms: u64,
) -> Result<bool> {
let start = std::time::Instant::now();
let timeout = Duration::from_millis(timeout_ms);
loop {
let count = node.graph().count(EndpointKind::Subscription, topic);
if count >= expected_count {
return Ok(true);
}
if start.elapsed() >= timeout {
return Ok(false);
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn test_get_topic_names_and_types() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let graph = node.graph().clone();
let topics = graph.get_topic_names_and_types();
assert!(
topics.is_empty()
|| topics
.iter()
.any(|(name, _)| name.contains("rosout") || name.contains("parameter_events"))
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_get_service_names_and_types() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let graph = node.graph().clone();
let services = graph.get_service_names_and_types();
assert!(
services.is_empty()
|| services
.iter()
.any(|(name, _)| name.contains("parameter")
|| name.contains("describe_parameters"))
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_count_publishers() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let topic_name = "/test_count_publishers";
let graph = node.graph().clone();
let count = graph.count(EndpointKind::Publisher, topic_name);
assert_eq!(count, 0, "Expected 0 publishers on non-existent topic");
let _pub = node.create_pub::<RosString>(topic_name).build()?;
tokio::time::sleep(Duration::from_millis(200)).await;
let count = graph.count(EndpointKind::Publisher, topic_name);
assert!(
count >= 1,
"Expected at least 1 publisher after creating one"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_count_subscribers() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let topic_name = "/test_count_subscribers";
let graph = node.graph().clone();
let count = graph.count(EndpointKind::Subscription, topic_name);
assert_eq!(count, 0, "Expected 0 subscribers on non-existent topic");
let _sub = node.create_sub::<RosString>(topic_name).build()?;
tokio::time::sleep(Duration::from_millis(200)).await;
let count = graph.count(EndpointKind::Subscription, topic_name);
assert!(
count >= 1,
"Expected at least 1 subscriber after creating one"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_count_clients() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let service_name = "/test_count_clients";
let graph = node.graph().clone();
let count = graph.count(EndpointKind::Client, service_name);
assert_eq!(count, 0, "Expected 0 clients on non-existent service");
let _client = node.create_client::<AddTwoInts>(service_name).build()?;
tokio::time::sleep(Duration::from_millis(200)).await;
let count = graph.count(EndpointKind::Client, service_name);
assert!(count >= 1, "Expected at least 1 client after creating one");
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_count_services() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let service_name = "/test_count_services";
let graph = node.graph().clone();
let count = graph.count(EndpointKind::Service, service_name);
assert_eq!(count, 0, "Expected 0 services on non-existent service");
let _service = node.create_service::<AddTwoInts>(service_name).build()?;
tokio::time::sleep(Duration::from_millis(200)).await;
let count = graph.count(EndpointKind::Service, service_name);
println!("Service count after creation: {}", count);
assert!(count >= 1, "Expected at least 1 service after creating one");
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_get_publisher_names_and_types_by_node() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let topic_name = "/test_pub_by_node";
let _pub = node.create_pub::<RosString>(topic_name).build()?;
tokio::time::sleep(Duration::from_millis(500)).await;
let graph = node.graph().clone();
let node_key: NodeKey = ("/".to_string(), "test_graph_node".to_string());
let entities = graph.get_entities_by_node(EndpointKind::Publisher, node_key);
if !entities.is_empty() {
assert!(
entities
.iter()
.any(|e| e.topic.contains("test_pub_by_node")),
"If entities found, should include our specific publisher"
);
}
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_get_subscriber_names_and_types_by_node() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let topic_name = "/test_sub_by_node";
let _sub = node.create_sub::<RosString>(topic_name).build()?;
tokio::time::sleep(Duration::from_millis(500)).await;
let graph = node.graph().clone();
let node_key: NodeKey = ("/".to_string(), "test_graph_node".to_string());
let entities = graph.get_entities_by_node(EndpointKind::Subscription, node_key);
if !entities.is_empty() {
assert!(
entities
.iter()
.any(|e| e.topic.contains("test_sub_by_node")),
"If entities found, should include our specific subscriber"
);
}
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_get_service_names_and_types_by_node() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let service_name = "/test_service_by_node";
let _service = node.create_service::<AddTwoInts>(service_name).build()?;
tokio::time::sleep(Duration::from_millis(200)).await;
let graph = node.graph().clone();
let node_key: NodeKey = ("".to_string(), "test_graph_node".to_string());
let entities = graph.get_entities_by_node(EndpointKind::Service, node_key);
assert!(!entities.is_empty(), "Expected to find service by node");
assert!(
entities
.iter()
.any(|e| e.topic.contains("test_service_by_node")),
"Expected to find our specific service"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_get_client_names_and_types_by_node() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let service_name = "/test_client_by_node";
let _client = node.create_client::<AddTwoInts>(service_name).build()?;
tokio::time::sleep(Duration::from_millis(200)).await;
let graph = node.graph().clone();
let node_key: NodeKey = ("".to_string(), "test_graph_node".to_string());
let entities = graph.get_entities_by_node(EndpointKind::Client, node_key);
assert!(!entities.is_empty(), "Expected to find client by node");
assert!(
entities
.iter()
.any(|e| e.topic.contains("test_client_by_node")),
"Expected to find our specific client"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_graph_query_functions() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let topic_name = format!(
"/test_graph_query_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
);
let graph = node.graph().clone();
let count_pubs = graph.count(EndpointKind::Publisher, &topic_name);
let count_subs = graph.count(EndpointKind::Subscription, &topic_name);
assert_eq!(count_pubs, 0, "Expected 0 publishers initially");
assert_eq!(count_subs, 0, "Expected 0 subscribers initially");
let pub_handle = node.create_pub::<RosString>(&topic_name).build()?;
tokio::time::sleep(Duration::from_millis(300)).await;
let count_pubs = graph.count(EndpointKind::Publisher, &topic_name);
assert!(
count_pubs >= 1,
"Expected at least 1 publisher after creation"
);
let sub_handle = node.create_sub::<RosString>(&topic_name).build()?;
tokio::time::sleep(Duration::from_millis(300)).await;
let count_pubs = graph.count(EndpointKind::Publisher, &topic_name);
let count_subs = graph.count(EndpointKind::Subscription, &topic_name);
assert!(count_pubs >= 1, "Expected at least 1 publisher");
assert!(count_subs >= 1, "Expected at least 1 subscriber");
drop(pub_handle);
tokio::time::sleep(Duration::from_millis(300)).await;
let count_pubs = graph.count(EndpointKind::Publisher, &topic_name);
let count_subs = graph.count(EndpointKind::Subscription, &topic_name);
assert_eq!(count_pubs, 0, "Expected 0 publishers after drop");
assert!(count_subs >= 1, "Expected at least 1 subscriber still");
drop(sub_handle);
tokio::time::sleep(Duration::from_millis(300)).await;
let count_pubs = graph.count(EndpointKind::Publisher, &topic_name);
let count_subs = graph.count(EndpointKind::Subscription, &topic_name);
assert_eq!(count_pubs, 0, "Expected 0 publishers after all drops");
assert_eq!(count_subs, 0, "Expected 0 subscribers after all drops");
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_get_node_names() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let graph = node.graph().clone();
let nodes = graph.get_node_names();
assert!(!nodes.is_empty(), "Expected to find at least one node");
assert!(
nodes.iter().any(|(name, _)| name == "test_graph_node"),
"Expected to find our test node"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_get_node_names_with_enclaves() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let graph = node.graph().clone();
let nodes = graph.get_node_names_with_enclaves();
assert!(!nodes.is_empty(), "Expected to find at least one node");
assert!(
nodes.iter().any(|(name, _, _)| name == "test_graph_node"),
"Expected to find our test node with enclave"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_multi_node_publishers() -> Result<()> {
let (_ctx1, node1) = setup_test_node("test_node_1").await?;
let (_ctx2, node2) = setup_test_node("test_node_2").await?;
let topic_name = "/test_multi_node_pub";
let _pub1 = node1.create_pub::<RosString>(topic_name).build()?;
let _pub2 = node2.create_pub::<RosString>(topic_name).build()?;
tokio::time::sleep(Duration::from_millis(800)).await;
let graph1 = node1.graph();
let count = graph1.count(EndpointKind::Publisher, topic_name);
assert!(
count >= 1,
"Expected at least 1 publisher from node1's view, got {}",
count
);
let graph2 = node2.graph();
let count = graph2.count(EndpointKind::Publisher, topic_name);
assert!(
count >= 1,
"Expected at least 1 publisher from node2's view, got {}",
count
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_multi_node_subscribers() -> Result<()> {
let (_ctx1, node1) = setup_test_node("test_node_1").await?;
let (_ctx2, node2) = setup_test_node("test_node_2").await?;
let topic_name = "/test_multi_node_sub";
let _sub1 = node1.create_sub::<RosString>(topic_name).build()?;
let _sub2 = node2.create_sub::<RosString>(topic_name).build()?;
tokio::time::sleep(Duration::from_millis(800)).await;
let graph1 = node1.graph();
let count = graph1.count(EndpointKind::Subscription, topic_name);
assert!(
count >= 1,
"Expected at least 1 subscriber from node1's view, got {}",
count
);
let graph2 = node2.graph();
let count = graph2.count(EndpointKind::Subscription, topic_name);
assert!(
count >= 1,
"Expected at least 1 subscriber from node2's view, got {}",
count
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_multi_node_services() -> Result<()> {
let ctx = ZContextBuilder::default().build()?;
let node1 = ctx.create_node("test_node_1").build()?;
let node2 = ctx.create_node("test_node_2").build()?;
let service_name1 = "/test_multi_node_service_1";
let service_name2 = "/test_multi_node_service_2";
let _srv1 = node1.create_service::<AddTwoInts>(service_name1).build()?;
let _srv2 = node2.create_service::<AddTwoInts>(service_name2).build()?;
tokio::time::sleep(Duration::from_millis(300)).await;
let graph1 = node1.graph();
let services = graph1.get_service_names_and_types();
assert!(
services.iter().any(|(name, _)| name.contains("service_1")),
"Expected to find service_1"
);
assert!(
services.iter().any(|(name, _)| name.contains("service_2")),
"Expected to find service_2"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_multi_node_clients() -> Result<()> {
let ctx = ZContextBuilder::default().build()?;
let node1 = ctx.create_node("test_node_1").build()?;
let node2 = ctx.create_node("test_node_2").build()?;
let service_name = "/test_multi_node_client";
let _srv = node1.create_service::<AddTwoInts>(service_name).build()?;
let _client1 = node1.create_client::<AddTwoInts>(service_name).build()?;
let _client2 = node2.create_client::<AddTwoInts>(service_name).build()?;
tokio::time::sleep(Duration::from_millis(300)).await;
let graph1 = node1.graph();
let count = graph1.count(EndpointKind::Client, service_name);
assert!(count >= 2, "Expected at least 2 clients");
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_service_server_is_available() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let service_name = "/test_service_available";
let client = node.create_client::<AddTwoInts>(service_name).build()?;
tokio::time::sleep(Duration::from_millis(100)).await;
let graph = node.graph().clone();
let count = graph.count(EndpointKind::Service, service_name);
assert_eq!(count, 0, "Expected 0 services before creating server");
let _service = node.create_service::<AddTwoInts>(service_name).build()?;
tokio::time::sleep(Duration::from_millis(300)).await;
let count = graph.count(EndpointKind::Service, service_name);
assert!(count >= 1, "Expected at least 1 service after creation");
drop(_service);
tokio::time::sleep(Duration::from_millis(300)).await;
let count = graph.count(EndpointKind::Service, service_name);
assert_eq!(count, 0, "Expected 0 services after dropping server");
drop(client);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_get_entities_by_topic() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let topic_name = "/test_entities_by_topic";
let _pub = node.create_pub::<RosString>(topic_name).build()?;
let _sub = node.create_sub::<RosString>(topic_name).build()?;
tokio::time::sleep(Duration::from_millis(300)).await;
let graph = node.graph().clone();
let pubs = graph.get_entities_by_topic(EndpointKind::Publisher, topic_name);
let subs = graph.get_entities_by_topic(EndpointKind::Subscription, topic_name);
assert!(!pubs.is_empty(), "Expected to find publishers");
assert!(!subs.is_empty(), "Expected to find subscribers");
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_wait_for_publishers() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let topic_name = "/test_wait_for_publishers";
let success = wait_for_publishers(&node, topic_name, 1, 100).await?;
assert!(!success, "Expected timeout since no publishers");
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_wait_for_subscribers() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let topic_name = "/test_wait_for_subscribers";
let success = wait_for_subscribers(&node, topic_name, 1, 100).await?;
assert!(!success, "Expected timeout since no subscribers");
Ok(())
}
#[cfg(feature = "ros2dds")]
#[tokio::test(flavor = "multi_thread")]
async fn test_ros2dds_context_graph_tracks_local_entities() -> Result<()> {
let ctx = ZContextBuilder::default()
.keyexpr_format(hiroz_protocol::KeyExprFormat::Ros2Dds)
.build()?;
let pub_node = ctx.create_node("test_graph_pub_dds").build()?;
let sub_node = ctx.create_node("test_graph_sub_dds").build()?;
let topic_name = "/test_ros2dds_context_graph";
let publisher = pub_node.create_pub::<RosString>(topic_name).build()?;
let subscriber = sub_node.create_sub::<RosString>(topic_name).build()?;
assert!(
publisher
.wait_for_subscription(1, Duration::from_secs(2))
.await
);
assert!(
subscriber
.wait_for_publisher(1, Duration::from_secs(2))
.await
);
let graph = ctx.graph();
assert!(
graph.count(EndpointKind::Publisher, topic_name) >= 1,
"Expected Ros2Dds graph to discover local publisher"
);
assert!(
graph.count(EndpointKind::Subscription, topic_name) >= 1,
"Expected Ros2Dds graph to discover local subscriber"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_action_names_and_types() -> Result<()> {
let (_ctx, node) = setup_test_node("test_graph_node").await?;
let graph = node.graph().clone();
let actions = graph.get_action_names_and_types();
assert!(actions.is_empty() || !actions.is_empty());
Ok(())
}
}