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)
+