Beispiel #1
0
    def test_node_outdegree_unique_triple(self):
        """Test that the validation of links with outdegree `unique_triple` works correctly

        The example here is a `CalculationNode` that has two outgoing CREATE links with the same label, but to different
        target nodes. This is legal and should pass validation.
        """
        creator = CalculationNode().store()
        data_one = Data()
        data_two = Data()

        # Verify that adding two create links with the same link label but to different target is allowed from the
        # perspective of the source node (the CalculationNode in this case)
        data_one.add_incoming(creator,
                              link_type=LinkType.CREATE,
                              link_label='create')
        data_two.add_incoming(creator,
                              link_type=LinkType.CREATE,
                              link_label='create')
        data_one.store()
        data_two.store()

        uuids_outgoing = set(node.uuid
                             for node in creator.get_outgoing().all_nodes())
        uuids_expected = set([data_one.uuid, data_two.uuid])
        self.assertEqual(uuids_outgoing, uuids_expected)
Beispiel #2
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]))
Beispiel #3
0
    def test_delete_collection_outgoing_link(self):
        """Test deletion through objects collection raises when there are outgoing links."""
        calculation = CalculationNode().store()
        data = Data()
        data.add_incoming(calculation, LinkType.CREATE, 'output')
        data.store()

        with pytest.raises(exceptions.InvalidOperation):
            Node.objects.delete(calculation.pk)
Beispiel #4
0
    def test_validate_outgoing_workflow(self):
        """Verify that attaching an unstored `Data` node with `RETURN` link from a `WorkflowNode` raises.

        This would for example be the case if a user inside a workfunction or work chain creates a new node based on its
        inputs or the outputs returned by another process and tries to attach it as an output. This would the provenance
        of that data node to be lost and should be explicitly forbidden by raising.
        """
        source = WorkflowNode()
        target = Data()

        with self.assertRaises(ValueError):
            target.add_incoming(source, LinkType.RETURN, 'link_label')
Beispiel #5
0
    def test_add_incoming_return(self):
        """Nodes can have an infinite amount of incoming RETURN links, as long as the link triple is unique."""
        source_one = WorkflowNode()
        source_two = WorkflowNode()
        target = Data().store()  # Needs to be stored: see `test_validate_outgoing_workflow`

        target.add_incoming(source_one, LinkType.RETURN, 'link_label')

        # Can only have a single incoming RETURN link from each source node if the label is not unique
        with self.assertRaises(ValueError):
            target.validate_incoming(source_one, LinkType.RETURN, 'link_label')

        # From another source node or using another label is fine
        target.validate_incoming(source_one, LinkType.RETURN, 'other_label')
        target.validate_incoming(source_two, LinkType.RETURN, 'link_label')
Beispiel #6
0
    def test_detect_invalid_links_calculation_return(self):
        """Test `verdi database integrity detect-invalid-links` outgoing `return` from `calculation`."""
        result = self.cli_runner.invoke(cmd_database.detect_invalid_links, [])
        self.assertEqual(result.exit_code, 0)
        self.assertClickResultNoException(result)

        # Create an invalid link: outgoing `return` from a calculation
        data = Data().store().backend_entity
        calculation = CalculationNode().store().backend_entity

        data.add_incoming(calculation, link_type=LinkType.RETURN, link_label='return')

        result = self.cli_runner.invoke(cmd_database.detect_invalid_links, [])
        self.assertNotEqual(result.exit_code, 0)
        self.assertIsNotNone(result.exception)
Beispiel #7
0
    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)
Beispiel #8
0
    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)
Beispiel #9
0
    def test_node_indegree_unique_triple(self):
        """Test that the validation of links with indegree `unique_triple` works correctly

        The example here is a `DataNode` that has two incoming RETURN links with the same label, but from different
        source nodes. This is legal and should pass validation.
        """
        return_one = WorkflowNode()
        return_two = WorkflowNode()
        data = Data().store()  # Needs to be stored: see `test_validate_outgoing_workflow`

        # Verify that adding two return links with the same link label but from different source is allowed
        data.add_incoming(return_one, link_type=LinkType.RETURN, link_label='returned')
        data.add_incoming(return_two, link_type=LinkType.RETURN, link_label='returned')

        uuids_incoming = set(node.uuid for node in data.get_incoming().all_nodes())
        uuids_expected = set([return_one.uuid, return_two.uuid])
        self.assertEqual(uuids_incoming, uuids_expected)
Beispiel #10
0
    def test_add_incoming_create(self):
        """Nodes can only have a single incoming CREATE link, independent of the source node."""
        source_one = CalculationNode()
        source_two = CalculationNode()
        target = Data()

        target.add_incoming(source_one, LinkType.CREATE, 'link_label')

        # Can only have a single incoming CREATE link
        with self.assertRaises(ValueError):
            target.validate_incoming(source_one, LinkType.CREATE, 'link_label')

        # Even when the source node is different
        with self.assertRaises(ValueError):
            target.validate_incoming(source_two, LinkType.CREATE, 'link_label')

        # Or when the link label is different
        with self.assertRaises(ValueError):
            target.validate_incoming(source_one, LinkType.CREATE, 'other_label')
Beispiel #11
0
    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)
Beispiel #12
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)
Beispiel #13
0
    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