diff --git a/force_bdss/core/tests/test_verifier.py b/force_bdss/core/tests/test_verifier.py new file mode 100644 index 0000000000000000000000000000000000000000..6c5ea3569d928820a4469f4f2154a18040aa92b2 --- /dev/null +++ b/force_bdss/core/tests/test_verifier.py @@ -0,0 +1,92 @@ +import unittest + +from force_bdss.core.execution_layer import ExecutionLayer +from force_bdss.core.input_slot_info import InputSlotInfo +from force_bdss.core.output_slot_info import OutputSlotInfo +from force_bdss.core.verifier import verify_workflow +from force_bdss.core.workflow import Workflow +from force_bdss.tests.dummy_classes.extension_plugin import \ + DummyExtensionPlugin + + +class TestVerifier(unittest.TestCase): + def setUp(self): + self.plugin = DummyExtensionPlugin() + self.workflow = Workflow() + + def test_empty_workflow(self): + wf = self.workflow + errors = verify_workflow(wf) + self.assertEqual(len(errors), 2) + self.assertEqual(errors[0].subject, wf) + self.assertIn("no MCO", errors[0].error) + + self.assertEqual(errors[1].subject, wf) + self.assertIn("no execution layers", errors[1].error) + + def test_no_mco_parameters(self): + wf = self.workflow + wf.mco = self.plugin.mco_factories[0].create_model() + + errors = verify_workflow(wf) + self.assertEqual(len(errors), 2) + self.assertEqual(errors[0].subject, wf.mco) + self.assertIn("no defined parameters", errors[0].error) + + def test_parameters_empty_names(self): + wf = self.workflow + mco_factory = self.plugin.mco_factories[0] + wf.mco = mco_factory.create_model() + parameter_factory = mco_factory.parameter_factories()[0] + wf.mco.parameters.append(parameter_factory.create_model()) + + errors = verify_workflow(wf) + self.assertEqual(len(errors), 3) + self.assertEqual(errors[0].subject, wf.mco.parameters[0]) + self.assertIn("empty name", errors[0].error) + self.assertEqual(errors[1].subject, wf.mco.parameters[0]) + self.assertIn("empty type", errors[1].error) + + def test_data_sources(self): + wf = self.workflow + mco_factory = self.plugin.mco_factories[0] + wf.mco = mco_factory.create_model() + parameter_factory = mco_factory.parameter_factories()[0] + wf.mco.parameters.append(parameter_factory.create_model()) + wf.mco.parameters[0].name = "name" + wf.mco.parameters[0].type = "type" + + layer = ExecutionLayer() + wf.execution_layers.append(layer) + ds_factory = self.plugin.data_source_factories[0] + ds_model = ds_factory.create_model() + layer.data_sources.append(ds_model) + + errors = verify_workflow(wf) + self.assertEqual(errors[0].subject, ds_model) + self.assertIn("Missing input slot name assignment", errors[0].error) + + ds_model.input_slot_info.append( + InputSlotInfo(name="name") + ) + + errors = verify_workflow(wf) + self.assertEqual(errors[0].subject, ds_model) + self.assertIn("Missing output slot name assignment", errors[0].error) + + ds_model.output_slot_info.append( + OutputSlotInfo(name="name") + ) + + errors = verify_workflow(wf) + self.assertEqual(len(errors), 0) + + ds_model.input_slot_info[0].name = '' + errors = verify_workflow(wf) + self.assertEqual(len(errors), 1) + self.assertIn("Undefined name for input parameter", errors[0].error) + + ds_model.output_slot_info[0].name = '' + errors = verify_workflow(wf) + self.assertEqual(len(errors), 2) + self.assertIn("Undefined name for output parameter", errors[1].error) diff --git a/force_bdss/core/verifier.py b/force_bdss/core/verifier.py new file mode 100644 index 0000000000000000000000000000000000000000..3361f35fc143a2b0b9bac09e1ca594cc548e57e3 --- /dev/null +++ b/force_bdss/core/verifier.py @@ -0,0 +1,122 @@ +import logging +from traits.api import HasStrictTraits, Str, Any + +logger = logging.getLogger(__name__) + + +class VerifierError(HasStrictTraits): + subject = Any() + error = Str() + + +def verify_workflow(workflow): + """Verifies if the workflow can be executed, and specifies where the + error occurs and why. + + """ + result = [] + + result.extend(_check_mco(workflow)) + result.extend(_check_execution_layers(workflow)) + + return result + + +def _check_mco(workflow): + errors = [] + if workflow.mco is None: + errors.append( + VerifierError( + subject=workflow, + error="Workflow has no MCO" + ) + ) + return errors + + mco = workflow.mco + if len(mco.parameters) == 0: + errors.append(VerifierError(subject=mco, + error="MCO has no defined parameters")) + + for param in mco.parameters: + if len(param.name.strip()) == 0: + errors.append(VerifierError(subject=param, + error="Parameter has empty name")) + if len(param.type.strip()) == 0: + errors.append(VerifierError(subject=param, + error="Parameter has empty type")) + return errors + + +def _check_execution_layers(workflow): + errors = [] + + layers = workflow.execution_layers + if len(layers) == 0: + errors.append( + VerifierError( + subject=workflow, + error="Workflow has no execution layers" + ) + ) + + return errors + + for layer in layers: + if len(layer.data_sources) == 0: + errors.append(VerifierError(subject=layer, + error="Layer has no data sources")) + + for ds in layer.data_sources: + errors.extend(_check_data_source(ds)) + + return errors + + +def _check_data_source(data_source_model): + errors = [] + + factory = data_source_model.factory + try: + data_source = factory.create_data_source() + except Exception: + logger.exception("Unable to create data source from factory" + " '{}', plugin '{}'. This might indicate a " + "programming error".format(factory.id, + factory.plugin.id)) + raise + + try: + input_slots, output_slots = data_source.slots(data_source_model) + except Exception: + logger.exception( + "Unable to retrieve slot information from data source" + " created by factory '{}', plugin '{}'. This might " + "indicate a programming error.".format( + data_source.factory.id, + data_source.factory.plugin.id)) + raise + + if len(input_slots) != len(data_source_model.input_slot_info): + errors.append(VerifierError( + subject=data_source_model, + error="Missing input slot name assignment")) + + for idx, info in enumerate(data_source_model.input_slot_info): + if len(info.name.strip()) == 0: + errors.append(VerifierError( + subject=data_source_model, + error="Undefined name for input parameter {}".format(idx))) + + if len(output_slots) != len(data_source_model.output_slot_info): + errors.append(VerifierError( + subject=data_source_model, + error="Missing output slot name assignment")) + + for idx, info in enumerate(data_source_model.output_slot_info): + if len(info.name.strip()) == 0: + errors.append(VerifierError( + subject=data_source_model, + error="Undefined name for output parameter {}".format(idx))) + + return errors diff --git a/force_bdss/tests/dummy_classes/data_source.py b/force_bdss/tests/dummy_classes/data_source.py index b53cb5cbcc6aff2689ffe790f2d84f2a1d372fc7..c4547881d5edae073c31044feeaf7b2c4b852406 100644 --- a/force_bdss/tests/dummy_classes/data_source.py +++ b/force_bdss/tests/dummy_classes/data_source.py @@ -1,3 +1,4 @@ +from force_bdss.core.slot import Slot from force_bdss.data_sources.base_data_source import BaseDataSource from force_bdss.data_sources.base_data_source_factory import \ BaseDataSourceFactory @@ -9,7 +10,11 @@ class DummyDataSource(BaseDataSource): pass def slots(self, model): - return (), () + return ( + Slot(type="TYPE1"), + ), ( + Slot(type="TYPE2"), + ) class DummyDataSourceModel(BaseDataSourceModel):