Skip to content

Commit 2864cd2

Browse files
committed
fix(tests): use OS-assigned ports in hiroz-console TestRouter
Replaces the PID-derived NEXT_PORT counter with bind(:0) port allocation, matching the hiroz-tests TestRouter. Prevents port conflicts when hiroz-tests and hiroz-console test binaries run sequentially on the same runner and happen to derive the same base port from their PIDs.
1 parent 7c85059 commit 2864cd2

1 file changed

Lines changed: 41 additions & 35 deletions

File tree

  • crates/hiroz-console/tests/common

crates/hiroz-console/tests/common/mod.rs

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::process::{Child, Command, Stdio};
2-
use std::sync::atomic::{AtomicU16, Ordering};
32
use std::thread;
43
use std::time::Duration;
54

@@ -75,14 +74,6 @@ impl Drop for ProcessGuard {
7574
}
7675
}
7776

78-
/// Port counter for generating unique Zenoh router ports per test
79-
static NEXT_PORT: once_cell::sync::Lazy<AtomicU16> = once_cell::sync::Lazy::new(|| {
80-
let pid = std::process::id();
81-
let base_port = 30000 + ((pid % 10000) as u16);
82-
println!("Test process {} using base port {}", pid, base_port);
83-
AtomicU16::new(base_port)
84-
});
85-
8677
/// Per-test Zenoh router configuration
8778
pub struct TestRouter {
8879
pub port: u16,
@@ -91,34 +82,49 @@ pub struct TestRouter {
9182
}
9283

9384
impl TestRouter {
94-
/// Start a new Zenoh router session on a unique port for this test
85+
/// Start a new Zenoh router session on a free OS-assigned port.
86+
///
87+
/// Uses bind(:0) to avoid PID-derived port collisions when multiple test
88+
/// binaries run in parallel (e.g. hiroz-tests and hiroz-console).
9589
pub fn new() -> Self {
96-
let port = NEXT_PORT.fetch_add(1, Ordering::SeqCst);
97-
let endpoint = format!("tcp/127.0.0.1:{}", port);
98-
99-
println!("Starting Zenoh router on port {}...", port);
100-
101-
let mut config = zenoh::Config::default();
102-
config.set_mode(Some(WhatAmI::Router)).unwrap();
103-
config
104-
.insert_json5("listen/endpoints", &format!("[\"{}\"]", endpoint))
105-
.unwrap();
106-
config
107-
.insert_json5("scouting/multicast/enabled", "false")
108-
.unwrap();
109-
110-
let session = zenoh::open(config)
111-
.wait()
112-
.expect("Failed to open Zenoh router session");
113-
114-
thread::sleep(Duration::from_millis(500));
115-
println!("Zenoh router ready on {}", endpoint);
116-
117-
Self {
118-
port,
119-
endpoint: endpoint.clone(),
120-
_session: session,
90+
for attempt in 0..5u32 {
91+
let port = {
92+
let listener =
93+
std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind port 0");
94+
listener.local_addr().unwrap().port()
95+
};
96+
let endpoint = format!("tcp/127.0.0.1:{}", port);
97+
println!(
98+
"Starting Zenoh router on port {} (attempt {})...",
99+
port,
100+
attempt + 1
101+
);
102+
103+
let mut config = zenoh::Config::default();
104+
config.set_mode(Some(WhatAmI::Router)).unwrap();
105+
config
106+
.insert_json5("listen/endpoints", &format!("[\"{}\"]", endpoint))
107+
.unwrap();
108+
config
109+
.insert_json5("scouting/multicast/enabled", "false")
110+
.unwrap();
111+
112+
match zenoh::open(config).wait() {
113+
Ok(session) => {
114+
thread::sleep(Duration::from_millis(500));
115+
println!("Zenoh router ready on {}", endpoint);
116+
return Self {
117+
port,
118+
endpoint,
119+
_session: session,
120+
};
121+
}
122+
Err(e) => {
123+
println!("Port {} unavailable ({}), retrying...", port, e);
124+
}
125+
}
121126
}
127+
panic!("Failed to start Zenoh router after 5 attempts");
122128
}
123129

124130
pub fn endpoint(&self) -> &str {

0 commit comments

Comments
 (0)