11use std:: process:: { Child , Command , Stdio } ;
2- use std:: sync:: atomic:: { AtomicU16 , Ordering } ;
32use std:: thread;
43use 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
8778pub struct TestRouter {
8879 pub port : u16 ,
@@ -91,34 +82,49 @@ pub struct TestRouter {
9182}
9283
9384impl 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