def test_delete_through_utility_method(self): """Test deletion works correctly through the `aiida.backends.utils.delete_nodes_and_connections`.""" from aiida.common import timezone from aiida.backends.utils import delete_nodes_and_connections data_one = Data().store() data_two = Data().store() calculation = CalculationNode() calculation.add_incoming(data_one, LinkType.INPUT_CALC, 'input_one') calculation.add_incoming(data_two, LinkType.INPUT_CALC, 'input_two') calculation.store() log_one = Log(timezone.now(), 'test', 'INFO', data_one.pk).store() log_two = Log(timezone.now(), 'test', 'INFO', data_two.pk).store() assert len(Log.objects.get_logs_for(data_one)) == 1 assert Log.objects.get_logs_for(data_one)[0].pk == log_one.pk assert len(Log.objects.get_logs_for(data_two)) == 1 assert Log.objects.get_logs_for(data_two)[0].pk == log_two.pk delete_nodes_and_connections([data_two.pk]) assert len(Log.objects.get_logs_for(data_one)) == 1 assert Log.objects.get_logs_for(data_one)[0].pk == log_one.pk assert len(Log.objects.get_logs_for(data_two)) == 0
def test_inputs_parents_relationship(self): """ This test checks that the inputs_q, parents_q relationship and the corresponding properties work as expected. """ n1 = Data().store() n2 = CalculationNode() n3 = Data().store() # Create a link between these 2 nodes n2.add_incoming(n1, link_type=LinkType.INPUT_CALC, link_label='N1') n2.store() n3.add_incoming(n2, link_type=LinkType.CREATE, link_label='N2') # Check that the result of outputs is a list self.assertIsInstance(n1.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(n1.backend_entity.dbmodel.inputs_q, AppenderQuery, 'This is expected to be an AppenderQuery') # Check that the result of inputs is correct out = set([_.pk for _ in n3.backend_entity.dbmodel.inputs]) self.assertEqual(out, set([n2.pk]))
def test_delete_collection_incoming_link(self): """Test deletion through objects collection raises when there are incoming links.""" data = Data().store() calculation = CalculationNode() calculation.add_incoming(data, LinkType.INPUT_CALC, 'input') calculation.store() with pytest.raises(exceptions.InvalidOperation): Node.objects.delete(calculation.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_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_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 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 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_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