示例#1
0
def test_focus_state():
    @step("Step that works on substate")
    def substep():
        return {"result": "substep"}

    subwf = focussteps("sub")
    wf = workflow("Workflow with sub workflow that focuses on sub state")(
        lambda: subwf(substep) >> done)

    log = []
    pstat = create_new_process_stat(wf, {"sub": {}})
    result = runwf(pstat, store(log))
    assert_complete(result)
    assert_state(result, {"sub": {"result": "substep"}})

    # Test on empty key
    subwf = focussteps("sub")
    wf = workflow("Workflow with sub workflow that focuses on sub state")(
        lambda: subwf(substep) >> done)

    log = []
    pstat = create_new_process_stat(wf, {})
    result = runwf(pstat, store(log))
    assert_complete(result)
    assert_state(result, {"sub": {"result": "substep"}})
示例#2
0
def test_exception_log_step():
    wf = workflow("Failing workflow")(lambda: init >> done)

    def failing_store(stat, step, state):
        raise Exception("Failing store error")

    with pytest.raises(Exception) as exc_info:
        pstat = create_new_process_stat(wf, {"name": "init-state"})
        runwf(pstat, failing_store)
    assert "Failing store error" in str(exc_info.value)
示例#3
0
def test_store_all_steps():
    log = []

    pstat = create_new_process_stat(sample_workflow, {})
    runwf(pstat, store(log))

    assert [
        ("Step 1", Success({"steps": [1]})),
        ("Step 2", Success({"steps": [1, 2]})),
        ("Step 3", Success({"steps": [1, 2, 3]})),
    ] == log
示例#4
0
def test_resume_suspended_workflow():
    wf = workflow("Workflow with user interaction")(
        lambda: begin >> step1 >> user_action >> step2)

    log = []

    p = ProcessStat(
        pid=1,
        workflow=wf,
        state=Suspend({
            "steps": [1],
            "name": "Jane Doe"
        }),
        log=wf.steps[1:],
        current_user="******",
    )
    result = runwf(p, logstep=store(log))

    assert_success(result)
    assert result == Success({"steps": [1, 2], "name": "Jane Doe"})
    assert [
        ("Input Name", Success({
            "steps": [1],
            "name": "Jane Doe"
        })),
        ("Step 2", Success({
            "steps": [1, 2],
            "name": "Jane Doe"
        })),
    ] == log
示例#5
0
def test_input_in_substate() -> None:
    @inputstep("Input Name", assignee=Assignee.SYSTEM)
    def input_action(state: State) -> FormGenerator:
        class SubForm(FormPage):
            a: int

        class TestForm(FormPage):
            sub: SubForm

        user_input = yield TestForm

        return user_input.dict()

    wf = workflow("Workflow with user interaction")(
        lambda: begin >> input_action >> purestep("process inputs")(Success))

    log: List[Tuple[str, Process]] = []
    pid = uuid4()
    p = ProcessStat(pid=pid,
                    workflow=wf,
                    state=Suspend({"sub": {
                        "a": 1,
                        "b": 2
                    }}),
                    log=wf.steps[1:],
                    current_user="******")
    result = runwf(p, logstep=store(log))

    assert_success(result)
    assert_state(result, {"sub": {"a": 1, "b": 2}})
示例#6
0
def test_exec_through_all_steps():
    log = []

    pstat = create_new_process_stat(sample_workflow, {})
    result = runwf(pstat, store(log))
    assert_success(result)
    assert_state(result, {"steps": [1, 2, 3]})
示例#7
0
def test_complete():
    wf = workflow("WF")(lambda: init >> done)

    log = []
    pstat = create_new_process_stat(wf, {"name": "completion"})
    result = runwf(pstat, store(log))
    assert_complete(result)
    assert_state(result, {"name": "completion"})
示例#8
0
def start_process(
    workflow_key: str,
    user_inputs: Optional[List[State]] = None,
    user: str = SYSTEM_USER,
    broadcast_func: Optional[BroadcastFunc] = None,
) -> Tuple[UUID, Future]:
    """Start a process for workflow.

    Args:
        workflow_key: name of workflow
        user_inputs: List of form inputs from frontend
        user: User who starts this process
        broadcast_func: Optional function to broadcast process data

    Returns:
        process id

    """
    # ATTENTION!! When modifying this function make sure you make similar changes to `run_workflow` in the test code

    if user_inputs is None:
        user_inputs = [{}]

    pid = uuid4()
    workflow = get_workflow(workflow_key)

    if not workflow:
        raise_status(HTTPStatus.NOT_FOUND, "Workflow does not exist")

    initial_state = {
        "process_id": pid,
        "reporter": user,
        "workflow_name": workflow_key,
        "workflow_target": workflow.target,
    }

    try:
        state = post_process(workflow.initial_input_form, initial_state,
                             user_inputs)
    except FormValidationError:
        logger.exception("Validation errors", user_inputs=user_inputs)
        raise

    pstat = ProcessStat(pid,
                        workflow=workflow,
                        state=Success({
                            **state,
                            **initial_state
                        }),
                        log=workflow.steps,
                        current_user=user)

    _db_create_process(pstat)

    _safe_logstep_withfunc = partial(_safe_logstep,
                                     broadcast_func=broadcast_func)
    return _run_process_async(pstat.pid,
                              lambda: runwf(pstat, _safe_logstep_withfunc))
示例#9
0
def test_abort():
    wf = workflow("Aborting workflow")(lambda: init >> abort)

    log = []

    pstat = create_new_process_stat(wf, {"name": "aborting"})
    result = runwf(pstat, store(log))
    assert_aborted(result)
    assert_state(result, {"name": "aborting"})
示例#10
0
def test_failed_log_step():
    wf = workflow("Failing workflow")(lambda: init >> done)

    def failing_store(stat, step, state):
        return Failed(error_state_to_dict(Exception("Failure Message")))

    pstat = create_new_process_stat(wf, {"name": "init-state"})
    result = runwf(pstat, failing_store)
    assert_failed(result)
    assert extract_error(result) == "Failure Message"
示例#11
0
def test_skip_step():
    wf = workflow("Workflow with skipped step")(
        lambda: init >> purestep("Skipped")(Skipped) >> done)

    log = []
    pstat = create_new_process_stat(wf, {})
    result = runwf(pstat, store(log))
    assert_complete(result)

    skipped = [x[1] for x in log if x[0] == "Skipped"]
    assert skipped[0].isskipped()
示例#12
0
def test_suspend():
    wf = workflow("Workflow with user interaction")(
        lambda: begin >> step1 >> user_action >> step2)

    log = []

    pstat = create_new_process_stat(wf, {})
    result = runwf(pstat, store(log))

    assert_suspended(result)
    assert [("Step 1", Success({"steps": [1]})),
            ("Input Name", Suspend({"steps": [1]}))] == log
示例#13
0
def test_recover():
    log = []

    p = ProcessStat(
        pid=1,
        workflow=sample_workflow,
        state=Success({"steps": [4]}),
        log=sample_workflow.steps[1:],
        current_user="******",
    )
    result = runwf(p, store(log))
    assert_success(result)
    assert_state(result, {"steps": [4, 2, 3]})
示例#14
0
def test_error_in_focus_state():
    @step("Step that works on substate")
    def substep():
        raise ValueError("Error")

    subwf = focussteps("sub")
    wf = workflow("Workflow with sub workflow that focuses on sub state")(
        lambda: subwf(substep) >> done)

    log = []
    pstat = create_new_process_stat(wf, {"sub": {}})
    result = runwf(pstat, store(log))
    assert_failed(result)
    assert extract_error(result) == "Error"
示例#15
0
def resume_process(
    process: ProcessTable,
    *,
    user_inputs: Optional[List[State]] = None,
    user: Optional[str] = None,
    broadcast_func: Optional[BroadcastFunc] = None,
) -> Tuple[UUID, Future]:
    """Resume a failed or suspended process.

    Args:
        process: Process from database
        user_inputs: Optional user input from forms
        user: user who resumed this process
        broadcast_func: Optional function to broadcast process data

    Returns:
        process id

    """
    # ATTENTION!! When modifying this function make sure you make similar changes to `resume_workflow` in the test code

    if user_inputs is None:
        user_inputs = [{}]

    pstat = load_process(process)

    if pstat.workflow == removed_workflow:
        raise ValueError("This workflow cannot be resumed")

    form = pstat.log[0].form

    user_input = post_process(form, pstat.state.unwrap(), user_inputs)

    if user:
        pstat.update(current_user=user)

    if user_input:
        pstat.update(state=pstat.state.map(
            lambda state: StateMerger.merge(state, user_input)))

    # enforce an update to the process status to properly show the process
    process.last_status = ProcessStatus.RUNNING
    db.session.add(process)
    db.session.commit()

    _safe_logstep_prep = partial(_safe_logstep, broadcast_func=broadcast_func)
    return _run_process_async(pstat.pid,
                              lambda: runwf(pstat, _safe_logstep_prep))
示例#16
0
def test_failed_step():
    wf = workflow("Failing workflow")(lambda: init >> fail)

    log = []

    pstat = create_new_process_stat(wf, {"name": "init-state"})
    result = runwf(pstat, store(log))
    assert_failed(result)
    assert extract_error(result) == "Failure Message"
    assert [
        ("Start", Success({"name": "init-state"})),
        ("Fail",
         Failed({
             "class": "ValueError",
             "error": "Failure Message",
             "traceback": mock.ANY
         })),
    ] == log
示例#17
0
def test_conditionally_skip_a_step():
    @step("Inc N")
    def inc_n(n=0):
        return {"n": n + 1}

    limit_to_10 = conditional(lambda s: s.get("n", 0) < 10)

    incs = [limit_to_10(inc_n) for _ in range(0, 25)]
    wf = workflow("Limit the number of increments")(
        lambda: init >> reduce(lambda acc, e: acc >> e, incs) >> done)

    log = []

    pstat = create_new_process_stat(wf, {})
    result = runwf(pstat, store(log))
    assert_complete(result)
    # from ipdb import set_trace; set_trace()
    assert_state(result, {"n": 10})
    assert len([x for x in log
                if x[1].isskipped()]) == 15, "15 steps should be skipped"
示例#18
0
def test_waiting():
    wf = workflow("Workflow with soft fail")(
        lambda: begin >> step1 >> soft_fail >> step2)

    log = []

    pstat = create_new_process_stat(wf, {})
    result = runwf(pstat, store(log))

    assert_waiting(result)
    assert extract_error(result) == "Failure Message"

    assert [
        ("Step 1", Success({"steps": [1]})),
        ("Waiting step",
         Waiting({
             "class": "ValueError",
             "error": "Failure Message",
             "traceback": mock.ANY
         })),
    ] == log
示例#19
0
def test_resume_waiting_workflow():
    hack = {"error": True}

    @retrystep("Waiting step")
    def soft_fail():
        if hack["error"]:
            raise ValueError("error")
        else:
            return {"some_key": True}

    wf = workflow("Workflow with soft fail")(
        lambda: begin >> step1 >> soft_fail >> step2)

    log = []

    state = Waiting({"steps": [1]})

    hack["error"] = False
    p = ProcessStat(pid=1,
                    workflow=wf,
                    state=state,
                    log=wf.steps[1:],
                    current_user="******")
    result = runwf(p, logstep=store(log))

    assert_success(result)
    assert [
        ("Waiting step", Success({
            "steps": [1],
            "some_key": True
        })),
        ("Step 2", Success({
            "steps": [1, 2],
            "some_key": True
        })),
    ] == log