spiff-arena/tests/SpiffWorkflow/bpmn/ParallelMultiInstanceTest.py
burnettk 38ff7e08da Squashed 'SpiffWorkflow/' changes from 01a25fc3..0adfc8cb
0adfc8cb update spiff signal event
52d65dfc Merge pull request #346 from sartography/bugfix/update-split-task-inputs
23ca808c make sure tasks with boundary events are connected properly
de427431 remove pointless parser attribute and variable
3236b5b9 change subprocess start event validation error message text
a5ad0672 Merge pull request #344 from sartography/bugfix/gateway-in-loop
6b22a195 allow event definitions without refs
bb843873 clean up inclusive gateway a little
a7d81c02 prevent parallel gateways from waiting on descendants
802352a2 move test to proper location
ebcdde95 Merge pull request #343 from sartography/feature/event-payloads
87636472 add an additional test for boundary event parent removal
41423168 add escalation event test
26c5a9bb fix message event catch check
9bd018e5 Add test case for call activities in sub processes, fixed bug (#342)
546923b4 add serialization migration for boundary events
46d41e20 add spiff payload extensions + base spec improvements
a0f5fc2a remove boundary event parent
56277ff1 add event class
6de6fb2b reorganize event definitions
7db65566 fix subprocess cancel
442535ae do not allow the task trace to infinitely loop to get the trace w/ burnettk
dbe41c8e Merge pull request #339 from sartography/improvement/better-subworkflow-management
6c61f20a update parent attribute name
ea0a7bf4 update correlations when catching external messages
f511e2dd remove unneeded method from tests
953fa63c clean up workflow methods
efcdcf54 Merge pull request #338 from sartography/bugfix/triggered-multiinstance-tasks
11b78ce7 prevent errors synching children of MI tasks
bbb79e05 miscellaneous improvements
3f8e8d84 split bpmn workflow into two classes
61e01201 clean up workflow attributes & methods

git-subtree-dir: SpiffWorkflow
git-subtree-split: 0adfc8cbaec80d36f98a4136434e960f666fcfe2
2023-08-09 16:14:27 -04:00

234 lines
9.6 KiB
Python

from SpiffWorkflow.task import TaskState
from SpiffWorkflow.bpmn.exceptions import WorkflowDataException
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow
from SpiffWorkflow.bpmn.specs.data_spec import TaskDataReference
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
class BaseTestCase(BpmnWorkflowTestCase):
def set_io_and_run_workflow(self, data, data_input=None, data_output=None, save_restore=False):
start = self.workflow.get_tasks_from_spec_name('Start')[0]
start.data = data
any_task = self.workflow.get_tasks_from_spec_name('any_task')[0]
any_task.task_spec.data_input = TaskDataReference(data_input) if data_input is not None else None
any_task.task_spec.data_output = TaskDataReference(data_output) if data_output is not None else None
self.workflow.do_engine_steps()
ready_tasks = self.workflow.get_ready_user_tasks()
self.assertEqual(len(ready_tasks), 3)
while len(ready_tasks) > 0:
task = ready_tasks[0]
self.assertEqual(task.task_spec.name, 'any_task [child]')
self.assertIn('input_item', task.data)
task.data['output_item'] = task.data['input_item'] * 2
task.run()
if save_restore:
self.save_restore()
ready_tasks = self.workflow.get_ready_user_tasks()
self.workflow.refresh_waiting_tasks()
self.workflow.do_engine_steps()
self.assertTrue(self.workflow.is_completed())
def run_workflow_with_condition(self, data):
start = self.workflow.get_tasks_from_spec_name('Start')[0]
start.data = data
task = self.workflow.get_tasks_from_spec_name('any_task')[0]
task.task_spec.condition = "input_item == 2"
self.workflow.do_engine_steps()
ready_tasks = self.workflow.get_ready_user_tasks()
self.assertEqual(len(ready_tasks), 3)
task = [t for t in ready_tasks if t.data['input_item'] == 2][0]
task.data['output_item'] = task.data['input_item'] * 2
task.run()
self.workflow.do_engine_steps()
self.workflow.refresh_waiting_tasks()
self.assertTrue(self.workflow.is_completed())
self.assertEqual(len([ t for t in ready_tasks if t.state == TaskState.CANCELLED]), 2)
class ParallellMultiInstanceExistingOutputTest(BaseTestCase):
def setUp(self):
self.spec, subprocess = self.load_workflow_spec('parallel_multiinstance_loop_input.bpmn', 'main')
self.workflow = BpmnWorkflow(self.spec)
def testListWithDictOutput(self):
data = {
'input_data': [1, 2, 3],
'output_data': {},
}
self.set_io_and_run_workflow(data, data_input='input_data', data_output='output_data')
self.assertDictEqual(self.workflow.data, {
'input_data': [1, 2, 3],
'output_data': {0: 2, 1: 4, 2: 6},
})
def testDictWithListOutput(self):
data = {
'input_data': {'a': 1, 'b': 2, 'c': 3},
'output_data': [],
}
self.set_io_and_run_workflow(data, data_input='input_data', data_output='output_data')
self.assertDictEqual(self.workflow.data, {
'input_data': {'a': 1, 'b': 2, 'c': 3},
'output_data': [2, 4, 6],
})
def testNonEmptyOutput(self):
with self.assertRaises(WorkflowDataException) as exc:
data = {
'input_data': [1, 2, 3],
'output_data': [1, 2, 3],
}
self.set_io_and_run_workflow(data, data_input='input_data', data_output='output_data')
self.assertEqual(exc.exception.message,
"If the input is not being updated in place, the output must be empty or it must be a map (dict)")
def testInvalidOutputType(self):
with self.assertRaises(WorkflowDataException) as exc:
data = {
'input_data': set([1, 2, 3]),
'output_data': set(),
}
self.set_io_and_run_workflow(data, data_input='input_data', data_output='output_data')
self.assertEqual(exc.exception.message, "Only a mutable map (dict) or sequence (list) can be used for output")
class ParallelMultiInstanceNewOutputTest(BaseTestCase):
def setUp(self):
self.spec, subprocess = self.load_workflow_spec('parallel_multiinstance_loop_input.bpmn', 'main')
self.workflow = BpmnWorkflow(self.spec)
def testList(self):
data = {'input_data': [1, 2, 3]}
self.set_io_and_run_workflow(data, data_input='input_data', data_output='output_data')
self.assertDictEqual(self.workflow.data, {
'input_data': [1, 2, 3],
'output_data': [2, 4, 6]
})
def testListSaveRestore(self):
data = {'input_data': [1, 2, 3]}
self.set_io_and_run_workflow(data, data_input='input_data', data_output='output_data', save_restore=True)
self.assertDictEqual(self.workflow.data, {
'input_data': [1, 2, 3],
'output_data': [2, 4, 6]
})
def testDict(self):
data = {'input_data': {'a': 1, 'b': 2, 'c': 3} }
self.set_io_and_run_workflow(data, data_input='input_data', data_output='output_data')
self.assertDictEqual(self.workflow.data, {
'input_data': {'a': 1, 'b': 2, 'c': 3},
'output_data': {'a': 2, 'b': 4, 'c': 6}
})
def testDictSaveRestore(self):
data = {'input_data': {'a': 1, 'b': 2, 'c': 3} }
self.set_io_and_run_workflow(data, data_input='input_data', data_output='output_data', save_restore=True)
self.assertDictEqual(self.workflow.data, {
'input_data': {'a': 1, 'b': 2, 'c': 3},
'output_data': {'a': 2, 'b': 4, 'c': 6}
})
def testSet(self):
data = {'input_data': set([1, 2, 3])}
self.set_io_and_run_workflow(data, data_input='input_data', data_output='output_data')
self.assertDictEqual(self.workflow.data, {
'input_data': set([1, 2, 3]),
'output_data': [2, 4, 6]
})
def testEmptyCollection(self):
start = self.workflow.get_tasks_from_spec_name('Start')[0]
start.data = {'input_data': []}
self.workflow.do_engine_steps()
self.assertTrue(self.workflow.is_completed())
self.assertDictEqual(self.workflow.data, {'input_data': [], 'output_data': []})
def testCondition(self):
self.run_workflow_with_condition({'input_data': [1, 2, 3]})
self.assertDictEqual(self.workflow.data, {
'input_data': [1, 2, 3],
'output_data': [4]
})
class ParallelMultiInstanceUpdateInputTest(BaseTestCase):
def setUp(self):
self.spec, subprocess = self.load_workflow_spec('parallel_multiinstance_loop_input.bpmn', 'main')
self.workflow = BpmnWorkflow(self.spec)
def testList(self):
data = { 'input_data': [1, 2, 3]}
self.set_io_and_run_workflow(data, data_input='input_data', data_output='input_data')
self.assertDictEqual(self.workflow.data, {'input_data': [2, 4, 6]})
def testDict(self):
data = { 'input_data': {'a': 1, 'b': 2, 'c': 3}}
self.set_io_and_run_workflow(data, data_input='input_data', data_output='input_data')
self.assertDictEqual(self.workflow.data, {'input_data': {'a': 2, 'b': 4, 'c': 6}})
class ParallelMultiInstanceWithCardinality(BaseTestCase):
def setUp(self) -> None:
self.spec, subprocess = self.load_workflow_spec('parallel_multiinstance_cardinality.bpmn', 'main')
self.workflow = BpmnWorkflow(self.spec)
def testCardinality(self):
self.set_io_and_run_workflow({}, data_output='output_data')
self.assertDictEqual(self.workflow.data, {'output_data': [0, 2, 4]})
def testCardinalitySaveRestore(self):
self.set_io_and_run_workflow({}, data_output='output_data', save_restore=True)
self.assertDictEqual(self.workflow.data, {'output_data': [0, 2, 4]})
def testCondition(self):
self.run_workflow_with_condition({})
self.assertDictEqual(self.workflow.data, {
'output_data': [4]
})
class ParallelMultiInstanceTaskTest(BpmnWorkflowTestCase):
def check_reference(self, reference, name):
self.assertIsInstance(reference, TaskDataReference)
self.assertEqual(reference.bpmn_id, name)
def testParseInputOutput(self):
spec, subprocess = self.load_workflow_spec('parallel_multiinstance_loop_input.bpmn', 'main')
workflow = BpmnWorkflow(spec)
task_spec = workflow.get_tasks_from_spec_name('any_task')[0].task_spec
self.check_reference(task_spec.data_input, 'input_data')
self.check_reference(task_spec.data_output, 'output_data')
self.check_reference(task_spec.input_item, 'input_item')
self.check_reference(task_spec.output_item, 'output_item')
self.assertIsNone(task_spec.cardinality)
def testParseCardinality(self):
spec, subprocess = self.load_workflow_spec('parallel_multiinstance_cardinality.bpmn', 'main')
workflow = BpmnWorkflow(spec)
task_spec = workflow.get_tasks_from_spec_name('any_task')[0].task_spec
self.assertIsNone(task_spec.data_input)
self.assertEqual(task_spec.cardinality, '3')
def testInvalidBpmn(self):
with self.assertRaises(ValidationException) as exc:
spec, subprocess = self.load_workflow_spec('parallel_multiinstance_invalid.bpmn', 'main')
self.assertEqual(exc.exception.message,
'A multiinstance task must specify exactly one of cardinality or loop input data reference.')