use edgefirst_schemas::{sensor_msgs::IMU, serde_cdr};
use std::{
env,
process::{Child, Command},
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
thread,
time::{Duration, Instant},
};
use zenoh::Wait;
const MIN_EXPECTED_RATE_HZ: f64 = 50.0;
const COLLECTION_DURATION: Duration = Duration::from_secs(5);
const IMU_TOPIC: &str = "rt/imu";
fn find_imu_binary() -> String {
if let Ok(path) = env::var("IMU_BINARY") {
return path;
}
let candidates = [
"target/llvm-cov-target/profiling/edgefirst-imu",
"target/profiling/edgefirst-imu",
"target/release/edgefirst-imu",
"target/debug/edgefirst-imu",
];
for candidate in candidates {
if std::path::Path::new(candidate).exists() {
return candidate.to_string();
}
}
panic!("Could not find edgefirst-imu binary. Set IMU_BINARY environment variable.");
}
fn start_imu_service() -> Child {
let binary = find_imu_binary();
println!("Starting IMU service: {}", binary);
Command::new(&binary)
.spawn()
.expect("Failed to start IMU service")
}
fn stop_imu_service(mut child: Child) {
println!("Sending SIGTERM to IMU service (pid: {})", child.id());
unsafe {
libc::kill(child.id() as i32, libc::SIGTERM);
}
let start = Instant::now();
loop {
match child.try_wait() {
Ok(Some(status)) => {
println!("IMU service exited with status: {:?}", status);
return;
}
Ok(None) => {
if start.elapsed() > Duration::from_secs(5) {
println!("IMU service did not exit gracefully, killing...");
let _ = child.kill();
return;
}
thread::sleep(Duration::from_millis(100));
}
Err(e) => {
println!("Error waiting for IMU service: {}", e);
return;
}
}
}
}
#[test]
#[ignore] fn test_imu_publishing() {
let imu_process = start_imu_service();
thread::sleep(Duration::from_secs(2));
let session = zenoh::open(zenoh::Config::default())
.wait()
.expect("Failed to open Zenoh session");
let message_count = Arc::new(AtomicU64::new(0));
let message_count_clone = message_count.clone();
let subscriber = session
.declare_subscriber(IMU_TOPIC)
.callback(move |sample| {
match serde_cdr::deserialize::<IMU>(&sample.payload().to_bytes()) {
Ok(imu) => {
let mag = (imu.orientation.x.powi(2)
+ imu.orientation.y.powi(2)
+ imu.orientation.z.powi(2)
+ imu.orientation.w.powi(2))
.sqrt();
let now = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap();
let stamp_secs = imu.header.stamp.sec as u64;
let now_secs = now.as_secs();
assert!(
now_secs.abs_diff(stamp_secs) < 5,
"IMU timestamp {stamp_secs}s not close to wall time {now_secs}s"
);
if (mag - 1.0).abs() < 0.1 {
message_count_clone.fetch_add(1, Ordering::SeqCst);
} else {
eprintln!("Invalid quaternion magnitude: {}", mag);
}
}
Err(e) => {
eprintln!("Failed to decode IMU message: {}", e);
}
}
})
.wait()
.expect("Failed to create subscriber");
println!("Collecting IMU messages for {:?}...", COLLECTION_DURATION);
thread::sleep(COLLECTION_DURATION);
let count = message_count.load(Ordering::SeqCst);
let rate = count as f64 / COLLECTION_DURATION.as_secs_f64();
println!("Received {} messages in {:?}", count, COLLECTION_DURATION);
println!("Message rate: {:.1} Hz", rate);
drop(subscriber);
drop(session);
stop_imu_service(imu_process);
assert!(count > 0, "No IMU messages received!");
assert!(
rate >= MIN_EXPECTED_RATE_HZ,
"IMU rate {:.1} Hz is below minimum {:.1} Hz",
rate,
MIN_EXPECTED_RATE_HZ
);
println!("✓ Integration test passed!");
}
#[test]
#[ignore] fn test_graceful_shutdown() {
let imu_process = start_imu_service();
thread::sleep(Duration::from_secs(3));
let pid = imu_process.id();
println!("Sending SIGTERM to IMU service (pid: {})", pid);
unsafe {
libc::kill(pid as i32, libc::SIGTERM);
}
let mut child = imu_process;
let start = Instant::now();
let exit_status = loop {
match child.try_wait() {
Ok(Some(status)) => break Some(status),
Ok(None) => {
if start.elapsed() > Duration::from_secs(5) {
println!("Timeout waiting for graceful shutdown");
let _ = child.kill();
break None;
}
thread::sleep(Duration::from_millis(100));
}
Err(_) => break None,
}
};
assert!(
exit_status.is_some(),
"IMU service did not exit within timeout"
);
let status = exit_status.unwrap();
println!("IMU service exited with status: {:?}", status);
assert!(
status.success() || status.code().is_none(),
"IMU service did not exit cleanly: {:?}",
status
);
println!("✓ Graceful shutdown test passed!");
}