示例#1
0
def test_scan_id_persists_between_executions(
    script_that_increments_and_returns_scan_id,
):
    """
    The scan ID should be shared and persisted between process executions.
    """
    manager = ProcessManager()
    queue = multiprocessing.Queue()
    run_args = ProcedureInput(queue)

    pid = manager.create(
        script_uri=script_that_increments_and_returns_scan_id,
        init_args=ProcedureInput(),
    )
    manager.run(pid, run_args=run_args)
    wait_for_process_to_complete(manager)
    scan_id = queue.get(timeout=1)

    pid = manager.create(
        script_uri=script_that_increments_and_returns_scan_id,
        init_args=ProcedureInput(),
    )
    manager.run(pid, run_args=run_args)
    wait_for_process_to_complete(manager)
    next_scan_id = queue.get(timeout=1)

    assert next_scan_id == scan_id + 1
示例#2
0
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
示例#3
0
def test_process_manager_updates_state_of_completed_procedures(manager, script_path):
    """
    Verify that ProcessManager updates procedure state to COMPLETED when finished
    successfully
    """
    pid = manager.create(script_path, init_args=ProcedureInput())
    manager.run(pid, run_args=ProcedureInput())
    wait_for_process_to_complete(manager)
    assert manager.procedures[pid].state == ProcedureState.COMPLETED
示例#4
0
def test_process_manager_sets_running_to_none_on_script_failure(manager, fail_script):
    """
    Verify that ProcessManager sets running procedure attribute to None
    when script execution fails
    """
    pid = manager.create(fail_script, init_args=ProcedureInput())
    manager.run(pid, run_args=ProcedureInput())
    wait_for_process_to_complete(manager)
    assert manager.running is None
示例#5
0
def test_process_manager_updates_procedure_state_on_stop(manager, abort_script):
    """
    Verify that ProcessManager removes an stopped procedure from
    the procedures list
    """
    pid = manager.create(abort_script, init_args=ProcedureInput())
    manager.run(pid, run_args=ProcedureInput())
    manager.stop(pid)
    wait_for_process_to_complete(manager)
    assert manager.procedures[pid].state == ProcedureState.STOPPED
示例#6
0
def test_procedure_input_eq_works_as_expected():
    """
    Verify ProcedureInput equality
    """
    pi1 = ProcedureInput(1, 2, 3, a=1, b=2)
    pi2 = ProcedureInput(1, 2, 3, a=1, b=2)
    pi3 = ProcedureInput(4, a=1)
    assert pi1 == pi2
    assert pi1 != pi3
    assert pi1 != object()
示例#7
0
def test_process_manager_sets_running_to_none_on_stop(manager, abort_script):
    """
    Verify that ProcessManager sets running procedure attribute to None
    when script is stopped
    """
    pid = manager.create(abort_script, init_args=ProcedureInput())
    manager.run(pid, run_args=ProcedureInput())
    manager.stop(pid)
    wait_for_process_to_complete(manager)
    assert manager.running is None
示例#8
0
def test_process_manager_run_changes_state_of_procedure_to_running(
    manager, script_path, process_cleanup
):
    """
    Verify that procedure state changes when ProcessManager starts
    procedure execution
    """
    pid = manager.create(script_path, init_args=ProcedureInput())
    assert manager.procedures[pid].state == ProcedureState.CREATED
    manager.run(pid, run_args=ProcedureInput())
    assert manager.procedures[pid].state == ProcedureState.RUNNING
示例#9
0
def test_process_manager_run_fails_on_process_that_is_already_running(
    manager, script_path, process_cleanup
):
    """
    Verify that an exception is raised when requesting run() for a procedure
    that is already running
    """
    pid = manager.create(script_path, init_args=ProcedureInput())
    manager.run(pid, run_args=ProcedureInput())
    with pytest.raises(ValueError):
        manager.run(pid, run_args=ProcedureInput())
示例#10
0
def test_process_manager_stop_terminates_the_process(manager, abort_script):
    """
    Verify that ProcessManager stops a script execution
    """
    pid = manager.create(abort_script, init_args=ProcedureInput())
    created = manager.procedures[pid]
    queue = multiprocessing.Queue()
    manager.run(pid, run_args=ProcedureInput(queue, created))
    manager.stop(pid)
    wait_for_process_to_complete(manager, timeout=3)
    assert queue.empty()
示例#11
0
def test_process_manager_sets_running_to_none_when_process_completes(
    manager, script_path
):
    """
    Verify that ProcessManager sets running procedure attribute to None
    when process completes
    """
    pid = manager.create(script_path, init_args=ProcedureInput())
    manager.run(pid, run_args=ProcedureInput())
    wait_for_process_to_complete(manager)
    assert manager.running is None
示例#12
0
def test_process_manager_updates_procedure_state_on_script_failure(
    manager, fail_script
):
    """
    Verify that ProcessManager removes a failed procedure from
    the procedures list
    """
    pid = manager.create(fail_script, init_args=ProcedureInput())
    manager.run(pid, run_args=ProcedureInput())
    wait_for_process_to_complete(manager)
    assert manager.procedures[pid].state == ProcedureState.FAILED
示例#13
0
def test_calling_process_manager_run_sets_run_args_on_procedure(
    manager, script_path, process_cleanup
):
    """
    Verify that the arguments to ProcessManager run() are captured and stored on the
    procedure instance
    """
    pid = manager.create(script_path, init_args=ProcedureInput())
    expected = ProcedureInput(5, 6, 7, kw3="c", kw4="d")
    created = manager.procedures[pid]
    manager.run(pid, run_args=expected)
    assert created.script_args["run"] == expected
示例#14
0
def test_process_manager_create_adds_new_procedure(manager, script_path):
    """
    Verify that ProcessManager keeps references to the processes it creates
    """
    len_before = len(manager.procedures)
    manager.create(script_path, init_args=ProcedureInput())
    assert len(manager.procedures) == len_before + 1
示例#15
0
def test_process_manager_create_sets_pid_of_new_procedure(manager, script_path):
    """
    Verify that procedures are assigned IDs on process creation
    """
    pid = manager.create(script_path, init_args=ProcedureInput())
    created = manager.procedures[pid]
    assert created.id == pid
示例#16
0
def test_process_manager_run_fails_on_invalid_pid(manager):
    """
    Verify that an exception is raised when run() is requested for an invalid
    PID
    """
    with pytest.raises(ValueError):
        manager.run(321, run_args=ProcedureInput())
示例#17
0
def test_procedure_input_accepts_expected_constructor_values():
    """
    Verify that ProcedureInput arguments are slurped into positional and
    keyword/value attributes.
    """
    procedure_input = ProcedureInput(1, 2, 3, a=1, b=2)
    assert procedure_input.args == (1, 2, 3)
    assert procedure_input.kwargs == dict(a=1, b=2)
示例#18
0
def test_process_manager_updates_history_of_completed_procedures(manager, script_path):
    """
    Verify that ProcessManager updates procedure state to COMPLETED when finished
    successfully
    """
    pid = manager.create(script_path, init_args=ProcedureInput())
    manager.run(pid, run_args=ProcedureInput())
    wait_for_process_to_complete(manager)
    procedure = manager.procedures[pid]

    assert ProcedureState.CREATED in procedure.history.process_states
    assert isinstance(procedure.history.process_states[ProcedureState.CREATED], float)
    assert ProcedureState.RUNNING in procedure.history.process_states
    assert isinstance(procedure.history.process_states[ProcedureState.RUNNING], float)
    assert ProcedureState.COMPLETED in procedure.history.process_states
    assert isinstance(procedure.history.process_states[ProcedureState.COMPLETED], float)
    assert procedure.history.stacktrace is None
示例#19
0
def test_process_manager_updates_procedure_history_on_script_failure(
    manager, fail_script
):
    """
    Verify that ProcessManager updates FAILED to procedure history when script fails
    """
    pid = manager.create(fail_script, init_args=ProcedureInput())
    manager.run(pid, run_args=ProcedureInput())
    wait_for_process_to_complete(manager)
    procedure = manager.procedures[pid]

    assert ProcedureState.CREATED in procedure.history.process_states
    assert isinstance(procedure.history.process_states[ProcedureState.CREATED], float)
    assert ProcedureState.RUNNING in procedure.history.process_states
    assert isinstance(procedure.history.process_states[ProcedureState.RUNNING], float)
    assert ProcedureState.FAILED in procedure.history.process_states
    assert isinstance(procedure.history.process_states[ProcedureState.FAILED], float)
    assert procedure.history.stacktrace is not None
示例#20
0
def test_runtime_arguments_are_passed_to_user_script(procedure):
    """
    Verify that arguments passed from procedure are accessible in the user script
    """
    run_args = ProcedureInput(5, 6, 7, kw3="c", kw4="d")
    procedure.script_args["run"] = run_args
    procedure.user_module = MagicMock()
    procedure.run()
    procedure.user_module.main.assert_called_with(5, 6, 7, kw3="c", kw4="d")
示例#21
0
def test_process_manager_create_captures_initialisation_arguments(manager, script_path):
    """
    Verify that ProcessManager passes through initialisation arguments to
    the procedures it creates
    """
    expected = ProcedureInput(1, 2, 3, a=4, b=5)
    pid = manager.create(script_path, init_args=expected)
    created = manager.procedures[pid]
    assert created.script_args["init"] == expected
示例#22
0
def test_process_manager_run_executes_procedure_start(manager, process_cleanup):
    """
    Verify that a call to ProcessManager run() executes Procedure.start()
    instead of Procedure.run(). This confirms that Procedure will execute in a
    child process.
    """
    procedure = MagicMock()
    manager.procedures[1] = procedure
    manager.run(1, run_args=ProcedureInput())
    procedure.start.assert_called_once()
示例#23
0
def create_empty_procedure_summary(
    procedure_id: int, script_uri: str, history: ProcedureHistory
):
    """
    Utility function to create a null procedure summary. The returned
    procedure defines zero script arguments.

    :param procedure_id: procedure ID
    :param script_uri: path to script
    :param history: Procedure history
    :return: corresponding ProcedureSummary object
    """
    return ProcedureSummary(
        id=procedure_id,
        script_uri=script_uri,
        script_args={"init": ProcedureInput(), "run": ProcedureInput()},
        history=history,
        state=ProcedureState.CREATED,
    )
示例#24
0
def test_process_manager_stop_fails_on_process_that_is_not_running(
    manager, script_path
):
    """
    Verify that an exception is raised when requesting stop() for a procedure
    that is not running
    """
    pid = manager.create(script_path, init_args=ProcedureInput())
    with pytest.raises(ValueError):
        manager.stop(pid)
示例#25
0
def test_process_manager_run_sets_running_procedure(manager, tmpdir, process_cleanup):
    """
    Verify that ProcessManager sets the running procedure attribute
    appropriately when run() is called
    """
    script_path = tmpdir.join("sleep.py")
    script_path.write(
        """
def main(shutdown_event, *args, **kwargs):
    while not shutdown_event.is_set():
        continue
"""
    )
    script_uri = f"file://{str(script_path)}"

    shutdown_event = multiprocessing.Event()
    pid = manager.create(script_uri, init_args=ProcedureInput())
    manager.run(pid, run_args=ProcedureInput(shutdown_event))
    assert manager.running == manager.procedures[pid]
    shutdown_event.set()
示例#26
0
def test_process_manager_create_removes_oldest_procedure_on_max_procedures(
    manager, script_path
):
    """
    Verify that ProcessManager removes the oldest procedure when the maximum number of
    saved procedures is reached
    """
    manager.procedures.clear()
    max_procedures = PROCEDURE_QUEUE_MAX_LENGTH
    for _ in range(len(manager.procedures), max_procedures):
        manager.create(script_path, init_args=ProcedureInput())

    assert len(manager.procedures) == max_procedures
    assert 1 in manager.procedures

    # adding procedure should not increase the number of procedures
    # and should remove the oldest procedure (with ID 1)
    manager.create(script_path, init_args=ProcedureInput())

    assert len(manager.procedures) == max_procedures
    assert 1 not in manager.procedures
示例#27
0
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
示例#28
0
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
示例#29
0
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"]
示例#30
0
def test_procedure_init_stores_initial_arguments(procedure):
    """
    Verify that the procedure constructor arguments are captured and persisted
    on the procedure instance.
    """
    assert procedure.script_args["init"] == ProcedureInput(1, 2, 3, kw1="a", kw2="b")