class TestGraphIteration(UnittestPythonCompatibility):
    """
    Test methods for iteration over nodes and edges in a graph
    """

    def setUp(self):
        """
        Build default Graph with node and edge attributes
        """

        self.graph = Graph()
        self.graph.add_nodes([('g', {'weight': 1.0, 'value': 'gr'}), ('r', {'weight': 1.5, 'value': 'ra'}),
                              ('a', {'weight': 2.0, 'value': 'ap'}), ('p', {'weight': 2.5, 'value': 'ph'}),
                              ('h', {'weight': 3.0})])
        self.graph.add_edges([(1, 2), (2, 3), (3, 4), (3, 5), (4, 5)], value=True, weight=43.2, key='edge')

    def test_magic_method_eq(self):
        """
        Test Graph equality __eq__ (==) test
        """

        self.assertTrue(self.graph == self.graph)
        self.assertTrue(self.graph.getnodes([1, 3, 4]) == self.graph.getnodes([1, 3, 4]))
        self.assertTrue(self.graph == self.graph.copy(deep=True))

        self.assertFalse(self.graph.getnodes([1, 2]) == self.graph.getnodes([2, 3]))
        self.assertFalse(self.graph.getedges([(1, 2), (2, 3)]) == self.graph.getnodes([2, 3]))

    def test_magic_method_add(self):
        """
        Test Graph addition __add__ (+) support
        """

        # Adding self to self does not change anything
        self.assertEqual(self.graph + self.graph, self.graph)

        # Adding sub graphs together to yield the full graph only works if
        # there is an overlap in the graphs connecting them together. Without
        # the overlap the connecting edges are lost
        sub1 = self.graph.getnodes([1, 2, 3])
        sub2 = self.graph.getnodes([3, 4, 5])
        self.assertEqual(sub1 + sub2, self.graph)

        sub1 = self.graph.getnodes([1, 2, 3])
        sub2 = self.graph.getnodes([4, 5])
        self.assertNotEqual(sub1 + sub2, self.graph)

        # Sub graphs are still views on the origin
        combined = sub1  + sub2
        self.assertTrue(combined.nodes.is_view)
        self.assertTrue(combined.edges.is_view)
        self.assertEqual(id(combined.origin), id(self.graph.origin))

        # Adding graphs together that do not share a common origin
        sub1_copy = sub1.copy()
        combined = sub1_copy  + sub2
        self.assertFalse(combined.nodes.is_view)
        self.assertFalse(combined.edges.is_view)
        self.assertNotEqual(id(combined.origin), id(self.graph.origin))

    def test_magic_method_iadd(self):
        """
        Test Graph in place addition __iadd__ (+=) support
        """

        # Adding sub graphs together to yield the full graph only works if
        # there is an overlap in the graphs connecting them together. Without
        # the overlap the connecting edges are lost
        sub1 = self.graph.getnodes([1, 2, 3])
        sub2 = self.graph.getnodes([3, 4, 5])
        sub1 += sub2
        self.assertEqual(sub1, self.graph)

        sub1 = self.graph.getnodes([1, 2, 3])
        sub2 = self.graph.getnodes([4, 5])
        sub1 += sub2
        self.assertNotEqual(sub1, self.graph)

        # Sub graphs are still views on the origin
        self.assertTrue(sub1.nodes.is_view)
        self.assertTrue(sub1.edges.is_view)
        self.assertEqual(id(sub1.origin), id(self.graph.origin))

        # Adding graphs together that do not share a common origin
        sub1_copy = sub1.copy()
        sub1_copy += sub2
        self.assertFalse(sub1_copy.nodes.is_view)
        self.assertFalse(sub1_copy.edges.is_view)
        self.assertNotEqual(id(sub1_copy.origin), id(self.graph.origin))

    def test_magic_method_contains(self):
        """
        Test Graph contains __contains__ test
        """

        # Equal graphs also contain each other
        self.assertTrue(self.graph in self.graph)

        sub1 = self.graph.getnodes([1, 2, 3])
        sub2 = self.graph.getnodes([4, 5])
        self.assertTrue(sub1 in self.graph)
        self.assertFalse(sub2 in sub1)

    def test_magic_method_getitem(self):
        """
        Test Graph dictionary style __getitem__ item lookup
        """

        self.assertEqual(self.graph[2], self.graph.getnodes(2))
        self.assertEqual(self.graph[(2, 3)], self.graph.getedges((2, 3)))

        # Support for slicing
        self.assertEqual(self.graph[2:], self.graph.getnodes([2, 3, 4, 5]))
        self.assertEqual(self.graph[2:4], self.graph.getnodes([2, 3]))
        self.assertEqual(self.graph[1:5:2], self.graph.getnodes([1, 3]))
        self.assertTrue(self.graph[1:-1].empty())

    def test_magic_method_ge(self):
        """
        Test Graph greater-then or equal __ge__ (>=) to support
        """

        sub = self.graph.getnodes([2, 3, 4])

        self.assertTrue(self.graph >= sub)
        self.assertFalse(sub >= self.graph)
        self.assertTrue(sub >= sub)

    def test_magic_method_gt(self):
        """
        Test Graph greater-then __gt__ (>) support
        """

        sub = self.graph.getnodes([2, 3, 4])

        self.assertTrue(self.graph > sub)
        self.assertFalse(sub > self.graph)

    def test_magic_method_len(self):
        """
        Test Graph length __len__ support
        """

        self.assertEqual(len(self.graph), 5)

    def test_magic_method_le(self):
        """
        Test Graph less-then or equal __le__ (<=) to support
        """

        sub = self.graph.getnodes([2, 3, 4])

        self.assertTrue(sub <= self.graph)
        self.assertFalse(self.graph <= sub)
        self.assertTrue(sub <= sub)

    def test_magic_method_lt(self):
        """
        Test Graph greater-then __lt__ (<) support
        """

        sub = self.graph.getnodes([2, 3, 4])

        self.assertTrue(sub < self.graph)
        self.assertFalse(self.graph < sub)

    def test_magic_method_ne(self):
        """
        Test Graph equality __ne__ (!=) test
        """

        self.assertFalse(self.graph != self.graph)
        self.assertFalse(self.graph.getnodes([1, 3, 4]) != self.graph.getnodes([1, 3, 4]))
        self.assertFalse(self.graph != self.graph.copy(deep=True))

        self.assertTrue(self.graph.getnodes([1, 2]) != self.graph.getnodes([2, 3]))
        self.assertTrue(self.graph.getedges([(1, 2), (2, 3)]) != self.graph.getnodes([2, 3]))

    def test_magic_method_sub(self):
        """
        Test Graph subtract __sub__ (-) support
        """

        sub = self.graph.getnodes([2, 3, 4])
        self.assertEqual(self.graph - sub, self.graph.getnodes([1,5]))
        self.assertTrue(len(sub - self.graph) == 0)

    def test_magic_method_isub(self):
        """
        Test Graph inplace subtract __isub__ (-=) support
        """

        cp = self.graph.copy()
        self.graph -= self.graph.getnodes([2, 3, 4])
        self.assertEqual(self.graph, cp.getnodes([1,5]))
class TestGraphEdgeAttribute(UnittestPythonCompatibility):
    """
    Test methods to get and set edge attributes using an edge storage driver.
    `DictStorage` is the default driver tested here which happens to be the
    same as for nodes.
    """

    def setUp(self):
        """
        Build default Graph with node and edge attributes
        """

        self.graph = Graph()
        self.graph.add_nodes([('g', {'weight': 1.0, 'value': 'gr'}), ('r', {'weight': 1.5, 'value': 'ra'}),
                              ('a', {'weight': 2.0, 'value': 'ap'}), ('p', {'weight': 2.5, 'value': 'ph'}),
                              ('h', {'weight': 3.0})])
        self.graph.add_edges([(1, 2), (2, 3), (3, 4), (3, 5), (4, 5)], value=True, weight=43.2, key='edge')

    def test_graph_edge_attr_storeget(self):
        """
        Test getting edge attributes directly from the `edges` storage
        """

        self.assertEqual(self.graph.edges[(1, 2)]['value'], True)
        self.assertEqual(self.graph.edges[(1, 2)]['weight'], 43.2)

    def test_graph_edge_attr_storeset(self):
        """
        Test setting node attributes directly from the `edges` storage
        """

        self.graph.edges[(1, 2)]['weight'] = 5.0
        self.graph.edges[(2, 3)]['value'] = 'dd'

        self.assertEqual(self.graph.edges[(1, 2)]['weight'], 5.0)
        self.assertEqual(self.graph.edges[(2, 3)]['value'], 'dd')

    def test_graph_edge_attr_key_tag(self):
        """
        Test get attributes based on `key_tag`
        """

        self.assertEqual(self.graph.edges[(1, 2)][self.graph.data.key_tag], 'edge')
        self.assertEqual(self.graph.get((1, 2)), 'edge')  # uses default node data tag

    def test_graph_edge_attr_value_tag(self):
        """
        Test get attributes based on `value_tag`
        """

        self.assertEqual(self.graph.edges[(4, 5)][self.graph.data.value_tag], True)

    def test_graph_edge_attr_dict(self):
        """
        Test if the returned full attribute dictionary is of expected format
        """

        self.assertDictEqual(self.graph.edges[(4, 5)], {'value': True, 'weight': 43.2, 'key': 'edge'})

    def test_graph_edge_attr_exception(self):
        """
        Test `edges` exception if edge not present
        """

        self.assertRaises(GraphitException, self.graph.__getitem__, (5, 6))
        self.assertIsNone(self.graph.nodes.get((5, 6)))

    def test_graph_edge_attr_graphget(self):
        """
        Test access edge attributes by nid using the (sub)graph 'get' method
        """

        self.assertEqual(self.graph.get((1, 2)), 'edge')
        self.assertEqual(self.graph.get((1, 2), 'weight'), 43.2)

        # Key does not exist
        self.assertIsNone(self.graph.get((1, 2), key='no_key'))

        # Key does not exist return defaultkey
        self.assertEqual(self.graph.get((1, 2), key='no_key', defaultattr='weight'), 43.2)

    def test_graph_edge_attr_singleedge_set(self):
        """
        Test setting edge attribute values directly using the single edge
        Graph API which has the required methods (edge_tools) added to it.
        """

        edge = self.graph.getedges((1, 2), directed=True)
        edge.weight = 4.5
        edge['key'] = 'edge_set'
        edge.set('value', False)

        self.assertEqual(edge.edges[(1, 2)]['weight'], 4.5)
        self.assertEqual(edge.edges[(1, 2)]['key'], 'edge_set')
        self.assertEqual(edge.edges[(1, 2)]['value'], False)

    def test_graph_edge_attr_singleedge_exception(self):
        """
        Test exceptions in direct access to edge attributes in a single graph
        class
        """

        edge = self.graph.getedges((1, 2), directed=True)

        self.assertEqual(edge.get(), True)
        self.assertRaises(KeyError, edge.__getitem__, 'no_key')
        self.assertRaises(AttributeError, edge.__getattr__, 'no_key')

    def test_graph_edge_attr_undirectional(self):
        """
        Undirectional edge has one attribute store
        """

        # True for DictStorage but may be different for other drivers
        self.assertEqual(id(self.graph.edges[(1, 2)]), id(self.graph.edges[(2, 1)]))

        self.assertDictEqual(self.graph.edges[(1, 2)], self.graph.edges[(2, 1)])

        self.graph.edges[(1, 2)]['key'] = 'edge_modified'
        self.assertTrue(self.graph.edges[(2, 1)]['key'] == 'edge_modified')

    def test_graph_edge_attr_directional(self):
        """
        Directional edge has two separated attribute stores
        """

        self.graph.add_edge(2, 5, directed=True, attr='to')
        self.graph.add_edge(5, 2, directed=True, attr='from')

        # True for DictStorage but may be different for other drivers
        self.assertNotEqual(id(self.graph.edges[(2, 5)]), id(self.graph.edges[(5, 2)]))

        self.assertNotEqual(self.graph.edges[(2, 5)], self.graph.edges[(5, 2)])

        self.graph.edges[(5, 2)]['attr'] = 'return'
        self.assertTrue(self.graph.edges[(2, 5)]['attr'] == 'to')
        self.assertTrue(self.graph.edges[(5, 2)]['attr'] == 'return')
class TestGraphAddNodeConnected(UnittestPythonCompatibility):
    """
    Test add_connect method for direct addition and edge connection of a new
    node to an existing node using a single node object
    """
    currpath = os.path.dirname(__file__)

    def setUp(self):
        """
        Build empty graph to add a node to and test default state
        """

        self.graph = Graph(auto_nid=False)

        # Add two nodes
        self.graph.add_nodes(('one', 'two'))
        self.graph.add_edge('one', 'two')

        # Two nodes and one edge
        self.assertTrue(len(self.graph) == 2)
        self.assertTrue(len(self.graph.nodes) == 2)
        self.assertTrue(len(self.graph.edges) == 2)
        self.assertTrue(len(self.graph.adjacency) == 2)

        # auto_nid
        self.assertFalse(self.graph.data.auto_nid)

    def tearDown(self):
        """
        Test state after node addition
        """

        nids = sorted(self.graph.nodes)

        # The nid should equal the node
        self.assertTrue(self.node in nids)

        # The _id is still set
        self.assertEqual(self.graph.nodes[self.node]['_id'], 3)
        self.assertEqual(self.graph.data.nodeid, 4)

        # filled after addition
        self.assertTrue(len(self.graph) == 3)
        self.assertTrue(len(self.graph.nodes) == 3)
        self.assertTrue(len(self.graph.edges) == 4)
        self.assertTrue(len(self.graph.adjacency) == 3)

        # no adjacency
        self.assertTrue(len(self.graph.adjacency[nids[0]]) == 1)

    def test_add_connect_string(self):
        """
        Test add connect a string node
        """

        self.node = 'three'

        node = self.graph.getnodes('two')
        node.add_connect(self.node)

        self.assertTrue(('two', 'three') in self.graph.edges)
        self.assertTrue(('three', 'two') in self.graph.edges)

    def test_add_connect_attr(self):
        """
        Test add connect a node with node and edge attributes
        """

        self.node = 'three'

        node = self.graph.getnodes('two')
        node.add_connect(self.node,
                         node_kwargs={
                             'arg': True,
                             'n': 1.22
                         },
                         edge_kwargs={
                             'arg': True,
                             'e': 5.44
                         })

        # Node should contain keyword arguments
        self.assertTrue(self.graph.nodes[self.node]['arg'])
        self.assertTrue(self.graph.nodes[self.node]['n'] == 1.22)

        # Edges should contain keyword arguments
        edge = self.graph.getedges(('two', self.node))
        self.assertTrue(all(e.get('arg', False) for e in edge.edges.values()))
        self.assertTrue(all(e.get('e') == 5.44 for e in edge.edges.values()))