def test_ses_summarise_returns_all_summaries_when_no_pid_requested(): """ Verify that summaries for all procedures are returned when no specific PID is requested. """ procedure_a = Procedure("test://a", procedure_id=1) procedure_b = Procedure("test://b", procedure_id=2) procedure_c = Procedure("test://c", procedure_id=3) procedures = {1: procedure_a, 2: procedure_b, 3: procedure_c} expected = [ create_empty_procedure_summary(1, "test://a", procedure_a.history), create_empty_procedure_summary(2, "test://b", procedure_b.history), create_empty_procedure_summary(3, "test://c", procedure_c.history), ] with mock.patch( "oet.procedure.application.application.domain.ProcessManager" ) as mock_pm: # get the mock ProcessManager instance instance = mock_pm.return_value # the manager's procedures attribute holds created procedures and is # used for retrieval instance.procedures = procedures service = ScriptExecutionService() returned = service.summarise() assert returned == expected
def test_ses_summarise_returns_summaries_for_requested_pids(): """ ScriptExecutionService.summarise() should only return status for requested procedures. """ procedure_a = Procedure("test://a", procedure_id=1) procedure_b = Procedure("test://b", procedure_id=2) procedure_c = Procedure("test://c", procedure_id=3) procedures = {1: procedure_a, 2: procedure_b, 3: procedure_c} expected = [ create_empty_procedure_summary(1, "test://a", procedure_a.history), create_empty_procedure_summary(3, "test://c", procedure_c.history), ] with mock.patch( "oet.procedure.application.application.domain.ProcessManager" ) as mock_pm: # get the mock ProcessManager instance instance = mock_pm.return_value # the manager's procedures attribute holds created procedures and is # used for retrieval instance.procedures = procedures service = ScriptExecutionService() returned = service.summarise([1, 3]) assert returned == expected
def test_procedure_run_executes_user_script(script_with_queue_path): """ Verify that user script executes when run() is called """ procedure = Procedure(script_uri=script_with_queue_path) queue = multiprocessing.Queue() procedure.script_args["run"].args = [queue, procedure] procedure.run() assert queue.qsize() == 1 assert queue.get() is None
def test_procedure_run_catches_and_stores_script_exception(fail_script): """ Verify that run() catches an exception thrown in a script and places it in the stacktrace queue """ procedure = Procedure(script_uri=fail_script) procedure.run() try: procedure.stacktrace_queue.get(timeout=1) except Exception: # pylint: disable=broad-except # test should not raise an exception, so fail if it does pytest.fail("Stacktrace not found in queue")
def test_ses_prepare_call_sequence_and_returns_summary_for_created_process(): """ Verify that ScriptExecutionService.prepare() calls the appropriate domain object methods for process creation and returns the expected summary object """ script_uri = "test://test.py" cmd = PrepareProcessCommand(script_uri=script_uri, init_args=ProcedureInput()) procedure = Procedure(script_uri, procedure_id=123) procedures = {123: procedure} expected = ProcedureSummary( id=123, script_uri=procedure.script_uri, script_args=procedure.script_args, history=procedure.history, state=procedure.state, ) with mock.patch( "oet.procedure.application.application.domain.ProcessManager" ) as mock_pm: # get the mock ProcessManager instance instance = mock_pm.return_value # tell ProcessManager.create to return PID 123, which is subsequently # used for lookup instance.create.return_value = 123 # the manager's procedures attribute holds created procedures and is # used for retrieval instance.procedures = procedures service = ScriptExecutionService() returned = service.prepare(cmd) instance.create.assert_called_once_with(script_uri, init_args=ProcedureInput()) assert returned == expected
def test_procedure_init_raises_exception_on_script_file_not_found(): """ Verify that FileNotFoundError is raised if script file does not exist """ script_uri = "file://abcbs" with pytest.raises(FileNotFoundError): _ = Procedure(script_uri=script_uri)
def test_ses_summarise_fails_when_invalid_pid_requested(): """ Verify that ScriptExecutionService.summarise() fails when an invalid procedure ID is requested. """ procedure_a = Procedure("test://a") procedure_b = Procedure("test://b") procedure_c = Procedure("test://c") procedures = {1: procedure_a, 2: procedure_b, 3: procedure_c} with mock.patch( "oet.procedure.application.application.domain.ProcessManager" ) as mock_pm: # get the mock ProcessManager instance instance = mock_pm.return_value # the manager's procedures attribute holds created procedures and is # used for retrieval instance.procedures = procedures service = ScriptExecutionService() with pytest.raises(ValueError): service.summarise([543534])
def test_ses_stop_calls_process_manager_function_with_no_script_execution(abort_script): """ Verify that ScriptExecutionService.stop() calls the appropriate domain object methods for stopping process execution without executing abort python script. """ # PID of running process running_pid = 123 # Test script/procedures will target sub-array 2 init_args = ProcedureInput(subarray_id=2) # Create Procedure representing the script to be stopped procedure_to_stop = Procedure("test://a") procedure_to_stop.script_args["init"] = init_args # Prepare a dict of PIDs to Procedures that we can use to mock the internal # data structure held by ProcessManager. process_manager_procedures = {running_pid: procedure_to_stop} cmd = StopProcessCommand(process_uid=running_pid, run_abort=False) # returned summary list should be empty if abort script is bypassed expected = [] with mock.patch( "oet.procedure.application.application.domain.ProcessManager" ) as mock_pm: # get the mock ProcessManager instance, preparing it for SES access instance = mock_pm.return_value instance.procedures = process_manager_procedures service = ScriptExecutionService(abort_script_uri=abort_script) returned = service.stop(cmd) # service should call stop() and return empty list instance.stop.assert_called_once_with(running_pid) assert returned == expected
def test_ses_get_subarray_id_for_requested_pid(): """ Verify that the private method _get_subarray_id returns subarray id correctly """ subarray_id = 123 process_pid = 456 procedure = Procedure("test://a") init_args = ProcedureInput(subarray_id=subarray_id) procedure.script_args["init"] = init_args procedures = {process_pid: procedure} process_summary = ProcedureSummary( id=process_pid, script_uri=procedure.script_uri, script_args=procedure.script_args, history=procedure.history, state=procedure.state, ) expected = [process_summary] with mock.patch( "oet.procedure.application.application.domain.ProcessManager" ) as mock_pm: # get the mock ProcessManager instance instance = mock_pm.return_value # the manager's procedures attribute holds created procedures and is # used for retrieval instance.procedures = procedures service = ScriptExecutionService() returned = service._get_subarray_id( process_pid ) # pylint: disable=protected-access assert returned == expected[0].script_args["init"].kwargs["subarray_id"]
def test_procedure_start_executes_user_script_in_child_process(script_with_queue_path): """ Verify that user script executes in a separate (child) process when run() is called """ procedure = Procedure(script_uri=script_with_queue_path) queue = multiprocessing.Queue() procedure.script_args["run"].args = [queue, procedure] procedure.start() procedure.join() assert not queue.empty() assert queue.get() is not None
def test_ses_get_subarray_id_fails_on_missing_subarray_id(): """ Verify that an exception is raised when subarray id is missing for requested PID """ procedure = Procedure("test://a") procedures = {1: procedure} with mock.patch( "oet.procedure.application.application.domain.ProcessManager" ) as mock_pm: # get the mock ProcessManager instance instance = mock_pm.return_value # the manager's procedures attribute holds created procedures and is # used for retrieval instance.procedures = procedures service = ScriptExecutionService() with pytest.raises(ValueError): service._get_subarray_id(1) # pylint: disable=protected-access
def test_ses_create_summary_returns_expected_object(): """ Verify that the private method _create_summary converts from Procedures to ProcedureSummary correctly """ procedure = Procedure("test://test.py", 1, 2, 3, procedure_id=123, kw1=4, kw2=5) procedures = {123: procedure} expected = ProcedureSummary( id=123, script_uri=procedure.script_uri, script_args=procedure.script_args, history=procedure.history, state=procedure.state, ) with mock.patch( "oet.procedure.application.application.domain.ProcessManager" ) as mock_pm: instance = mock_pm.return_value instance.procedures = procedures service = ScriptExecutionService() summary = service._create_summary(123) # pylint: disable=protected-access assert summary == expected
def test_ses_start_calls_process_manager_function_and_returns_summary(): """ Verify that ScriptExecutionService.start() calls the appropriate domain object methods for starting process execution and returns the expected summary object """ script_uri = "test://test.py" cmd = StartProcessCommand(process_uid=123, run_args=ProcedureInput()) procedure = Procedure(script_uri, procedure_id=123) procedures = {123: procedure} expected = ProcedureSummary( id=123, script_uri=procedure.script_uri, script_args=procedure.script_args, history=procedure.history, state=procedure.state, ) with mock.patch( "oet.procedure.application.application.domain.ProcessManager" ) as mock_pm: # get the mock ProcessManager instance instance = mock_pm.return_value # the manager's procedures attribute holds created procedures and is # used for retrieval instance.procedures = procedures service = ScriptExecutionService() returned = service.start(cmd) # service should call run() and return the summary for the executed # procedure instance.run.assert_called_once_with(123, run_args=ProcedureInput()) assert returned == expected # we don't validate or modify procedure state, so this should still be # READY rather than RUNNING assert returned.state == ProcedureState.CREATED
def procedure(script_path): """ Pytest fixture to return a prepared Procedure """ return Procedure(script_path, 1, 2, 3, kw1="a", kw2="b")
def test_ses_stop_calls_process_manager_function(abort_script): """ Verify that ScriptExecutionService.stop() calls the appropriate ProcessManager methods to stop process execution, then prepares and starts a new Process running the abort script. """ # Test script/procedures will target sub-array 2 subarray_id = 4 # PID of running script running_pid = 50 # PID of new abort Process will be 123 abort_pid = 123 # Create Procedure representing the script to be stopped procedure_to_stop = Procedure( "test://a", procedure_id=running_pid, subarray_id=subarray_id ) # Create second Procedure to represent the Process running the # post-termination abort script abort_procedure = Procedure( abort_script, procedure_id=abort_pid, subarray_id=subarray_id ) # Prepare a dict of PIDs to Procedures that we can use to mock the internal # data structure held by ProcessManager. This dict is read by the SES when # when summarising the prepared and running processes. process_manager_procedures = {running_pid: procedure_to_stop} # When SES.stop() is called, the SES should stop the current process, # prepare a process for the abort script, then set the abort process # running.. cmd_stop = StopProcessCommand(process_uid=running_pid, run_abort=True) cmd_create = PrepareProcessCommand( script_uri=abort_script, init_args=abort_procedure.script_args["init"] ) cmd_run = StartProcessCommand( process_uid=abort_pid, run_args=abort_procedure.script_args["run"] ) # .. before returning a summary of the running abort Process expected = [ ProcedureSummary( id=abort_pid, script_uri=abort_procedure.script_uri, script_args=abort_procedure.script_args, history=abort_procedure.history, state=abort_procedure.state, ) ] with mock.patch( "oet.procedure.application.application.domain.ProcessManager" ) as mock_pm: # get the mock ProcessManager instance, preparing it for SES access instance = mock_pm.return_value instance.procedures = process_manager_procedures def create_abort(*args, **kwargs): # The real .create() function would add the abort procedure to its # internal data structure when called process_manager_procedures[abort_pid] = abort_procedure return abort_pid instance.create.side_effect = create_abort service = ScriptExecutionService(abort_script_uri=abort_script) returned = service.stop(cmd_stop) # service should call stop -> create -> run, then return list containing # summary instance.stop.assert_called_once_with(cmd_stop.process_uid) instance.create.assert_called_once_with( cmd_create.script_uri, init_args=cmd_create.init_args ) instance.run.assert_called_once_with( cmd_run.process_uid, run_args=cmd_run.run_args ) assert returned == expected