def test_calling_kill_subprocesses_will_break_out_of_command_execution_wait_loop(self): def fake_communicate(timeout=None): # The fake implementation is that communicate() times out forever until os.killpg is called. if mock_killpg.call_count == 0 and timeout is not None: raise TimeoutExpired(None, timeout) elif mock_killpg.call_count > 0: return b'fake output', b'fake error' self.fail('Popen.communicate() should not be called without a timeout before os.killpg has been called.') mock_killpg = self.patch('os.killpg') self.mock_popen.communicate.side_effect = fake_communicate self.mock_popen.returncode = 1 self.mock_popen.pid = 55555 project_type = ProjectType() command_thread = SafeThread(target=project_type.execute_command_in_project, args=('echo The power is yours!',)) # This calls execute_command_in_project() on one thread, and calls kill_subprocesses() on another. The # kill_subprocesses() call should cause the first thread to exit. command_thread.start() project_type.kill_subprocesses() # This *should* join immediately, but we specify a timeout just in case something goes wrong so that the test # doesn't hang. A successful join implies success. We also use the UnhandledExceptionHandler so that exceptions # propagate from the child thread to the test thread and fail the test. with UnhandledExceptionHandler.singleton(): command_thread.join(timeout=10) if command_thread.is_alive(): mock_killpg() # Calling killpg() causes the command thread to end. self.fail('project_type.kill_subprocesses should cause the command execution wait loop to exit.') mock_killpg.assert_called_once_with(pgid=55555, sig=ANY)
def test_executing_build_teardown_multiple_times_will_raise_exception( self): self.mock_network.post().status_code = http.client.OK slave = self._create_cluster_slave() project_type_mock = self.patch( 'app.slave.cluster_slave.util.create_project_type').return_value # This test uses setup_complete_event to detect when the async fetch_project() has executed. setup_complete_event = Event() project_type_mock.fetch_project.side_effect = self.no_args_side_effect( setup_complete_event.set) # This test uses teardown_event to cause a thread to block on the teardown_build() call. teardown_event = Event() project_type_mock.teardown_build.side_effect = self.no_args_side_effect( teardown_event.wait) slave.connect_to_master(self._FAKE_MASTER_URL) slave.setup_build(build_id=123, project_type_params={'type': 'Fake'}) self.assertTrue(setup_complete_event.wait(timeout=5), 'Build setup should complete very quickly.') # Start the first thread that does build teardown. This thread will block on teardown_build(). first_thread = SafeThread(target=slave._do_build_teardown_and_reset) first_thread.start() # Call build teardown() again and it should raise an exception. with self.assertRaises(BuildTeardownError): slave._do_build_teardown_and_reset() # Cleanup: Unblock the first thread and let it finish. We use the unhandled exception handler just in case any # exceptions occurred on the thread (so that they'd be passed back to the main thread and fail the test). teardown_event.set() with UnhandledExceptionHandler.singleton(): first_thread.join()
def test_calling_kill_subprocesses_will_break_out_of_command_execution_wait_loop( self): self._mock_stdout_and_stderr(b'fake_output', b'fake_error') self.mock_popen.pid = 55555 self._simulate_hanging_popen_process() project_type = ProjectType() command_thread = SafeThread( target=project_type.execute_command_in_project, args=('echo The power is yours!', )) # This calls execute_command_in_project() on one thread, and calls kill_subprocesses() on another. The # kill_subprocesses() call should cause the first thread to exit. command_thread.start() project_type.kill_subprocesses() # This *should* join immediately, but we specify a timeout just in case something goes wrong so that the test # doesn't hang. A successful join implies success. We also use the UnhandledExceptionHandler so that exceptions # propagate from the child thread to the test thread and fail the test. with UnhandledExceptionHandler.singleton(): command_thread.join(timeout=10) if command_thread.is_alive(): self.mock_killpg( ) # Calling killpg() causes the command thread to end. self.fail( 'project_type.kill_subprocesses should cause the command execution wait loop to exit.' ) self.mock_killpg.assert_called_once_with( 55555, ANY) # Note: os.killpg does not accept keyword args.
def test_executing_build_teardown_multiple_times_will_raise_exception(self): self.mock_network.post().status_code = http.client.OK slave = self._create_cluster_slave() project_type_mock = self.patch('app.slave.cluster_slave.util.create_project_type').return_value # This test uses setup_complete_event to detect when the async fetch_project() has executed. setup_complete_event = Event() project_type_mock.fetch_project.side_effect = self.no_args_side_effect(setup_complete_event.set) # This test uses teardown_event to cause a thread to block on the teardown_build() call. teardown_event = Event() project_type_mock.teardown_build.side_effect = self.no_args_side_effect(teardown_event.wait) slave.connect_to_master(self._FAKE_MASTER_URL) slave.setup_build(build_id=123, project_type_params={'type': 'Fake'}, build_executor_start_index=0) self.assertTrue(setup_complete_event.wait(timeout=5), 'Build setup should complete very quickly.') # Start the first thread that does build teardown. This thread will block on teardown_build(). first_thread = SafeThread(target=slave._do_build_teardown_and_reset) first_thread.start() # Call build teardown() again and it should raise an exception. with self.assertRaises(BuildTeardownError): slave._do_build_teardown_and_reset() # Cleanup: Unblock the first thread and let it finish. We use the unhandled exception handler just in case any # exceptions occurred on the thread (so that they'd be passed back to the main thread and fail the test). teardown_event.set() with UnhandledExceptionHandler.singleton(): first_thread.join()
def run(self, *args, **kwargs): app_thread = SafeThread( name=self._THREAD_NAME, target=self.async_run, args=args, kwargs=kwargs, ) app_thread.start() app_thread.join()
def test_exception_on_safe_thread_calls_teardown_callbacks(self): my_awesome_teardown_callback = MagicMock() unhandled_exception_handler = UnhandledExceptionHandler.singleton() unhandled_exception_handler.add_teardown_callback(my_awesome_teardown_callback, 'fake arg', fake_kwarg='boop') def my_terrible_method(): raise Exception('Sic semper tyrannis!') thread = SafeThread(target=my_terrible_method) thread.start() thread.join() my_awesome_teardown_callback.assert_called_once_with('fake arg', fake_kwarg='boop')
def test_normal_execution_on_safe_thread_does_not_call_teardown_callbacks(self): my_lonely_teardown_callback = MagicMock() unhandled_exception_handler = UnhandledExceptionHandler.singleton() unhandled_exception_handler.add_teardown_callback(my_lonely_teardown_callback) def my_fantastic_method(): print('Veritas vos liberabit!') thread = SafeThread(target=my_fantastic_method) thread.start() thread.join() self.assertFalse(my_lonely_teardown_callback.called, 'The teardown callback should not be called unless an exception is raised.')
def test_exception_on_safe_thread_calls_teardown_callbacks(self): my_awesome_teardown_callback = MagicMock() unhandled_exception_handler = UnhandledExceptionHandler.singleton() unhandled_exception_handler.add_teardown_callback( my_awesome_teardown_callback, 'fake arg', fake_kwarg='boop') def my_terrible_method(): raise Exception('Sic semper tyrannis!') thread = SafeThread(target=my_terrible_method) thread.start() thread.join() my_awesome_teardown_callback.assert_called_once_with('fake arg', fake_kwarg='boop')
def test_normal_execution_on_safe_thread_does_not_call_teardown_callbacks( self): my_lonely_teardown_callback = MagicMock() unhandled_exception_handler = UnhandledExceptionHandler.singleton() unhandled_exception_handler.add_teardown_callback( my_lonely_teardown_callback) def my_fantastic_method(): print('Veritas vos liberabit!') thread = SafeThread(target=my_fantastic_method) thread.start() thread.join() self.assertFalse( my_lonely_teardown_callback.called, 'The teardown callback should not be called unless an exception is raised.' )
def test_calling_kill_subprocesses_will_break_out_of_command_execution_wait_loop(self): self._mock_out_popen_communicate() project_type = ProjectType() command_thread = SafeThread(target=project_type.execute_command_in_project, args=('echo The power is yours!',)) # This calls execute_command_in_project() on one thread, and calls kill_subprocesses() on another. The # kill_subprocesses() call should cause the first thread to exit. command_thread.start() project_type.kill_subprocesses() # This *should* join immediately, but we specify a timeout just in case something goes wrong so that the test # doesn't hang. A successful join implies success. We also use the UnhandledExceptionHandler so that exceptions # propagate from the child thread to the test thread and fail the test. with UnhandledExceptionHandler.singleton(): command_thread.join(timeout=10) if command_thread.is_alive(): self.mock_killpg() # Calling killpg() causes the command thread to end. self.fail('project_type.kill_subprocesses should cause the command execution wait loop to exit.') self.mock_killpg.assert_called_once_with(55555, ANY) # Note: os.killpg does not accept keyword args.