@@ -178,3 +178,48 @@ def test_execute_process_with_output_dictionary():
178178 ls = LaunchService ()
179179 ls .include_launch_description (ld )
180180 assert 0 == ls .run ()
181+
182+
183+ def test_execute_process_with_shutdown_on_error ():
184+ """Test proper shutdown of children after exception during launch."""
185+ exited_processes = 0
186+
187+ def on_exit (event , context ):
188+ nonlocal exited_processes
189+ print (event , context )
190+ exited_processes += 1
191+
192+ executable_1 = ExecuteLocal (
193+ process_description = Executable (
194+ cmd = [sys .executable , '-c' , 'while True: pass' ]
195+ ),
196+ output = {'stdout' : 'screen' , 'stderr' : 'screen' },
197+ on_exit = on_exit ,
198+ )
199+ executable_2 = ExecuteLocal (
200+ process_description = Executable (
201+ cmd = [sys .executable , '-c' , 'while True: pass' ]
202+ ),
203+ output = {'stdout' : 'screen' , 'stderr' : 'screen' },
204+ on_exit = on_exit ,
205+ )
206+
207+ # It's slightly tricky to coerce the standard implementation to fail in
208+ # this way. However, launch_ros's Node class can fail similar to this and
209+ # this case therefore needs to be handled correctly.
210+ class ExecutableThatFails (ExecuteLocal ):
211+
212+ def execute (self , context ):
213+ raise Exception ('Execute Local failed' )
214+
215+ executable_invalid = ExecutableThatFails (
216+ process_description = Executable (
217+ cmd = ['fake_process_that_doesnt_exists' ]
218+ ),
219+ output = {'stdout' : 'screen' , 'stderr' : 'screen' },
220+ )
221+ ld = LaunchDescription ([executable_1 , executable_2 , executable_invalid ])
222+ ls = LaunchService ()
223+ ls .include_launch_description (ld )
224+ assert ls .run () == 1
225+ assert exited_processes == 2
0 commit comments