def test_ses_calls_process_manager_as_expected(self,
                                                   ses: ScriptExecutionService,
                                                   main_hang_script):
        """
        Verify ScriptExecutionService interactions with ProcessManager for
        standard script execution.
        """
        mgr = mock.Mock(wraps=ses._process_manager)
        ses._process_manager = mgr

        main_running = multiprocessing.Barrier(2)
        prepare_cmd = PrepareProcessCommand(
            script=main_hang_script, init_args=ProcedureInput(main_running))
        summary = ses.prepare(prepare_cmd)
        pid = summary.id
        mgr.create.assert_called_once_with(prepare_cmd.script,
                                           init_args=prepare_cmd.init_args)

        ses._wait_for_state(pid, ProcedureState.READY)
        run_cmd = StartProcessCommand(process_uid=pid,
                                      fn_name="main",
                                      run_args=ProcedureInput())
        _ = ses.start(run_cmd)
        mgr.run.assert_called_once_with(run_cmd.process_uid,
                                        call=run_cmd.fn_name,
                                        run_args=run_cmd.run_args)

        ses._wait_for_state(pid, ProcedureState.RUNNING)
        main_running.wait(0.1)
        stop_cmd = StopProcessCommand(process_uid=pid, run_abort=False)
        _ = ses.stop(stop_cmd)
        mgr.stop.assert_called_once_with(stop_cmd.process_uid)

        # assert no other calls to ProcessManager
        assert len(mgr.method_calls) == 3
Exemple #2
0
    def test_running_to_none_when_process_completes(self, manager,
                                                    barrier_script):
        """
        Verify that ProcessManager sets running procedure attribute to None
        when process completes.
        """
        init_running = multiprocessing.Barrier(2)
        main_running = multiprocessing.Barrier(2)
        resume = multiprocessing.Barrier(2)
        init_args = ProcedureInput(init_running, main_running, resume)

        pid = manager.create(barrier_script, init_args=init_args)
        init_running.wait(0.1)
        resume.wait(0.1)
        resume.reset()  # reset to pause main method call
        wait_for_state(manager, pid, ProcedureState.READY)

        # now set main running
        manager.run(pid, call="main", run_args=ProcedureInput())
        main_running.wait(0.1)
        wait_for_state(manager, pid, ProcedureState.RUNNING)
        assert manager.running is not None

        resume.wait(0.1)
        wait_for_state(manager, pid, ProcedureState.COMPLETE)
        assert manager.running is None
    def test_run_args_are_captured_in_history(self, ses):
        """
        Verify that arguments to start() are captured and stored
        """
        timestamp = 12345
        pid = 456
        fn_name = "foo"
        run_args = ProcedureInput(5, 6, 7, kw3="c", kw4="d")

        # must create process for history entries to be present
        script = FileSystemScript("file://test.py")
        cmd = PrepareProcessCommand(script=script, init_args=ProcedureInput())
        with patch.object(ProcessManager, "create", return_value=pid):
            with patch.object(ScriptExecutionService, "_summarise"):
                _ = ses.prepare(cmd)

        # now we can test when method invocation args are recorded
        cmd = StartProcessCommand(pid, fn_name=fn_name, run_args=run_args)
        expected = ArgCapture(fn=fn_name, fn_args=run_args, time=timestamp)

        with patch("time.time", MagicMock(return_value=timestamp)):
            with patch.object(ScriptExecutionService, "_summarise"):
                with patch.object(ProcessManager, "run"):
                    _ = ses.start(cmd)

        assert len(ses.script_args[pid]) == 2
        assert ses.script_args[pid][1] == expected
Exemple #4
0
    def test_stop_during_main_sets_lifecycle_state_to_stopped(
            self, manager, main_hang_script):
        """
        Verify that procedure state changes to STOPPED
        when terminate() is called
        """
        helper = PubSubHelper()

        main_running = multiprocessing.Barrier(2)
        init_args = ProcedureInput(main_running)

        pid = manager.create(main_hang_script, init_args=init_args)
        helper.wait_for_lifecycle(ProcedureState.READY)
        manager.run(pid, call="main", run_args=ProcedureInput())
        main_running.wait(0.5)

        helper.wait_for_lifecycle(ProcedureState.RUNNING)
        manager.stop(pid)
        helper.wait_for_lifecycle(ProcedureState.STOPPED)

        expected = [
            ProcedureState.CREATING,  # ScriptWorker initialising
            ProcedureState.IDLE,  # ScriptWorker ready
            ProcedureState.LOADING,  # load user module
            ProcedureState.IDLE,  # user module loaded
            ProcedureState.RUNNING,  # init running
            ProcedureState.READY,  # init complete
            ProcedureState.RUNNING,  # main running
            ProcedureState.STOPPED,  # main stopped
        ]
        self.assert_states(helper, pid, expected)
Exemple #5
0
    def test_shared_environment_waits_for_creation_to_complete(
            self, mock_subprocess_fn, mock_clone_fn, git_script,
            git_script_branch, manager):
        """
        Verify calls to subprocess to install environment are only run if the environment does not yet exist.
        The first two scripts run in the same environment and the third one creates a new environment.
        """
        environment = Environment(
            "123",
            multiprocessing.Event(),
            multiprocessing.Event(),
            "/",
            "/python/site_packages",
        )
        environment2 = Environment(
            "456",
            multiprocessing.Event(),
            multiprocessing.Event(),
            "/",
            "/python/site_packages",
        )
        manager.em.create_env = MagicMock()
        manager.em.create_env.side_effect = [
            environment, environment, environment2
        ]

        # Return path to git file from clone call in env creation and module load
        mock_clone_fn.return_value = "/"
        calls = multiprocessing.Value("i", 0)

        def called(*args, **kwargs):
            calls.value += 1

        mock_subprocess_fn.side_effect = called

        pid1 = manager.create(git_script, init_args=ProcedureInput())
        env1 = manager.environments[pid1]
        pid2 = manager.create(git_script, init_args=ProcedureInput())
        env2 = manager.environments[pid2]

        assert env1 == env2

        wait_for_state(manager, pid1, ProcedureState.READY)
        wait_for_state(manager, pid2, ProcedureState.READY)

        # Subprocess should only be called twice because second script should
        # just wait for first one to create the environment
        assert calls.value == 2

        pid3 = manager.create(git_script_branch, init_args=ProcedureInput())
        env3 = manager.environments[pid3]

        wait_for_state(manager, pid3, ProcedureState.READY)

        assert env1 != env3
        assert env3.created.is_set()

        # Call count should go up because the new script should run in a new environment
        assert calls.value == 4
Exemple #6
0
 def test_procedure_input_equality(self):
     """
     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()
Exemple #7
0
 def test_stop_fails_on_process_that_is_not_running(self, manager, script):
     """
     Verify that an exception is raised when requesting stop() for a procedure
     that is not running
     """
     pid = manager.create(script, init_args=ProcedureInput())
     wait_for_state(manager, pid, ProcedureState.READY)
     manager.run(pid, call="main", run_args=ProcedureInput())
     wait_for_state(manager, pid, ProcedureState.COMPLETE)
     with pytest.raises(ValueError):
         manager.stop(pid)
Exemple #8
0
    def test_cleanup_on_failed(self, manager, fail_script):
        pid = manager.create(fail_script, init_args=ProcedureInput())
        wait_for_state(manager, pid, ProcedureState.READY)
        manager.run(pid, call="main", run_args=ProcedureInput("foo"))
        wait_for_state(manager, pid, ProcedureState.FAILED)

        # TODO how can we synchronise with the cleanup function running in another thread?
        time.sleep(0.1)
        assert pid not in manager.states
        assert pid not in manager.script_queues
        assert pid not in manager.procedures
    def test_sad_path_events(self, ses, fail_script):
        """
        Verify that OET events are published at the appropriate times for a
        script that raises an exception.
        """
        helper = PubSubHelper()

        prepare_cmd = PrepareProcessCommand(script=fail_script,
                                            init_args=ProcedureInput())
        summary = ses.prepare(prepare_cmd)

        helper.wait_for_message_on_topic(topics.procedure.lifecycle.created,
                                         timeout=3.0)
        assert len(helper.messages_on_topic(
            topics.procedure.lifecycle.created)) == 1
        assert len(helper.messages_on_topic(
            topics.procedure.lifecycle.started)) == 0
        assert len(
            helper.messages_on_topic(topics.procedure.lifecycle.complete)) == 0
        assert len(helper.messages_on_topic(
            topics.procedure.lifecycle.failed)) == 0
        assert len(helper.messages_on_topic(
            topics.procedure.lifecycle.stopped)) == 0

        run_cmd = StartProcessCommand(process_uid=summary.id,
                                      fn_name="main",
                                      run_args=ProcedureInput(msg="foo"))
        _ = ses.start(run_cmd)

        helper.wait_for_message_on_topic(topics.procedure.lifecycle.stacktrace,
                                         timeout=3.0)
        assert len(helper.messages_on_topic(
            topics.procedure.lifecycle.created)) == 1
        assert len(helper.messages_on_topic(
            topics.procedure.lifecycle.started)) == 1
        assert len(
            helper.messages_on_topic(topics.procedure.lifecycle.complete)) == 0
        assert len(helper.messages_on_topic(
            topics.procedure.lifecycle.failed)) == 1
        assert len(helper.messages_on_topic(
            topics.procedure.lifecycle.stopped)) == 0

        filtered = [
            t for t in helper.topic_list
            if t != topics.procedure.lifecycle.statechange
        ]
        assert filtered == [
            topics.procedure.lifecycle.created,
            topics.procedure.lifecycle.started,
            topics.procedure.lifecycle.failed,
            topics.procedure.lifecycle.stacktrace,
        ]
Exemple #10
0
    def test_happy_path_script_execution_lifecycle_states(
            self, manager: ProcessManager, barrier_script):
        """
        Verify that a new ScriptWorker sends the appropriate lifecycle states.

        The mptools ProcWorker tests already verify that events are set at the
        appropriate times. This test is to confirm that lifecycle EventMessages
        are sent at the appropriate times too.
        """
        helper = PubSubHelper()

        init_running = multiprocessing.Barrier(2)
        main_running = multiprocessing.Barrier(2)
        resume = multiprocessing.Barrier(2)
        init_args = ProcedureInput(init_running, main_running, resume)

        pid = manager.create(barrier_script, init_args=init_args)
        init_running.wait(0.1)
        wait_for_state(manager, pid, ProcedureState.RUNNING)
        expected = [
            ProcedureState.CREATING,  # ScriptWorker initialising
            ProcedureState.IDLE,  # ScriptWorker ready
            ProcedureState.LOADING,  # load user module
            ProcedureState.IDLE,  # user module loaded
            ProcedureState.RUNNING,  # init present and called
        ]
        self.assert_states(helper, pid, expected)

        # let init complete, then check for completion
        resume.wait(0.1)
        resume.reset()  # reset to pause main method call
        wait_for_state(manager, pid, ProcedureState.READY)
        expected.append(ProcedureState.READY)  # init complete
        self.assert_states(helper, pid, expected)

        # now set main running
        manager.run(pid, call="main", run_args=ProcedureInput())
        expected.append(ProcedureState.RUNNING)  # main running
        main_running.wait(0.1)
        wait_for_state(manager, pid, ProcedureState.RUNNING)
        self.assert_states(helper, pid, expected)

        # wait for ScriptWorker process to complete
        resume.wait(0.1)
        resume.reset()  # reset to pause main method call
        wait_for_state(manager, pid, ProcedureState.COMPLETE)
        expected.extend([
            ProcedureState.READY,  # main complete
            ProcedureState.COMPLETE,  # script complete
        ])
        self.assert_states(helper, pid, expected)
    def test_scalar_summarise(self, ses, script):  # pylint: disable=protected-access
        """
        Verify that the private _summarise method compiles the execution
        history correctly.

        Note that this test exercises the SES and ProcessManager, hence this
        also functions as an integration test between the two.
        """
        t = 12345
        init_args = ProcedureInput(1, 2, a="b", c="d")
        run_args = ProcedureInput(3, 4, e="f", g="h")
        history = ProcedureHistory(
            [
                (ProcedureState.CREATING, t),
                (ProcedureState.IDLE, t),
                (ProcedureState.LOADING, t),
                (ProcedureState.IDLE, t),
                (ProcedureState.READY, t),
                (ProcedureState.RUNNING, t),
                (ProcedureState.READY, t),
                (ProcedureState.COMPLETE, t),
            ],
            stacktrace=None,
        )

        prepare_cmd = PrepareProcessCommand(script, init_args=init_args)
        with patch("time.time", MagicMock(return_value=t)):
            summary = ses.prepare(prepare_cmd)
            pid = summary.id
            ses._wait_for_state(pid, ProcedureState.READY)

            run_cmd = StartProcessCommand(process_uid=pid,
                                          fn_name="main",
                                          run_args=run_args)
            ses.start(run_cmd)
            ses._wait_for_state(pid, ProcedureState.COMPLETE)

        expected = ProcedureSummary(
            id=pid,
            script=script,
            script_args=[
                ArgCapture(fn="init", fn_args=init_args, time=t),
                ArgCapture(fn="main", fn_args=run_args, time=t),
            ],
            history=history,
            state=ProcedureState.COMPLETE,
        )

        summary = ses._summarise(pid)
        assert summary == expected
Exemple #12
0
    def test_run_fails_for_loading_process(self, manager, script):
        """
        Verify that an exception is raised if requesting run() for a procedure
        that has not been loaded or is still loading
        """
        with patch(
                "ska_oso_oet.procedure.domain.ModuleFactory.get_module") as fn:
            fn.side_effect = lambda _: time.sleep(3)
            pid = manager.create(script, init_args=ProcedureInput())
            wait_for_state(manager, pid, ProcedureState.LOADING)

            assert manager.states[pid] == ProcedureState.LOADING
            with pytest.raises(ValueError):
                manager.run(pid, call="main", run_args=ProcedureInput())
Exemple #13
0
    def test_events_emitted_from_scripts_are_republished(
            self, manager, pubsub_script):
        """
        Verify that OET events are published at the appropriate times for a
        sad-path script.
        """
        helper = PubSubHelper()

        pid = manager.create(pubsub_script, init_args=ProcedureInput())
        wait_for_state(manager, pid, ProcedureState.READY)

        manager.run(pid, call="main", run_args=ProcedureInput(msg="foo"))
        helper.wait_for_message_on_topic(topics.user.script.announce)
        user_msgs = helper.messages_on_topic(topics.user.script.announce)
        assert len(user_msgs) == 1
Exemple #14
0
    def test_stop_during_init_sets_lifecycle_state_to_stopped(
            self, manager, init_hang_script):
        """
        Verify that procedure terminate changes to STOPPED
        when terminate() is called
        """
        helper = PubSubHelper()

        init_running = multiprocessing.Barrier(2)
        init_args = ProcedureInput(init_running)

        pid = manager.create(init_hang_script, init_args=init_args)

        init_running.wait(0.1)
        wait_for_empty_message_queue(manager)
        manager.stop(pid)

        expected = [
            ProcedureState.CREATING,  # ScriptWorker initialising
            ProcedureState.IDLE,  # ScriptWorker ready
            ProcedureState.LOADING,  # load user module
            ProcedureState.IDLE,  # user module loaded
            ProcedureState.RUNNING,  # init running
            ProcedureState.STOPPED,  # init stopped
        ]
        helper.wait_for_lifecycle(ProcedureState.STOPPED)
        self.assert_states(helper, pid, expected)
Exemple #15
0
    def test_environment_created_condition_is_set(self, mock_subprocess_fn,
                                                  mock_clone_fn, git_script,
                                                  manager):
        """
        Verify event is correctly set on Environment object when env is being created.
        """
        environment = Environment(
            "123",
            multiprocessing.Event(),
            multiprocessing.Event(),
            "/",
            "/python/site_packages",
        )
        manager.em.create_env = MagicMock()
        manager.em.create_env.return_value = environment
        # Return path to git file from clone call in env creation and module load
        mock_clone_fn.side_effect = ["", ""]

        pid = manager.create(git_script, init_args=ProcedureInput())
        env = manager.environments[pid]
        assert not env.created.is_set()
        assert env.env_id == environment.env_id

        wait_for_state(manager, pid, ProcedureState.READY)
        assert env.created.is_set()
Exemple #16
0
def test_scan_id_persists_between_executions(
    manager,
    script_that_increments_and_returns_scan_id,
):
    """
    The scan ID should be shared and persisted between process executions.
    """
    queue = multiprocessing.Queue()
    init_args = ProcedureInput(queue)

    def run_script():
        pid = manager.create(
            script=script_that_increments_and_returns_scan_id,
            init_args=init_args,
        )
        wait_for_state(manager, pid, ProcedureState.READY)
        manager.run(pid, call="main", run_args=ProcedureInput())
        wait_for_state(manager, pid, ProcedureState.COMPLETE)

    run_script()
    scan_id = queue.get(timeout=1)

    run_script()
    next_scan_id = queue.get(timeout=1)

    assert next_scan_id == scan_id + 1
Exemple #17
0
 def test_run_fails_on_invalid_pid(self, manager):
     """
     Verify that an exception is raised when run() is requested for an invalid
     PID
     """
     with pytest.raises(ValueError):
         manager.run(321, call="foo", run_args=ProcedureInput())
    def test_ses_get_subarray_id_for_requested_pid(self, ses):
        """
        Verify that the private method _get_subarray_id returns
        subarray id correctly
        """
        subarray_id = 123
        process_pid = 456

        init_args = ArgCapture(fn="init",
                               fn_args=ProcedureInput(subarray_id=subarray_id),
                               time=1)
        process_summary = ProcedureSummary(
            id=process_pid,
            script=FileSystemScript("file://a"),
            script_args=[init_args],
            history=mock.MagicMock(),
            state=ProcedureState.IDLE,
        )
        expected = [process_summary]

        with patch.object(ScriptExecutionService,
                          "_summarise",
                          return_value=process_summary) as method:
            returned = ses._get_subarray_id(process_pid)

            assert method.called_with(process_pid)
            assert returned == expected[0].script_args[0].fn_args.kwargs[
                "subarray_id"]
    def test_exceeding_history_limit_removes_oldest_deletable_state(
            self, ses, script):
        """
        Verify that SES removes the oldest deletable state and history when
        the record limit is reached.
        """
        prepare_cmd = PrepareProcessCommand(script=script,
                                            init_args=ProcedureInput())

        # reduce max history to make test quicker
        limit = 3
        with patch(
                "ska_oso_oet.procedure.application.application.HISTORY_MAX_LENGTH",
                new=limit,
        ):
            for _ in range(limit):
                summary = ses.prepare(prepare_cmd)
                pid = summary.id
                ses._wait_for_state(pid, ProcedureState.READY)
                run_cmd = StartProcessCommand(process_uid=pid,
                                              fn_name="main",
                                              run_args=ProcedureInput())
                summary = ses.start(run_cmd)
                pid = summary.id

            ses._wait_for_state(pid, ProcedureState.COMPLETE)
            assert len(ses.history) == limit
            assert len(ses.script_args) == limit
            assert len(ses.states) == limit
            assert len(ses.scripts) == limit

            oldest_pid = next(iter(ses.states.keys()))
            assert oldest_pid in ses.history
            assert oldest_pid in ses.script_args
            assert oldest_pid in ses.states
            assert oldest_pid in ses.scripts

            _ = ses.prepare(prepare_cmd)

        # adding procedure should not increase the number of procedures
        # and should remove the oldest procedure
        assert len(ses.history) == limit
        assert oldest_pid not in ses.history
        assert oldest_pid not in ses.script_args
        assert oldest_pid not in ses.states
        assert oldest_pid not in ses.scripts
Exemple #20
0
 def test_procedure_input_accepts_expected_constructor_values(self):
     """
     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)
Exemple #21
0
 def run_script():
     pid = manager.create(
         script=script_that_increments_and_returns_scan_id,
         init_args=init_args,
     )
     wait_for_state(manager, pid, ProcedureState.READY)
     manager.run(pid, call="main", run_args=ProcedureInput())
     wait_for_state(manager, pid, ProcedureState.COMPLETE)
    def test_two_phase_abort_calls_process_manager_as_expected(
            self, sleep_script, script):
        """
        Verify ScriptExecutionService interactions with ProcessManager when
        terminating a script with a request to run the follow-on abort script
        """
        pubsub.pub.unsubAll()
        # fixture accepts any positional args and kwargs, which is useful as the
        # subarray ID will be passed to the abort script
        ses = ScriptExecutionService(abort_script=script)
        subarray_id = 3
        try:
            prepare_cmd = PrepareProcessCommand(
                script=sleep_script,
                init_args=ProcedureInput(subarray_id=subarray_id))
            summary = ses.prepare(prepare_cmd)
            pid = summary.id

            ses._wait_for_state(pid, ProcedureState.READY)
            run_cmd = StartProcessCommand(
                process_uid=pid,
                fn_name="main",
                run_args=ProcedureInput(
                    2),  # max 2 seconds before process completes naturally
            )
            _ = ses.start(run_cmd)
            ses._wait_for_state(pid, ProcedureState.RUNNING)

            # wrap the manager here so that we only capture calls related to stop
            mgr = mock.Mock(wraps=ses._process_manager)
            ses._process_manager = mgr

            stop_cmd = StopProcessCommand(process_uid=pid, run_abort=True)
            _ = ses.stop(stop_cmd)

            assert len(mgr.method_calls) == 3
            mgr.stop.assert_called_once_with(1)
            mgr.create.assert_called_once_with(
                script, init_args=ProcedureInput(subarray_id=subarray_id))
            mgr.run.assert_called_once_with(pid + 1,
                                            call="main",
                                            run_args=ProcedureInput())

        finally:
            ses.shutdown()
Exemple #23
0
 def test_create_adds_to_procedures(self, manager, script):
     """
     Verify that ProcessManager.procedures references the processes it creates
     """
     for _ in range(3):
         len_before = len(manager.procedures)
         pid = manager.create(script, init_args=ProcedureInput())
         assert len(manager.procedures) == len_before + 1
         assert pid in manager.procedures
Exemple #24
0
    def test_stop_terminates_the_process(self, manager, abort_script):
        """
        Verify that ProcessManager stops a script execution
        """
        helper = PubSubHelper()
        with Manager() as mgr:
            q = mgr.Queue()
            is_running = multiprocessing.Barrier(2)
            pid = manager.create(abort_script,
                                 init_args=ProcedureInput(q, is_running))
            wait_for_state(manager, pid, ProcedureState.READY)
            manager.run(pid, call="main", run_args=ProcedureInput())

            is_running.wait(0.1)
            helper.wait_for_lifecycle(ProcedureState.RUNNING)
            manager.stop(pid)
            helper.wait_for_lifecycle(ProcedureState.STOPPED)
            assert manager.running is None
            assert q.empty()
    def test_failure_and_stacktrace_recorded_in_history(
            self, ses: ScriptExecutionService, fail_script):
        t = 12345
        random_exc_string = str(uuid.uuid4())
        init_args = ProcedureInput()
        run_args = ProcedureInput(random_exc_string)
        expected_states = [
            (ProcedureState.CREATING, t),  # ScriptWorker initialising
            (ProcedureState.IDLE, t),  # ScriptWorker ready
            (ProcedureState.LOADING, t),  # load user module
            (ProcedureState.IDLE, t),  # user module loaded
            # fail script has no init so no IDLE->READY expected
            (ProcedureState.READY, t),  # init complete
            (ProcedureState.RUNNING, t),  # main running
            (ProcedureState.FAILED, t),  # exception raised
        ]

        with patch("time.time", MagicMock(return_value=t)):
            prepare_cmd = PrepareProcessCommand(script=fail_script,
                                                init_args=init_args)
            summary = ses.prepare(prepare_cmd)

            pid = summary.id
            ses._wait_for_state(pid, ProcedureState.READY)

            assert summary.history.stacktrace is None

            cmd = StartProcessCommand(process_uid=pid,
                                      fn_name="main",
                                      run_args=run_args)
            _ = ses.start(cmd)
            ses._wait_for_state(pid, ProcedureState.FAILED)

            time.sleep(1.0)
            summary = ses._summarise(pid)

        assert summary.history.process_states == expected_states

        # most recent stacktrace should also have been captured and recorded in history
        assert random_exc_string in summary.history.stacktrace
Exemple #26
0
    def test_run_fails_for_running_process(self, manager, barrier_script):
        """
        Verify that an exception is raised when requesting run() for a procedure
        that is already running
        """
        init_running = multiprocessing.Barrier(2)
        main_running = multiprocessing.Barrier(2)
        resume = multiprocessing.Barrier(2)
        init_args = ProcedureInput(init_running, main_running, resume)

        pid = manager.create(barrier_script, init_args=init_args)
        init_running.wait(0.1)
        resume.wait()
        resume.reset()

        wait_for_state(manager, pid, ProcedureState.READY)
        manager.run(pid, call="main", run_args=ProcedureInput())
        main_running.wait(0.1)
        wait_for_state(manager, pid, ProcedureState.RUNNING)
        with pytest.raises(ValueError):
            manager.run(pid, call="main", run_args=ProcedureInput())
        resume.wait()
Exemple #27
0
    def test_error_in_main_lifecycles_states(self, manager: ProcessManager,
                                             fail_script):
        helper = PubSubHelper()

        pid = manager.create(fail_script, init_args=ProcedureInput())
        wait_for_state(manager, pid, ProcedureState.READY)

        manager.run(pid, call="main", run_args=ProcedureInput("foo"))

        wait_for_state(manager, pid, ProcedureState.FAILED)
        expected = [
            ProcedureState.CREATING,  # ScriptWorker initialising
            ProcedureState.IDLE,  # ScriptWorker ready
            ProcedureState.LOADING,  # load user module
            ProcedureState.IDLE,  # user module loaded
            # fail script has no init so no IDLE->READY expected
            ProcedureState.READY,  # init complete
            ProcedureState.RUNNING,  # main running
            ProcedureState.FAILED,  # exception raised
        ]
        helper.wait_for_lifecycle(ProcedureState.FAILED)
        # wait_for_state(manager, pid, ProcedureState.FAILED)
        # helper.wait_for_message_on_topic(topics.procedure.lifecycle.stacktrace)
        self.assert_states(helper, pid, expected)
Exemple #28
0
    def test_shared_environment_sys_path_is_set(self, mock_subprocess_fn,
                                                mock_clone_fn,
                                                git_sys_path_script, manager):
        """
        Verify site packages are added to sys.path correctly for scripts sharing an environment.
        """
        site_pkg = "/python/site_packages"
        env = Environment(
            "123",
            multiprocessing.Event(),
            multiprocessing.Event(),
            "/",
            site_pkg,
        )
        manager.em.create_env = MagicMock()
        manager.em.create_env.side_effect = [env, env]

        # Return path to git file from clone call in env creation and module load
        mock_clone_fn.return_value = "/"

        pid1 = manager.create(git_sys_path_script,
                              init_args=ProcedureInput(site_pkg))
        pid2 = manager.create(git_sys_path_script,
                              init_args=ProcedureInput(site_pkg))
        wait_for_state(manager, pid1, ProcedureState.READY)
        wait_for_state(manager, pid2, ProcedureState.READY)

        # Running the main function asserts the site_pkg is in sys.path
        # If assertion fails, script state goes to FAILED, else it goes
        # to COMPLETE
        helper = PubSubHelper()
        manager.run(pid1, call="main", run_args=ProcedureInput(site_pkg))
        assert helper.wait_for_lifecycle(ProcedureState.COMPLETE, msg_src=pid1)

        manager.run(pid2, call="main", run_args=ProcedureInput(site_pkg))
        assert helper.wait_for_lifecycle(ProcedureState.COMPLETE, msg_src=pid2)
Exemple #29
0
    def test_cleanup_on_stopped(self, manager, init_hang_script):
        init_running = multiprocessing.Barrier(2)
        init_args = ProcedureInput(init_running)

        pid = manager.create(init_hang_script, init_args=init_args)
        init_running.wait(0.1)
        wait_for_state(manager, pid, ProcedureState.RUNNING)
        manager.stop(pid)
        wait_for_state(manager, pid, ProcedureState.STOPPED)

        # TODO how can we synchronise with the cleanup function running in another thread?
        time.sleep(0.1)
        assert pid not in manager.states
        assert pid not in manager.script_queues
        assert pid not in manager.procedures
Exemple #30
0
    def test_run_sends_run_message(self, manager):
        """
        Verify that a call to ProcessManager.run() sends the run message to the
        ScriptWorker.
        """
        q = manager.ctx.MPQueue()
        manager.procedures[1] = MagicMock()
        manager.states[1] = ProcedureState.READY
        manager.script_queues[1] = q
        method = "foo"
        run_args = ProcedureInput("a", "b", kw1="c", kw2="d")

        manager.run(1, call=method, run_args=run_args)
        msg = q.safe_get(timeout=0.1)
        assert msg.msg_type == "RUN"
        assert msg.msg == (method, run_args)