def test_detect_invalid_links_workflow_create(self): """Test `verdi database integrity detect-invalid-links` outgoing `create` from `workflow`.""" result = self.cli_runner.invoke(cmd_database.detect_invalid_links, []) self.assertEqual(result.exit_code, 0) self.assertClickResultNoException(result) # Create an invalid link: outgoing `create` from a workflow data = Data().store().backend_entity workflow = WorkflowNode().store().backend_entity data.add_incoming(workflow, 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_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 _generate_eos_node(include_magnetization=True): from aiida.common import LinkType from aiida.orm import Float, WorkflowNode node = WorkflowNode(process_type='aiida.workflows:common_workflows.eos').store() for index in range(5): structure = generate_structure().store() energy = Float(index).store() structure.add_incoming(node, link_type=LinkType.RETURN, link_label=f'structures__{index}') energy.add_incoming(node, link_type=LinkType.RETURN, link_label=f'total_energies__{index}') if include_magnetization: magnetization = Float(index).store() magnetization.add_incoming(node, link_type=LinkType.RETURN, link_label=f'total_magnetizations__{index}') return node
def _generate_dissociation_curve_node(include_magnetization=True): from aiida.common import LinkType from aiida.orm import Float, WorkflowNode node = WorkflowNode(process_type='aiida.workflows:common_workflows.dissociation_curve').store() for index in range(5): distance = Float(index / 10).store() energy = Float(index).store() distance.add_incoming(node, link_type=LinkType.RETURN, link_label=f'distances__{index}') energy.add_incoming(node, link_type=LinkType.RETURN, link_label=f'total_energies__{index}') if include_magnetization: magnetization = Float(index).store() magnetization.add_incoming(node, link_type=LinkType.RETURN, link_label=f'total_magnetizations__{index}') return node
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_process_report(self): """Test verdi process report""" node = WorkflowNode().store() # Running without identifiers should not except and not print anything options = [] result = self.cli_runner.invoke(cmd_process.process_report, options) self.assertIsNone(result.exception, result.output) self.assertEqual(len(get_result_lines(result)), 0) # Giving a single identifier should print a non empty string message options = [str(node.pk)] result = self.cli_runner.invoke(cmd_process.process_report, options) self.assertIsNone(result.exception, result.output) self.assertTrue(len(get_result_lines(result)) > 0) # Giving multiple identifiers should print a non empty string message options = [str(calc.pk) for calc in [node]] result = self.cli_runner.invoke(cmd_process.process_report, options) self.assertIsNone(result.exception, result.output) self.assertTrue(len(get_result_lines(result)) > 0)
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
def test_add_incoming_call_work(self): """Nodes can only have a single incoming CALL_WORK link, independent of the source node.""" source_one = WorkflowNode() source_two = WorkflowNode() target = WorkflowNode() target.add_incoming(source_one, LinkType.CALL_WORK, 'link_label') # Can only have a single incoming CALL_WORK link with self.assertRaises(ValueError): target.validate_incoming(source_one, LinkType.CALL_WORK, 'link_label') # Even when the source node is different with self.assertRaises(ValueError): target.validate_incoming(source_two, LinkType.CALL_WORK, 'link_label') # Or when the link label is different with self.assertRaises(ValueError): target.validate_incoming(source_one, LinkType.CALL_WORK, 'other_label')