@@ -342,45 +342,46 @@ def configure_case(kind: str) -> list[str]:
342342 return commands
343343
344344
345- def sitl_log_counts (sitl : SitlProcess ) -> tuple [ int , int ] :
345+ def sitl_log_count (sitl : SitlProcess , marker : str ) -> int :
346346 try :
347347 text = sitl .log_path .read_text (encoding = "utf-8" , errors = "replace" )
348348 except OSError :
349- return 0 , 0
350- return text .count ("[SYSTEM] Reset" ), text . count ( "[SYSTEM] Init..." )
349+ return 0
350+ return text .count (marker )
351351
352352
353- def wait_for_sitl_reboot (
354- sitl : SitlProcess ,
355- reset_count_before : int ,
356- init_count_before : int ,
357- timeout_s : float = 8.0 ,
358- ) -> None :
359- """Wait for CLI `save` to actually reset and finish re-execing SITL.
360-
361- Waiting for the TCP port alone is insufficient: the pre-reset listener can
362- still accept a client while the delayed CLI reboot is pending, after which
363- systemReset() closes that just-opened MSP connection underneath the test.
364- """
353+ def wait_for_sitl_reset (sitl : SitlProcess , reset_count_before : int , timeout_s : float = 8.0 ) -> None :
354+ """Wait until CLI `save` has persisted configuration and entered reset."""
365355 deadline = time .monotonic () + timeout_s
366- saw_reset = False
367356 last_reset_count = reset_count_before
368- last_init_count = init_count_before
369357 while time .monotonic () < deadline :
370358 sitl .check_alive ()
371- last_reset_count , last_init_count = sitl_log_counts (sitl )
359+ last_reset_count = sitl_log_count (sitl , "[SYSTEM] Reset" )
372360 if last_reset_count > reset_count_before :
373- saw_reset = True
374- if saw_reset and last_init_count > init_count_before :
375361 return
376362 time .sleep (0.02 )
377363 raise TestFailure (
378- "SITL did not complete CLI save/reboot cycle within "
379- f"{ timeout_s :.1f} s (reset { reset_count_before } ->{ last_reset_count } , "
380- f"init { init_count_before } -> { last_init_count } ) \n { sitl .tail_log (lines = 80 )} "
364+ "SITL did not enter CLI save/reset cycle within "
365+ f"{ timeout_s :.1f} s (reset { reset_count_before } ->{ last_reset_count } ) \n "
366+ f"{ sitl .tail_log (lines = 80 )} "
381367 )
382368
383369
370+ def hard_restart_sitl_after_configuration (sitl : SitlProcess , reset_count_before : int ) -> None :
371+ """Replace SITL's in-process exec reboot with a clean harness restart.
372+
373+ The mixed serial layout opens UARTs lazily. During the exec-based reboot a
374+ TCP listener can survive long enough for the new instance to hit EADDRINUSE,
375+ leaving a receiver connected to the dying listener. Once `save` has reached
376+ systemReset(), terminate that process completely and start a fresh one from
377+ the saved EEPROM so every UART listener is recreated from a clean process.
378+ """
379+ wait_for_sitl_reset (sitl , reset_count_before )
380+ sitl .stop ()
381+ time .sleep (0.05 )
382+ sitl .start ()
383+
384+
384385def run_case (kind : str , binary : Path , repo : Path , tcp_base : int , temp_dir : Path ) -> None :
385386 eeprom = temp_dir / f"{ kind } .bin"
386387 log_path = temp_dir / f"{ kind } .log"
@@ -393,9 +394,9 @@ def run_case(kind: str, binary: Path, repo: Path, tcp_base: int, temp_dir: Path)
393394 msp_port = tcp_port (tcp_base , UART_MSP )
394395 wait_tcp ("127.0.0.1" , msp_port , 8.0 , sitl )
395396 time .sleep (0.15 )
396- reset_count , init_count = sitl_log_counts (sitl )
397+ reset_count = sitl_log_count (sitl , "[SYSTEM] Reset" )
397398 configure_cli ("127.0.0.1" , msp_port , configure_case (kind ))
398- wait_for_sitl_reboot (sitl , reset_count , init_count )
399+ hard_restart_sitl_after_configuration (sitl , reset_count )
399400 wait_tcp ("127.0.0.1" , msp_port , 8.0 , sitl )
400401
401402 msp = MspClient ("127.0.0.1" , msp_port , timeout_s = 2.0 )
0 commit comments