diff --git a/SpiffWorkflow/bpmn/parser/event_parsers.py b/SpiffWorkflow/bpmn/parser/event_parsers.py index 6db5ebe2..cd9666fa 100644 --- a/SpiffWorkflow/bpmn/parser/event_parsers.py +++ b/SpiffWorkflow/bpmn/parser/event_parsers.py @@ -306,6 +306,3 @@ def create_task(self): def handles_multiple_outgoing(self): return True - def connect_outgoing(self, outgoing_task, sequence_flow_node, is_default): - self.task.event_definition.event_definitions.append(outgoing_task.event_definition) - self.task.connect(outgoing_task) diff --git a/SpiffWorkflow/bpmn/serializer/migration/version_1_5.py b/SpiffWorkflow/bpmn/serializer/migration/version_1_5.py new file mode 100644 index 00000000..f605e171 --- /dev/null +++ b/SpiffWorkflow/bpmn/serializer/migration/version_1_5.py @@ -0,0 +1,18 @@ + +def update_event_gateway_children(dct): + + def update(tasks, specs): + for task in tasks: + task_spec = specs.get(task['task_spec'], {}) + if task_spec['typename'] == 'EventBasedGateway': + for name in task_spec['outputs']: + child = specs.get(name) + child['event_definition'] = { + "description": "Default", + "name": None, + "typename": "NoneEventDefinition" + } + + for up in dct['subprocesses'].values(): + update(sp['tasks'].values(), sp['spec']['task_specs']) + update(dct['tasks'].values(), dct['spec']['task_specs']) diff --git a/SpiffWorkflow/bpmn/serializer/migration/version_migration.py b/SpiffWorkflow/bpmn/serializer/migration/version_migration.py index 15b39a58..f30fe821 100644 --- a/SpiffWorkflow/bpmn/serializer/migration/version_migration.py +++ b/SpiffWorkflow/bpmn/serializer/migration/version_migration.py @@ -36,6 +36,19 @@ update_data_objects, ) from .version_1_4 import update_mi_states +from .version_1_5 import update_event_gateway_children + +def from_version_1_4(dct): + """Upgrade serialization from v1.4 to v1.5 + + Event based gateways now manage events for their children. Duplicating the events in + multiple task specs was very difficult to manage and introduced a lot of problems with + task states and predictions. The gateway will handle dropping the branches for the + alternate events and the child branch with the matched event will proceed with a none + event. + """ + dct['VERSION'] = "1.5" + update_event_gateway_children(dct) def from_version_1_3(dct): """Upgrade serialization from v1.3 to v1.4 @@ -122,4 +135,5 @@ def from_version_1_0(dct): '1.1': from_version_1_1, '1.2': from_version_1_2, '1.3': from_version_1_3, + '1.4': from_version_1_4, } diff --git a/SpiffWorkflow/bpmn/serializer/workflow.py b/SpiffWorkflow/bpmn/serializer/workflow.py index 12abfc51..fae69d8d 100644 --- a/SpiffWorkflow/bpmn/serializer/workflow.py +++ b/SpiffWorkflow/bpmn/serializer/workflow.py @@ -26,7 +26,7 @@ from .config import DEFAULT_CONFIG # This is the default version set on the workflow, it can be overridden in init -VERSION = "1.4" +VERSION = "1.5" class BpmnWorkflowSerializer: @@ -181,4 +181,4 @@ def from_dict(self, dct, **kwargs): Returns: a restored object """ - return self.registry.restore(dct, **kwargs) \ No newline at end of file + return self.registry.restore(dct, **kwargs) diff --git a/SpiffWorkflow/bpmn/specs/event_definitions/multiple.py b/SpiffWorkflow/bpmn/specs/event_definitions/multiple.py index 7c23aebe..4fad3033 100644 --- a/SpiffWorkflow/bpmn/specs/event_definitions/multiple.py +++ b/SpiffWorkflow/bpmn/specs/event_definitions/multiple.py @@ -1,3 +1,4 @@ +from SpiffWorkflow.bpmn.util.event import BpmnEvent from .timer import TimerEventDefinition, EventDefinition class MultipleEventDefinition(EventDefinition): @@ -11,16 +12,17 @@ def has_fired(self, my_task): event_definitions = list(self.event_definitions) seen_events = my_task.internal_data.get('seen_events', []) - for event_definition in self.event_definitions: + for event in seen_events: + if event.event_definition in event_definitions: + event_definitions.remove(event.event_definition) + + for event_definition in event_definitions: if isinstance(event_definition, TimerEventDefinition): - child = [c for c in my_task.children if c.task_spec.event_definition == event_definition] - child[0].task_spec._update_hook(child[0]) - if event_definition.has_fired(child[0]) and event_definition in event_definitions: + if event_definition.has_fired(my_task): event_definitions.remove(event_definition) - else: - for event in seen_events: - if event_definition.catches(my_task, event) and event_definition in event_definitions: - event_definitions.remove(event_definition) + seen_events.append(BpmnEvent(event_definition)) + + my_task.internal_data['seen_events'] = seen_events if self.parallel: # Parallel multiple need to match all events @@ -28,10 +30,16 @@ def has_fired(self, my_task): else: return len(seen_events) > 0 + def catches(self, my_task, event=None): + for item in self.event_definitions: + if item.catches(my_task, event): + return True + def catch(self, my_task, event=None): - event.event_definition.catch(my_task, event) - seen_events = my_task.internal_data.get('seen_events', []) + [event] - my_task._set_internal_data(seen_events=seen_events) + for item in self.event_definitions: + if item.catches(my_task, event): + seen_events = my_task.internal_data.get('seen_events', []) + [event] + my_task._set_internal_data(seen_events=seen_events) def reset(self, my_task): my_task.internal_data.pop('seen_events', None) @@ -47,4 +55,4 @@ def __eq__(self, other): def throw(self, my_task): # Mutiple events throw all associated events when they fire for event_definition in self.event_definitions: - event_definition.throw(my_task) \ No newline at end of file + event_definition.throw(my_task) diff --git a/SpiffWorkflow/bpmn/specs/mixins/events/intermediate_event.py b/SpiffWorkflow/bpmn/specs/mixins/events/intermediate_event.py index 62861154..9a16cc1c 100644 --- a/SpiffWorkflow/bpmn/specs/mixins/events/intermediate_event.py +++ b/SpiffWorkflow/bpmn/specs/mixins/events/intermediate_event.py @@ -18,6 +18,7 @@ # 02110-1301 USA from SpiffWorkflow.util.task import TaskState +from SpiffWorkflow.bpmn.specs.event_definitions import NoneEventDefinition from .event_types import ThrowingEvent, CatchingEvent @@ -53,11 +54,24 @@ def catches(self, my_task, event): class EventBasedGateway(CatchingEvent): + def connect(self, child): + # Having the events duplicated in the gateway and the child is difficult to manage + # Therefore, I am going to have the gateway manage the events and remove the event from the child + super().connect(child) + self.event_definition.event_definitions.append(child.event_definition) + child.event_definition = NoneEventDefinition() + def _predict_hook(self, my_task): - my_task._sync_children(self.outputs, state=TaskState.WAITING) + my_task._sync_children(self.outputs, state=TaskState.MAYBE) - def _on_ready_hook(self, my_task): - for child in my_task.children: - if not child.internal_data.get('event_fired'): - child.cancel() + def _run_hook(self, my_task): + seen_events = my_task.internal_data.get('seen_events', []) + matches = [] + for event in seen_events: + idx = self.event_definition.event_definitions.index(event.event_definition) + matches.append(my_task.children[idx].task_spec) + my_task._sync_children(matches, TaskState.FUTURE) + for child in my_task.children: + child.task_spec._predict(child, mask=TaskState.FUTURE|TaskState.PREDICTED_MASK) + return True diff --git a/SpiffWorkflow/bpmn/workflow.py b/SpiffWorkflow/bpmn/workflow.py index a676709c..5551607c 100644 --- a/SpiffWorkflow/bpmn/workflow.py +++ b/SpiffWorkflow/bpmn/workflow.py @@ -25,6 +25,7 @@ from SpiffWorkflow.bpmn.specs.control import BoundaryEventSplit from SpiffWorkflow.bpmn.specs.event_definitions.timer import TimerEventDefinition +from SpiffWorkflow.bpmn.specs.event_definitions.multiple import MultipleEventDefinition from SpiffWorkflow.bpmn.util.subworkflow import BpmnBaseWorkflow, BpmnSubWorkflow from SpiffWorkflow.bpmn.util.event import EventManager @@ -181,7 +182,7 @@ def refresh_timers(self): # Ideally this would go in event manager but I can't import the necessary classes there # Eventually I'll move it for task in list(self.event_manager.tasks.values()): - if isinstance(task.task_spec.event_definition, (TimerEventDefinition, )): + if isinstance(task.task_spec.event_definition, (TimerEventDefinition, MultipleEventDefinition)): task.task_spec._update(task) def get_task_from_id(self, task_id): diff --git a/tests/SpiffWorkflow/bpmn/data/event-gateway.bpmn b/tests/SpiffWorkflow/bpmn/data/event-gateway.bpmn index 1a83c7a4..f8a9a88d 100644 --- a/tests/SpiffWorkflow/bpmn/data/event-gateway.bpmn +++ b/tests/SpiffWorkflow/bpmn/data/event-gateway.bpmn @@ -4,16 +4,16 @@ Flow_0w4b5t2 - + - Flow_0w4b5t2 + Flow_04a2n0x Flow_0gge7fn Flow_0px7ksu Flow_1rfbrlf Flow_0gge7fn - Flow_1g4g85l + Flow_1xrb5hw @@ -27,59 +27,32 @@ Flow_1rfbrlf Flow_0mppjk9 - "PT1S" + "PT0.1S" - + Flow_0mppjk9 - - - Flow_1g4g85l - - + Flow_18v90rx + + Flow_0w4b5t2 + Flow_1xrb5hw + Flow_04a2n0x + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -93,15 +66,51 @@ - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/SpiffWorkflow/bpmn/data/serialization/v1.4-event-gateway.json b/tests/SpiffWorkflow/bpmn/data/serialization/v1.4-event-gateway.json new file mode 100644 index 00000000..1fdcd654 --- /dev/null +++ b/tests/SpiffWorkflow/bpmn/data/serialization/v1.4-event-gateway.json @@ -0,0 +1,440 @@ +{ + "serializer_version": "1.4", + "data": {}, + "correlations": {}, + "last_task": null, + "success": true, + "completed": false, + "tasks": { + "1be7f3e2-5256-4587-a4b9-746f6d669ff6": { + "id": "1be7f3e2-5256-4587-a4b9-746f6d669ff6", + "parent": null, + "children": [ + "122b69da-8576-4cd6-926a-a6f90e01ec03" + ], + "last_state_change": 1783529182.712798, + "state": 16, + "task_spec": "Start", + "triggered": false, + "internal_data": {}, + "data": {}, + "delta": {}, + "typename": "Task" + }, + "122b69da-8576-4cd6-926a-a6f90e01ec03": { + "id": "122b69da-8576-4cd6-926a-a6f90e01ec03", + "parent": "1be7f3e2-5256-4587-a4b9-746f6d669ff6", + "children": [ + "bf38d74f-495a-4528-8e32-f7a05576df55" + ], + "last_state_change": 1783529182.712654, + "state": 4, + "task_spec": "StartEvent_1", + "triggered": false, + "internal_data": {}, + "data": {}, + "delta": { + "updates": {}, + "deletions": [] + }, + "typename": "Task" + }, + "bf38d74f-495a-4528-8e32-f7a05576df55": { + "id": "bf38d74f-495a-4528-8e32-f7a05576df55", + "parent": "122b69da-8576-4cd6-926a-a6f90e01ec03", + "children": [ + "d51085ff-faa9-4cb7-91cb-40a7e11bd2ab" + ], + "last_state_change": 1783529182.7126684, + "state": 4, + "task_spec": "Gateway_1dxbnbw", + "triggered": false, + "internal_data": {}, + "data": {}, + "delta": { + "updates": {}, + "deletions": [] + }, + "typename": "Task" + }, + "d51085ff-faa9-4cb7-91cb-40a7e11bd2ab": { + "id": "d51085ff-faa9-4cb7-91cb-40a7e11bd2ab", + "parent": "bf38d74f-495a-4528-8e32-f7a05576df55", + "children": [ + "0bbac972-73ab-4959-b6a6-e41b4bc0de94", + "b5f495e6-2743-4790-b188-e2e06d5acb71", + "f7067209-8af8-4cce-b200-9949d5c72edc" + ], + "last_state_change": 1783529182.7126849, + "state": 2, + "task_spec": "Gateway_1434v9l", + "triggered": false, + "internal_data": {}, + "data": {}, + "delta": { + "updates": {}, + "deletions": [] + }, + "typename": "Task" + }, + "0bbac972-73ab-4959-b6a6-e41b4bc0de94": { + "id": "0bbac972-73ab-4959-b6a6-e41b4bc0de94", + "parent": "d51085ff-faa9-4cb7-91cb-40a7e11bd2ab", + "children": [], + "last_state_change": 1783529182.7126927, + "state": 1, + "task_spec": "message_1_event", + "triggered": false, + "internal_data": {}, + "data": {}, + "delta": { + "updates": {}, + "deletions": [] + }, + "typename": "Task" + }, + "b5f495e6-2743-4790-b188-e2e06d5acb71": { + "id": "b5f495e6-2743-4790-b188-e2e06d5acb71", + "parent": "d51085ff-faa9-4cb7-91cb-40a7e11bd2ab", + "children": [], + "last_state_change": 1783529182.7126956, + "state": 1, + "task_spec": "message_2_event", + "triggered": false, + "internal_data": {}, + "data": {}, + "delta": { + "updates": {}, + "deletions": [] + }, + "typename": "Task" + }, + "f7067209-8af8-4cce-b200-9949d5c72edc": { + "id": "f7067209-8af8-4cce-b200-9949d5c72edc", + "parent": "d51085ff-faa9-4cb7-91cb-40a7e11bd2ab", + "children": [], + "last_state_change": 1783529182.7126977, + "state": 1, + "task_spec": "timer_event", + "triggered": false, + "internal_data": {}, + "data": {}, + "delta": { + "updates": {}, + "deletions": [] + }, + "typename": "Task" + } + }, + "root": "1be7f3e2-5256-4587-a4b9-746f6d669ff6", + "spec": { + "name": "Process_0pvx19v", + "description": "Process_0pvx19v", + "file": "/home/essweine/work/sartography/SpiffWorkflow/tests/SpiffWorkflow/bpmn/data/diagram.bpmn", + "task_specs": { + "Start": { + "name": "Start", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": [], + "outputs": [ + "StartEvent_1" + ], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "BpmnStartTask" + }, + "Process_0pvx19v.EndJoin": { + "name": "Process_0pvx19v.EndJoin", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": [ + "message_2_end", + "timer_end" + ], + "outputs": [ + "End" + ], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "_EndJoin" + }, + "End": { + "name": "End", + "description": "BPMN Task", + "manual": false, + "lookahead": 2, + "inputs": [ + "Process_0pvx19v.EndJoin" + ], + "outputs": [], + "bpmn_id": null, + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "typename": "SimpleBpmnTask" + }, + "StartEvent_1": { + "name": "StartEvent_1", + "description": "Default Start Event", + "manual": false, + "lookahead": 2, + "inputs": [ + "Start" + ], + "outputs": [ + "Gateway_1dxbnbw" + ], + "bpmn_id": "StartEvent_1", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "extensions": {}, + "event_definition": { + "description": "Default", + "name": null, + "typename": "NoneEventDefinition" + }, + "typename": "StartEvent" + }, + "Gateway_1dxbnbw": { + "name": "Gateway_1dxbnbw", + "description": "Exclusive Gateway", + "manual": false, + "lookahead": 2, + "inputs": [ + "message_1_event", + "StartEvent_1" + ], + "outputs": [ + "Gateway_1434v9l" + ], + "bpmn_id": "Gateway_1dxbnbw", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "extensions": {}, + "cond_task_specs": [ + { + "condition": null, + "task_spec": "Gateway_1434v9l" + } + ], + "choice": null, + "default_task_spec": "Gateway_1434v9l", + "typename": "ExclusiveGateway" + }, + "Gateway_1434v9l": { + "name": "Gateway_1434v9l", + "description": "Default Event Based Gateway", + "manual": false, + "lookahead": 2, + "inputs": [ + "Gateway_1dxbnbw" + ], + "outputs": [ + "message_1_event", + "message_2_event", + "timer_event" + ], + "bpmn_id": "Gateway_1434v9l", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "extensions": {}, + "event_definition": { + "description": null, + "name": null, + "parallel": false, + "event_definitions": [ + { + "description": "Message", + "name": "message_1", + "correlation_properties": [], + "typename": "MessageEventDefinition" + }, + { + "description": "Message", + "name": "message_2", + "correlation_properties": [], + "typename": "MessageEventDefinition" + }, + { + "description": "Timer", + "name": "timer_event", + "expression": "\"PT0.1S\"", + "typename": "DurationTimerEventDefinition" + } + ], + "typename": "MultipleEventDefinition" + }, + "typename": "EventBasedGateway" + }, + "message_1_event": { + "name": "message_1_event", + "description": "Message Intermediate Catch Event", + "manual": false, + "lookahead": 2, + "inputs": [ + "Gateway_1434v9l" + ], + "outputs": [ + "Gateway_1dxbnbw" + ], + "bpmn_id": "message_1_event", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "extensions": {}, + "event_definition": { + "description": "Message", + "name": "message_1", + "correlation_properties": [], + "typename": "MessageEventDefinition" + }, + "typename": "IntermediateCatchEvent" + }, + "message_2_event": { + "name": "message_2_event", + "description": "Message Intermediate Catch Event", + "manual": false, + "lookahead": 2, + "inputs": [ + "Gateway_1434v9l" + ], + "outputs": [ + "message_2_end" + ], + "bpmn_id": "message_2_event", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "extensions": {}, + "event_definition": { + "description": "Message", + "name": "message_2", + "correlation_properties": [], + "typename": "MessageEventDefinition" + }, + "typename": "IntermediateCatchEvent" + }, + "message_2_end": { + "name": "message_2_end", + "description": "Default End Event", + "manual": false, + "lookahead": 2, + "inputs": [ + "message_2_event" + ], + "outputs": [ + "Process_0pvx19v.EndJoin" + ], + "bpmn_id": "message_2_end", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "extensions": {}, + "event_definition": { + "description": "Default", + "name": null, + "typename": "NoneEventDefinition" + }, + "typename": "EndEvent" + }, + "timer_event": { + "name": "timer_event", + "description": "Timer Intermediate Catch Event", + "manual": false, + "lookahead": 2, + "inputs": [ + "Gateway_1434v9l" + ], + "outputs": [ + "timer_end" + ], + "bpmn_id": "timer_event", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "extensions": {}, + "event_definition": { + "description": "Timer", + "name": "timer_event", + "expression": "\"PT0.1S\"", + "typename": "DurationTimerEventDefinition" + }, + "typename": "IntermediateCatchEvent" + }, + "timer_end": { + "name": "timer_end", + "description": "Default End Event", + "manual": false, + "lookahead": 2, + "inputs": [ + "timer_event" + ], + "outputs": [ + "Process_0pvx19v.EndJoin" + ], + "bpmn_id": "timer_end", + "bpmn_name": null, + "lane": null, + "documentation": null, + "data_input_associations": [], + "data_output_associations": [], + "io_specification": null, + "extensions": {}, + "event_definition": { + "description": "Default", + "name": null, + "typename": "NoneEventDefinition" + }, + "typename": "EndEvent" + } + }, + "io_specification": null, + "data_objects": {}, + "correlation_keys": {}, + "typename": "BpmnProcessSpec" + }, + "subprocess_specs": {}, + "subprocesses": {}, + "bpmn_events": [], + "typename": "BpmnWorkflow" +} diff --git a/tests/SpiffWorkflow/bpmn/events/EventBasedGatewayTest.py b/tests/SpiffWorkflow/bpmn/events/EventBasedGatewayTest.py index 8238f078..43616fab 100644 --- a/tests/SpiffWorkflow/bpmn/events/EventBasedGatewayTest.py +++ b/tests/SpiffWorkflow/bpmn/events/EventBasedGatewayTest.py @@ -1,4 +1,5 @@ from datetime import timedelta +from time import sleep from SpiffWorkflow import TaskState from SpiffWorkflow.bpmn import BpmnWorkflow, BpmnEvent @@ -13,6 +14,7 @@ def setUp(self): self.spec, self.subprocesses = self.load_workflow_spec('event-gateway.bpmn', 'Process_0pvx19v') self.script_engine = PythonScriptEngine(environment=TaskDataEnvironment({"timedelta": timedelta})) self.workflow = BpmnWorkflow(self.spec, script_engine=self.script_engine) + self.workflow.do_engine_steps() def testEventBasedGateway(self): self.actual_test() @@ -22,34 +24,45 @@ def testEventBasedGatewaySaveRestore(self): def actual_test(self, save_restore=False): - self.workflow.do_engine_steps() waiting_tasks = self.workflow.get_tasks(state=TaskState.WAITING) if save_restore: self.save_restore() self.workflow.script_engine = self.script_engine - self.assertEqual(len(waiting_tasks), 4) - self.workflow.catch(BpmnEvent(MessageEventDefinition('message_1'), {})) + self.assertEqual(len(waiting_tasks), 1) + self.workflow.catch(BpmnEvent(MessageEventDefinition('message_2'), {})) self.workflow.do_engine_steps() - # This needs to be fixed -- it shouldn't be necessary to call this method - # Unfortunately that requires completely rewriting event based gateways - # I really don't understand why the bpmn spec dictates that both the gateway and the children - # have duplicate event definitions, but it sure makes things difficult self.assertEqual(self.workflow.is_completed(), True) - self.assertEqual(self.workflow.get_next_task(spec_name='message_1_event').state, TaskState.COMPLETED) - self.assertEqual(self.workflow.get_next_task(spec_name='message_2_event').state, TaskState.CANCELLED) - self.assertEqual(self.workflow.get_next_task(spec_name='timer_event').state, TaskState.CANCELLED) + self.assertEqual(self.workflow.get_next_task(spec_name='message_2_event').state, TaskState.COMPLETED) + # The gateway now drops the branches that weren't followed, so these tasks shouldn't exist + self.assertEqual(self.workflow.get_next_task(spec_name='message_1_event'), None) + self.assertEqual(self.workflow.get_next_task(spec_name='timer_event'), None) + + def testLoop(self): + + self.workflow.catch(BpmnEvent(MessageEventDefinition('message_1'), {})) + self.workflow.do_engine_steps() + # We should have returned to the gateway, with one completed message 1 task and three maybe tasks + waiting_tasks = self.workflow.get_tasks(state=TaskState.WAITING) + self.assertEqual(len(waiting_tasks), 1) + states = [t.state for t in self.workflow.get_tasks(spec_name='message_1_event')] + self.assertEqual(states, [TaskState.COMPLETED, TaskState.MAYBE]) + self.assertEqual(self.workflow.get_next_task(spec_name='message_2_event').state, TaskState.MAYBE) + self.assertEqual(self.workflow.get_next_task(spec_name='timer_event').state, TaskState.MAYBE) def testTimeout(self): self.workflow.do_engine_steps() waiting_tasks = self.workflow.get_tasks(state=TaskState.WAITING) - self.assertEqual(len(waiting_tasks), 4) - timer_event_definition = waiting_tasks[0].task_spec.event_definition.event_definitions[-1] - self.workflow.catch(BpmnEvent(timer_event_definition)) + self.assertEqual(len(waiting_tasks), 1) + sleep(0.3) + self.workflow.refresh_timers() + # The gateway should be ready now + task = self.workflow.get_next_task(state=TaskState.READY) + self.assertEqual(task.state, TaskState.READY) self.workflow.do_engine_steps() self.assertEqual(self.workflow.is_completed(), True) - self.assertEqual(self.workflow.get_next_task(spec_name='message_1_event').state, TaskState.CANCELLED) - self.assertEqual(self.workflow.get_next_task(spec_name='message_2_event').state, TaskState.CANCELLED) + self.assertEqual(self.workflow.get_next_task(spec_name='message_1_event'), None) + self.assertEqual(self.workflow.get_next_task(spec_name='message_2_event'), None) self.assertEqual(self.workflow.get_next_task(spec_name='timer_event').state, TaskState.COMPLETED) def testMultipleStart(self): diff --git a/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py b/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py index d4624526..72f8b331 100644 --- a/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py +++ b/tests/SpiffWorkflow/bpmn/serializer/VersionMigrationTest.py @@ -4,6 +4,8 @@ from SpiffWorkflow import TaskState from SpiffWorkflow.bpmn.script_engine import PythonScriptEngine, TaskDataEnvironment from SpiffWorkflow.bpmn.serializer.exceptions import VersionMigrationError +from SpiffWorkflow.bpmn.util.event import BpmnEvent +from SpiffWorkflow.bpmn.specs.event_definitions import NoneEventDefinition, MessageEventDefinition from .BaseTestCase import BaseTestCase @@ -195,3 +197,21 @@ def test_update_mi_states(self): self.assertEqual(len(task_info['running']), 0) self.assertEqual(len(task_info['future']), 0) self.assertTrue(wf.completed) + +class Version_1_4_Test(BaseTestCase): + + def test_update_event_gateway_children(self): + + wf = self.deserialize_workflow('v1.4-event-gateway.json') + task = wf.get_next_task(spec_name='Gateway_1434v9l') + for child in task.children: + self.assertTrue(isinstance(child.task_spec.event_definition, NoneEventDefinition)) + + wf.do_engine_steps() + wf.catch(BpmnEvent(MessageEventDefinition('message_2'), {})) + wf.do_engine_steps() + self.assertEqual(wf.is_completed(), True) + self.assertEqual(wf.get_next_task(spec_name='message_2_event').state, TaskState.COMPLETED) + self.assertEqual(wf.get_next_task(spec_name='message_1_event'), None) + self.assertEqual(wf.get_next_task(spec_name='timer_event'), None) +