def test_node_indegree_unique_pair(self): """Test that the validation of links with indegree `unique_pair` works correctly The example here is a `DataNode` that has two incoming links with the same label, but with different types. This is legal and should pass validation. """ caller = WorkflowNode().store() data = Data().store() called = CalculationNode() # Verify that adding two incoming links with the same link label but different type is allowed called.add_incoming(caller, link_type=LinkType.CALL_CALC, link_label='call') called.add_incoming(data, link_type=LinkType.INPUT_CALC, link_label='call') called.store() uuids_incoming = set(node.uuid for node in called.get_incoming().all_nodes()) uuids_expected = set([caller.uuid, data.uuid]) self.assertEqual(uuids_incoming, uuids_expected)
def test_detect_invalid_links_unknown_link_type(self): """Test `verdi database integrity detect-invalid-links` when link type is invalid.""" result = self.cli_runner.invoke(cmd_database.detect_invalid_links, []) self.assertEqual(result.exit_code, 0) self.assertClickResultNoException(result) class WrongLinkType(enum.Enum): WRONG_CREATE = 'wrong_create' # Create an invalid link: invalid link type data = Data().store().backend_entity calculation = CalculationNode().store().backend_entity data.add_incoming(calculation, link_type=WrongLinkType.WRONG_CREATE, link_label='create') result = self.cli_runner.invoke(cmd_database.detect_invalid_links, []) self.assertNotEqual(result.exit_code, 0) self.assertIsNotNone(result.exception)
def setUpClass(cls, *args, **kwargs): """Create a basic valid graph that should help detect false positives.""" super(TestVerdiDatabasaIntegrity, cls).setUpClass(*args, **kwargs) data_input = Data().store() data_output = Data().store() calculation = CalculationNode() workflow_parent = WorkflowNode() workflow_child = WorkflowNode() workflow_parent.add_incoming(data_input, link_label='input', link_type=LinkType.INPUT_WORK) workflow_parent.store() workflow_child.add_incoming(data_input, link_label='input', link_type=LinkType.INPUT_WORK) workflow_child.add_incoming(workflow_parent, link_label='call', link_type=LinkType.CALL_WORK) workflow_child.store() calculation.add_incoming(data_input, link_label='input', link_type=LinkType.INPUT_CALC) calculation.add_incoming(workflow_child, link_label='input', link_type=LinkType.CALL_CALC) calculation.store() data_output.add_incoming(calculation, link_label='output', link_type=LinkType.CREATE) data_output.add_incoming(workflow_child, link_label='output', link_type=LinkType.RETURN) data_output.add_incoming(workflow_parent, link_label='output', link_type=LinkType.RETURN)
def test_detect_invalid_links_create_links(self): """Test `verdi database integrity detect-invalid-links` when there are multiple incoming `create` links.""" result = self.cli_runner.invoke(cmd_database.detect_invalid_links, []) self.assertEqual(result.exit_code, 0) self.assertClickResultNoException(result) # Create an invalid link: two `create` links data = Data().store().backend_entity calculation = CalculationNode().store().backend_entity data.add_incoming(calculation, link_type=LinkType.CREATE, link_label='create') data.add_incoming(calculation, link_type=LinkType.CREATE, link_label='create') result = self.cli_runner.invoke(cmd_database.detect_invalid_links, []) self.assertNotEqual(result.exit_code, 0) self.assertIsNotNone(result.exception)
def test_get_stored_link_triples(self): """Validate the `get_stored_link_triples` method.""" data = Data().store() calculation = CalculationNode() calculation.add_incoming(data, LinkType.INPUT_CALC, 'input') calculation.store() stored_triples = calculation.get_stored_link_triples() self.assertEqual(len(stored_triples), 1) link_triple = stored_triples[0] # Verify the type and value of the tuple elements self.assertTrue(isinstance(link_triple, LinkTriple)) self.assertTrue(isinstance(link_triple.node, Node)) self.assertTrue(isinstance(link_triple.link_type, LinkType)) self.assertEqual(link_triple.node.uuid, data.uuid) self.assertEqual(link_triple.link_type, LinkType.INPUT_CALC) self.assertEqual(link_triple.link_label, 'input')
def _generate_calculation_node(process_state=ProcessState.FINISHED, exit_status=None, entry_point=None): """Generate an instance of a `CalculationNode`.. :param process_state: state to set :param exit_status: optional exit status, will be set to `0` if `process_state` is `ProcessState.FINISHED` :return: a `CalculationNode` instance. """ from aiida.orm import CalculationNode if process_state is ProcessState.FINISHED and exit_status is None: exit_status = 0 node = CalculationNode(process_type=entry_point) node.set_process_state(process_state) if exit_status is not None: node.set_exit_status(exit_status) return node
def test_get_incoming(self): """Test that `Node.get_incoming` will return stored and cached input links.""" source_one = Data().store() source_two = Data().store() target = CalculationNode() target.add_incoming(source_one, LinkType.INPUT_CALC, 'link_one') target.add_incoming(source_two, LinkType.INPUT_CALC, 'link_two') # Without link type incoming_nodes = target.get_incoming().all() incoming_uuids = sorted([neighbor.node.uuid for neighbor in incoming_nodes]) self.assertEqual(incoming_uuids, sorted([source_one.uuid, source_two.uuid])) # Using a single link type incoming_nodes = target.get_incoming(link_type=LinkType.INPUT_CALC).all() incoming_uuids = sorted([neighbor.node.uuid for neighbor in incoming_nodes]) self.assertEqual(incoming_uuids, sorted([source_one.uuid, source_two.uuid])) # Using a link type tuple incoming_nodes = target.get_incoming(link_type=(LinkType.INPUT_CALC, LinkType.INPUT_WORK)).all() incoming_uuids = sorted([neighbor.node.uuid for neighbor in incoming_nodes]) self.assertEqual(incoming_uuids, sorted([source_one.uuid, source_two.uuid]))
def test_detect_invalid_links_call_links(self): """Test `verdi database integrity detect-invalid-links` when there are multiple incoming `call` links.""" result = self.cli_runner.invoke(cmd_database.detect_invalid_links, []) self.assertEqual(result.exit_code, 0) self.assertClickResultNoException(result) # Create an invalid link: two `call` links workflow = WorkflowNode().store().backend_entity calculation = CalculationNode().store().backend_entity calculation.add_incoming(workflow, link_type=LinkType.CALL_CALC, link_label='call') calculation.add_incoming(workflow, link_type=LinkType.CALL_CALC, link_label='call') result = self.cli_runner.invoke(cmd_database.detect_invalid_links, []) self.assertNotEqual(result.exit_code, 0) self.assertIsNotNone(result.exception)
def test_inputs_parents_relationship(self): """This test checks that the inputs_q, parents_q relationship and the corresponding properties work as expected.""" n_1 = Data().store() n_2 = CalculationNode() n_3 = Data().store() # Create a link between these 2 nodes n_2.add_incoming(n_1, link_type=LinkType.INPUT_CALC, link_label='N1') n_2.store() n_3.add_incoming(n_2, link_type=LinkType.CREATE, link_label='N2') # Check that the result of outputs is a list self.assertIsInstance(n_1.backend_entity.dbmodel.inputs, list, 'This is expected to be a list') # Check that the result of outputs_q is a query from sqlalchemy.orm.dynamic import AppenderQuery self.assertIsInstance( n_1.backend_entity.dbmodel.inputs_q, AppenderQuery, 'This is expected to be an AppenderQuery' ) # Check that the result of inputs is correct out = {_.pk for _ in n_3.backend_entity.dbmodel.inputs} self.assertEqual(out, set([n_2.pk]))
def test_process_node_updatable_attribute(self): """Check that updatable attributes and only those can be mutated for a stored but unsealed CalculationNode.""" node = CalculationNode() attrs_to_set = { 'bool': self.boolval, 'integer': self.intval, 'float': self.floatval, 'string': self.stringval, 'dict': self.dictval, 'list': self.listval, 'state': self.stateval } for key, value in attrs_to_set.items(): node.set_attribute(key, value) # Check before storing node.set_attribute(CalculationNode.PROCESS_STATE_KEY, self.stateval) self.assertEqual(node.get_attribute(CalculationNode.PROCESS_STATE_KEY), self.stateval) node.store() # Check after storing self.assertEqual(node.get_attribute(CalculationNode.PROCESS_STATE_KEY), self.stateval) # I should be able to mutate the updatable attribute but not the others node.set_attribute(CalculationNode.PROCESS_STATE_KEY, 'FINISHED') node.delete_attribute(CalculationNode.PROCESS_STATE_KEY) # Deleting non-existing attribute should raise attribute error with self.assertRaises(AttributeError): node.delete_attribute(CalculationNode.PROCESS_STATE_KEY) with self.assertRaises(ModificationNotAllowed): node.set_attribute('bool', False) with self.assertRaises(ModificationNotAllowed): node.delete_attribute('bool') node.seal() # After sealing, even updatable attributes should be immutable with self.assertRaises(ModificationNotAllowed): node.set_attribute(CalculationNode.PROCESS_STATE_KEY, 'FINISHED') with self.assertRaises(ModificationNotAllowed): node.delete_attribute(CalculationNode.PROCESS_STATE_KEY)
def test_tab_completable_properties(self): """Test properties to go from one node to a neighboring one""" # pylint: disable=too-many-statements input1 = Data().store() input2 = Data().store() top_workflow = WorkflowNode() workflow = WorkflowNode() calc1 = CalculationNode() calc2 = CalculationNode() output1 = Data().store() output2 = Data().store() # The `top_workflow` has two inputs, proxies them to `workflow`, that in turn calls two calculations, passing # one data node to each as input, and return the two data nodes returned one by each called calculation top_workflow.add_incoming(input1, link_type=LinkType.INPUT_WORK, link_label='a') top_workflow.add_incoming(input2, link_type=LinkType.INPUT_WORK, link_label='b') top_workflow.store() workflow.add_incoming(input1, link_type=LinkType.INPUT_WORK, link_label='a') workflow.add_incoming(input2, link_type=LinkType.INPUT_WORK, link_label='b') workflow.add_incoming(top_workflow, link_type=LinkType.CALL_WORK, link_label='CALL') workflow.store() calc1.add_incoming(input1, link_type=LinkType.INPUT_CALC, link_label='input_value') calc1.add_incoming(workflow, link_type=LinkType.CALL_CALC, link_label='CALL') calc1.store() output1.add_incoming(calc1, link_type=LinkType.CREATE, link_label='result') calc2.add_incoming(input2, link_type=LinkType.INPUT_CALC, link_label='input_value') calc2.add_incoming(workflow, link_type=LinkType.CALL_CALC, link_label='CALL') calc2.store() output2.add_incoming(calc2, link_type=LinkType.CREATE, link_label='result') output1.add_incoming(workflow, link_type=LinkType.RETURN, link_label='result_a') output2.add_incoming(workflow, link_type=LinkType.RETURN, link_label='result_b') output1.add_incoming(top_workflow, link_type=LinkType.RETURN, link_label='result_a') output2.add_incoming(top_workflow, link_type=LinkType.RETURN, link_label='result_b') # creator self.assertEqual(output1.creator.pk, calc1.pk) self.assertEqual(output2.creator.pk, calc2.pk) # caller (for calculations) self.assertEqual(calc1.caller.pk, workflow.pk) self.assertEqual(calc2.caller.pk, workflow.pk) # caller (for workflows) self.assertEqual(workflow.caller.pk, top_workflow.pk) # .inputs for calculations self.assertEqual(calc1.inputs.input_value.pk, input1.pk) self.assertEqual(calc2.inputs.input_value.pk, input2.pk) with self.assertRaises(exceptions.NotExistent): _ = calc1.inputs.some_label # .inputs for workflows self.assertEqual(top_workflow.inputs.a.pk, input1.pk) self.assertEqual(top_workflow.inputs.b.pk, input2.pk) self.assertEqual(workflow.inputs.a.pk, input1.pk) self.assertEqual(workflow.inputs.b.pk, input2.pk) with self.assertRaises(exceptions.NotExistent): _ = workflow.inputs.some_label # .outputs for calculations self.assertEqual(calc1.outputs.result.pk, output1.pk) self.assertEqual(calc2.outputs.result.pk, output2.pk) with self.assertRaises(exceptions.NotExistent): _ = calc1.outputs.some_label # .outputs for workflows self.assertEqual(top_workflow.outputs.result_a.pk, output1.pk) self.assertEqual(top_workflow.outputs.result_b.pk, output2.pk) self.assertEqual(workflow.outputs.result_a.pk, output1.pk) self.assertEqual(workflow.outputs.result_b.pk, output2.pk) with self.assertRaises(exceptions.NotExistent): _ = workflow.outputs.some_label
def test_get_node_by_label(self): """Test the get_node_by_label() method of the `LinkManager` In particular, check both the it returns the correct values, but also that it raises the expected exceptions where appropriate (missing link with a given label, or more than one link) """ data = Data().store() calc_one_a = CalculationNode() calc_one_b = CalculationNode() calc_two = CalculationNode() # Two calcs using the data with the same label calc_one_a.add_incoming(data, link_type=LinkType.INPUT_CALC, link_label='input') calc_one_b.add_incoming(data, link_type=LinkType.INPUT_CALC, link_label='input') # A different label calc_two.add_incoming(data, link_type=LinkType.INPUT_CALC, link_label='the_input') calc_one_a.store() calc_one_b.store() calc_two.store() # Retrieve a link when the label is unique output_the_input = data.get_outgoing(link_type=LinkType.INPUT_CALC).get_node_by_label('the_input') self.assertEqual(output_the_input.pk, calc_two.pk) with self.assertRaises(exceptions.MultipleObjectsError): data.get_outgoing(link_type=LinkType.INPUT_CALC).get_node_by_label('input') with self.assertRaises(exceptions.NotExistent): data.get_outgoing(link_type=LinkType.INPUT_CALC).get_node_by_label('some_weird_label')
def setUp(self): super(TestNodeLinks, self).setUp() self.node_source = CalculationNode() self.node_target = Data()
def setUp(self): super().setUp() self.node_source = CalculationNode() self.node_target = Data()