diff --git a/Cargo.lock b/Cargo.lock index 0ec02b3..b3dc036 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2393,7 +2393,9 @@ dependencies = [ name = "ros-z-tests" version = "0.1.0" dependencies = [ + "nix 0.29.0", "once_cell", + "rand 0.8.5", "ros-z", "ros-z-msgs", "serial_test", diff --git a/ros-z-tests/Cargo.toml b/ros-z-tests/Cargo.toml index e4fd960..70d275d 100644 --- a/ros-z-tests/Cargo.toml +++ b/ros-z-tests/Cargo.toml @@ -17,6 +17,8 @@ ros-z-msgs = { path = "../ros-z-msgs", optional = true } tokio = { workspace = true, features = ["full"] } once_cell = { workspace = true } # For lazy static zenohd daemon serial_test = "3.0" # For sequential test execution +nix = { version = "0.29" , features = ["signal"] }# Used to send ctrl_c to child proccesses +rand = "0.8" # Used for test fuzzing [features] default = [] diff --git a/ros-z-tests/tests/common/mod.rs b/ros-z-tests/tests/common/mod.rs index c3ea79b..fba7bea 100644 --- a/ros-z-tests/tests/common/mod.rs +++ b/ros-z-tests/tests/common/mod.rs @@ -22,7 +22,14 @@ impl Drop for ProcessGuard { fn drop(&mut self) { if let Some(mut child) = self.child.take() { println!("Stopping process: {}", self.name); - let _ = child.kill(); + // Note: cannot directly kill a `ros2` command line command + // As that will kill the commandline process without giving it time to shutdown whatever + // child processes it spawns. + let pid = child.id(); + // Send Ctrl-C to trigger graceful shutdown + nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid as _), nix::sys::signal::SIGINT) + .expect("Failed to send SIGINT to process"); + // Wait for shutdown, we assume procces is well behaved and will exit let _ = child.wait(); } } diff --git a/ros-z-tests/tests/service_interop.rs b/ros-z-tests/tests/service_interop.rs index e00854e..a846b1e 100644 --- a/ros-z-tests/tests/service_interop.rs +++ b/ros-z-tests/tests/service_interop.rs @@ -90,8 +90,12 @@ fn test_ros_z_server_ros2_client() { println!("\n=== Test: ros-z server <-> ROS2 client ==="); + // Use shared state to confirm this specific server gets our specific request + let shared_state = std::sync::Arc::new(std::sync::Mutex::new(0)); + let shared_state_clone = shared_state.clone(); + // Start ros-z server - let _server = thread::spawn(|| { + let _server = thread::spawn(move || { let ctx = create_ros_z_context().expect("Failed to create context"); let node = ctx @@ -113,11 +117,17 @@ fn test_ros_z_server_ros2_client() { println!("Sending response: {}", resp.sum); zsrv.send_response(&resp, &key) .expect("Failed to send response"); + // Store most recent sum in shared state + *shared_state_clone.lock().unwrap() = resp.sum; } }); wait_for_ready(Duration::from_secs(10)); + // Pick two random values to add + let a = rand::random::() as i64; + let b = rand::random::() as i64; + // Call from ros2 CLI let output = Command::new("timeout") .args([ @@ -127,7 +137,7 @@ fn test_ros_z_server_ros2_client() { "call", "/add_two_ints", "example_interfaces/srv/AddTwoInts", - "{a: 10, b: 7}", + &format!("{{a: {}, b: {}}}", a, b), ]) .env("RMW_IMPLEMENTATION", "rmw_zenoh_cpp") .output() @@ -136,11 +146,15 @@ fn test_ros_z_server_ros2_client() { let stdout = String::from_utf8_lossy(&output.stdout); println!("ROS2 output: {}", stdout); assert!( - stdout.contains("sum: 17") || stdout.contains("sum=17"), - "Expected sum: 17, got: {}", + stdout.contains(&format!("sum: {}", a + b)) || stdout.contains(&format!("sum={}", a + b)), + "Expected sum: {}, got: {}", + a + b, stdout ); + // Confirm server received the request + let received_sum = shared_state.lock().unwrap(); + assert_eq!(*received_sum, a + b as i64); println!("✅ Test passed: ROS2 client called ros-z service"); }