3030# MAX_SAMPLES_SAVED is the maximum number of samples saved.
3131MAX_SAMPLES_SAVED = 500
3232
33+ def stop_process (child_process , timeout = 30 , poll_interval = 0.2 ):
34+ """
35+ Stops a pexpect child process using SIGINT (Ctrl+C),
36+ and forcefully terminates it if it doesn't exit within the timeout.
37+
38+ Parameters:
39+ child_process (pexpect.spawn): The process to stop.
40+ timeout (int): Max time (in seconds) to wait for graceful exit.
41+ poll_interval (float): Time between checks in seconds.
42+
43+ Returns:
44+ bool: True if process exited gracefully, False if it was killed.
45+ """
46+ if child_process .isalive ():
47+ try :
48+ child_process .sendintr ()
49+ except Exception as e :
50+ return True # Process already exited
51+ else :
52+ return True # Process already exited
53+
54+ start_time = time .time ()
55+
56+ while child_process .isalive () and (time .time () - start_time < timeout ):
57+ time .sleep (poll_interval )
58+
59+ if child_process .isalive ():
60+ child_process .terminate (force = True )
61+ return False # Process was forcefully terminated
62+
63+ return True
64+
3365def run_subscriber_shape_main (
3466 name_executable : str ,
3567 parameters : str ,
@@ -165,11 +197,13 @@ def run_subscriber_shape_main(
165197 log_message (f'Subscriber { subscriber_index } : Waiting for Publishers to '
166198 'finish' , verbosity )
167199 for element in publishers_finished :
168- element .wait () # wait for all publishers to finish
169- # Send SIGINT to nicely close the application
170- if child_sub .isalive ():
171- child_sub .sendintr ()
172- child_sub .wait ()
200+ element .wait () # wait for all publishers to finish
201+ # Stop process
202+ if not stop_process (child_sub ):
203+ log_message (f'Subscriber { subscriber_index } process did not exit '
204+ 'gracefully; it was forcefully terminated.' ,
205+ verbosity )
206+
173207 return
174208
175209
@@ -332,10 +366,12 @@ def run_publisher_shape_main(
332366 for element in subscribers_finished :
333367 element .wait () # wait for all subscribers to finish
334368 publisher_finished .set () # set publisher as finished
335- # Send SIGINT to nicely close the application
336- if child_pub .isalive ():
337- child_pub .sendintr ()
338- child_pub .wait ()
369+ # Stop process
370+ if not stop_process (child_pub ):
371+ log_message (f'Publisher { publisher_index } process did not exit '
372+ 'gracefully; it was forcefully terminated.' ,
373+ verbosity )
374+
339375 return
340376
341377
@@ -599,18 +635,27 @@ def parser():
599635 help = 'Print debug information to stdout. This option also shows the '
600636 'shape_main application output in case of error. '
601637 'If this option is not used, only the test results are printed '
602- 'in the stdout. (Default: False).' )
638+ 'in the stdout. '
639+ 'Default: False' )
603640 optional .add_argument ('-x' ,'--data-representation' ,
604641 default = "2" ,
605642 required = None ,
606643 type = str ,
607644 choices = ["1" ,"2" ],
608645 help = 'Data Representation used if no provided when running the '
609646 'shape_main application. If this application already sets the '
610- 'data representation, this parameter is not used.'
647+ 'data representation, this parameter is not used. '
611648 'The potential values are 1 for XCDR1 and 2 for XCDR2.'
612649 'Default value 2.' )
613650
651+ optional .add_argument ('-a' , '--periodic-announcement' ,
652+ default = 0 ,
653+ required = False ,
654+ type = int ,
655+ metavar = 'periodic_announcement_ms' ,
656+ help = 'Indicates the periodic participant announcement period in ms. '
657+ 'Default: 0 (off).' )
658+
614659 tests = parser .add_argument_group (title = 'Test Case and Test Suite' )
615660 tests .add_argument ('-s' , '--suite' ,
616661 default = 'test_suite' ,
@@ -623,7 +668,7 @@ def parser():
623668 'This value should not contain the extension ".py", '
624669 'only the name of the file. '
625670 'It will run all the dictionaries defined in the file. '
626- '( Default: test_suite) .' )
671+ 'Default: test_suite.' )
627672
628673 enable_disable = tests .add_mutually_exclusive_group (required = False )
629674 enable_disable .add_argument ('-t' , '--test' ,
@@ -635,7 +680,7 @@ def parser():
635680 help = 'Test Case that the script will run. '
636681 'This option is not supported with --disable-test. '
637682 'This allows to set multiple values separated by a space. '
638- '( Default: run all Test Cases from the Test Suite.) ' )
683+ 'Default: run all Test Cases from the Test Suite.' )
639684 enable_disable .add_argument ('-d' , '--disable-test' ,
640685 nargs = '+' ,
641686 default = None ,
@@ -644,7 +689,8 @@ def parser():
644689 metavar = 'test_cases_disabled' ,
645690 help = 'Test Case that the script will skip. '
646691 'This allows to set multiple values separated by a space. '
647- 'This option is not supported with --test. (Default: None)' )
692+ 'This option is not supported with --test. '
693+ 'Default: None' )
648694
649695 out_opts = parser .add_argument_group (title = 'output options' )
650696 out_opts .add_argument ('-o' , '--output-name' ,
@@ -655,7 +701,7 @@ def parser():
655701 'If the file passed already exists, it will add '
656702 'the new results to it. In other case it will create '
657703 'a new file. '
658- '( Default: <publisher_name>-<subscriber_name>-date.xml) ' )
704+ 'Default: <publisher_name>-<subscriber_name>-date.xml' )
659705
660706 return parser
661707
@@ -682,6 +728,7 @@ def main():
682728 'test_cases' : args .test ,
683729 'test_cases_disabled' : args .disable_test ,
684730 'data_representation' : args .data_representation ,
731+ 'periodic_announcement_ms' : args .periodic_announcement ,
685732 }
686733
687734 # The executables's names are supposed to follow the pattern: name_shape_main
@@ -714,7 +761,7 @@ def main():
714761 # applications. A TestSuite contains a collection of TestCases.
715762 suite = junitparser .TestSuite (f"{ name_publisher } ---{ name_subscriber } " )
716763
717- timeout = 10
764+ timeout = 15
718765 now = datetime .now ()
719766
720767 t_suite_module = importlib .import_module (options ['test_suite' ])
@@ -768,9 +815,16 @@ def main():
768815
769816 assert (len (parameters ) == len (expected_codes ))
770817
771- for element in parameters :
818+ for i , element in enumerate ( parameters ) :
772819 if not '-x ' in element :
773- element += f'-x { options ["data_representation" ]} '
820+ element += f' -x { options ["data_representation" ]} '
821+ # Add periodic announcement argument if needed
822+ if options ['periodic_announcement_ms' ] > 0 \
823+ and not '--periodic-announcement ' in element \
824+ and 'connext' in options ['publisher' ].lower () \
825+ and '-P' in element :
826+ element += f' --periodic-announcement { options ["periodic_announcement_ms" ]} '
827+ parameters [i ] = element # Update the list in place
774828
775829 case = junitparser .TestCase (f'{ test_suite_name } _{ test_case_name } ' )
776830 now_test_case = datetime .now ()
0 commit comments