test_agraph.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. """Unit tests for PyGraphviz interface."""
  2. import os
  3. import tempfile
  4. import pytest
  5. pygraphviz = pytest.importorskip("pygraphviz")
  6. import networkx as nx
  7. from networkx.utils import edges_equal, graphs_equal, nodes_equal
  8. class TestAGraph:
  9. def build_graph(self, G):
  10. edges = [("A", "B"), ("A", "C"), ("A", "C"), ("B", "C"), ("A", "D")]
  11. G.add_edges_from(edges)
  12. G.add_node("E")
  13. G.graph["metal"] = "bronze"
  14. return G
  15. def assert_equal(self, G1, G2):
  16. assert nodes_equal(G1.nodes(), G2.nodes())
  17. assert edges_equal(G1.edges(), G2.edges())
  18. assert G1.graph["metal"] == G2.graph["metal"]
  19. def agraph_checks(self, G):
  20. G = self.build_graph(G)
  21. A = nx.nx_agraph.to_agraph(G)
  22. H = nx.nx_agraph.from_agraph(A)
  23. self.assert_equal(G, H)
  24. fd, fname = tempfile.mkstemp()
  25. nx.drawing.nx_agraph.write_dot(H, fname)
  26. Hin = nx.nx_agraph.read_dot(fname)
  27. self.assert_equal(H, Hin)
  28. os.close(fd)
  29. os.unlink(fname)
  30. (fd, fname) = tempfile.mkstemp()
  31. with open(fname, "w") as fh:
  32. nx.drawing.nx_agraph.write_dot(H, fh)
  33. with open(fname) as fh:
  34. Hin = nx.nx_agraph.read_dot(fh)
  35. os.close(fd)
  36. os.unlink(fname)
  37. self.assert_equal(H, Hin)
  38. def test_from_agraph_name(self):
  39. G = nx.Graph(name="test")
  40. A = nx.nx_agraph.to_agraph(G)
  41. H = nx.nx_agraph.from_agraph(A)
  42. assert G.name == "test"
  43. @pytest.mark.parametrize(
  44. "graph_class", (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
  45. )
  46. def test_from_agraph_create_using(self, graph_class):
  47. G = nx.path_graph(3)
  48. A = nx.nx_agraph.to_agraph(G)
  49. H = nx.nx_agraph.from_agraph(A, create_using=graph_class)
  50. assert isinstance(H, graph_class)
  51. def test_from_agraph_named_edges(self):
  52. # Create an AGraph from an existing (non-multi) Graph
  53. G = nx.Graph()
  54. G.add_nodes_from([0, 1])
  55. A = nx.nx_agraph.to_agraph(G)
  56. # Add edge (+ name, given by key) to the AGraph
  57. A.add_edge(0, 1, key="foo")
  58. # Verify a.name roundtrips out to 'key' in from_agraph
  59. H = nx.nx_agraph.from_agraph(A)
  60. assert isinstance(H, nx.Graph)
  61. assert ("0", "1", {"key": "foo"}) in H.edges(data=True)
  62. def test_undirected(self):
  63. self.agraph_checks(nx.Graph())
  64. def test_directed(self):
  65. self.agraph_checks(nx.DiGraph())
  66. def test_multi_undirected(self):
  67. self.agraph_checks(nx.MultiGraph())
  68. def test_multi_directed(self):
  69. self.agraph_checks(nx.MultiDiGraph())
  70. def test_to_agraph_with_nodedata(self):
  71. G = nx.Graph()
  72. G.add_node(1, color="red")
  73. A = nx.nx_agraph.to_agraph(G)
  74. assert dict(A.nodes()[0].attr) == {"color": "red"}
  75. @pytest.mark.parametrize("graph_class", (nx.Graph, nx.MultiGraph))
  76. def test_to_agraph_with_edgedata(self, graph_class):
  77. G = graph_class()
  78. G.add_nodes_from([0, 1])
  79. G.add_edge(0, 1, color="yellow")
  80. A = nx.nx_agraph.to_agraph(G)
  81. assert dict(A.edges()[0].attr) == {"color": "yellow"}
  82. def test_view_pygraphviz_path(self, tmp_path):
  83. G = nx.complete_graph(3)
  84. input_path = str(tmp_path / "graph.png")
  85. out_path, A = nx.nx_agraph.view_pygraphviz(G, path=input_path, show=False)
  86. assert out_path == input_path
  87. # Ensure file is not empty
  88. with open(input_path, "rb") as fh:
  89. data = fh.read()
  90. assert len(data) > 0
  91. def test_view_pygraphviz_file_suffix(self, tmp_path):
  92. G = nx.complete_graph(3)
  93. path, A = nx.nx_agraph.view_pygraphviz(G, suffix=1, show=False)
  94. assert path[-6:] == "_1.png"
  95. def test_view_pygraphviz(self):
  96. G = nx.Graph() # "An empty graph cannot be drawn."
  97. pytest.raises(nx.NetworkXException, nx.nx_agraph.view_pygraphviz, G)
  98. G = nx.barbell_graph(4, 6)
  99. nx.nx_agraph.view_pygraphviz(G, show=False)
  100. def test_view_pygraphviz_edgelabel(self):
  101. G = nx.Graph()
  102. G.add_edge(1, 2, weight=7)
  103. G.add_edge(2, 3, weight=8)
  104. path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel="weight", show=False)
  105. for edge in A.edges():
  106. assert edge.attr["weight"] in ("7", "8")
  107. def test_view_pygraphviz_callable_edgelabel(self):
  108. G = nx.complete_graph(3)
  109. def foo_label(data):
  110. return "foo"
  111. path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel=foo_label, show=False)
  112. for edge in A.edges():
  113. assert edge.attr["label"] == "foo"
  114. def test_view_pygraphviz_multigraph_edgelabels(self):
  115. G = nx.MultiGraph()
  116. G.add_edge(0, 1, key=0, name="left_fork")
  117. G.add_edge(0, 1, key=1, name="right_fork")
  118. path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel="name", show=False)
  119. edges = A.edges()
  120. assert len(edges) == 2
  121. for edge in edges:
  122. assert edge.attr["label"].strip() in ("left_fork", "right_fork")
  123. def test_graph_with_reserved_keywords(self):
  124. # test attribute/keyword clash case for #1582
  125. # node: n
  126. # edges: u,v
  127. G = nx.Graph()
  128. G = self.build_graph(G)
  129. G.nodes["E"]["n"] = "keyword"
  130. G.edges[("A", "B")]["u"] = "keyword"
  131. G.edges[("A", "B")]["v"] = "keyword"
  132. A = nx.nx_agraph.to_agraph(G)
  133. def test_view_pygraphviz_no_added_attrs_to_input(self):
  134. G = nx.complete_graph(2)
  135. path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
  136. assert G.graph == {}
  137. @pytest.mark.xfail(reason="known bug in clean_attrs")
  138. def test_view_pygraphviz_leaves_input_graph_unmodified(self):
  139. G = nx.complete_graph(2)
  140. # Add entries to graph dict that to_agraph handles specially
  141. G.graph["node"] = {"width": "0.80"}
  142. G.graph["edge"] = {"fontsize": "14"}
  143. path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
  144. assert G.graph == {"node": {"width": "0.80"}, "edge": {"fontsize": "14"}}
  145. def test_graph_with_AGraph_attrs(self):
  146. G = nx.complete_graph(2)
  147. # Add entries to graph dict that to_agraph handles specially
  148. G.graph["node"] = {"width": "0.80"}
  149. G.graph["edge"] = {"fontsize": "14"}
  150. path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
  151. # Ensure user-specified values are not lost
  152. assert dict(A.node_attr)["width"] == "0.80"
  153. assert dict(A.edge_attr)["fontsize"] == "14"
  154. def test_round_trip_empty_graph(self):
  155. G = nx.Graph()
  156. A = nx.nx_agraph.to_agraph(G)
  157. H = nx.nx_agraph.from_agraph(A)
  158. # assert graphs_equal(G, H)
  159. AA = nx.nx_agraph.to_agraph(H)
  160. HH = nx.nx_agraph.from_agraph(AA)
  161. assert graphs_equal(H, HH)
  162. G.graph["graph"] = {}
  163. G.graph["node"] = {}
  164. G.graph["edge"] = {}
  165. assert graphs_equal(G, HH)
  166. @pytest.mark.xfail(reason="integer->string node conversion in round trip")
  167. def test_round_trip_integer_nodes(self):
  168. G = nx.complete_graph(3)
  169. A = nx.nx_agraph.to_agraph(G)
  170. H = nx.nx_agraph.from_agraph(A)
  171. assert graphs_equal(G, H)
  172. def test_graphviz_alias(self):
  173. G = self.build_graph(nx.Graph())
  174. pos_graphviz = nx.nx_agraph.graphviz_layout(G)
  175. pos_pygraphviz = nx.nx_agraph.pygraphviz_layout(G)
  176. assert pos_graphviz == pos_pygraphviz
  177. @pytest.mark.parametrize("root", range(5))
  178. def test_pygraphviz_layout_root(self, root):
  179. # NOTE: test depends on layout prog being deterministic
  180. G = nx.complete_graph(5)
  181. A = nx.nx_agraph.to_agraph(G)
  182. # Get layout with root arg is not None
  183. pygv_layout = nx.nx_agraph.pygraphviz_layout(G, prog="circo", root=root)
  184. # Equivalent layout directly on AGraph
  185. A.layout(args=f"-Groot={root}", prog="circo")
  186. # Parse AGraph layout
  187. a1_pos = tuple(float(v) for v in dict(A.get_node("1").attr)["pos"].split(","))
  188. assert pygv_layout[1] == a1_pos
  189. def test_2d_layout(self):
  190. G = nx.Graph()
  191. G = self.build_graph(G)
  192. G.graph["dimen"] = 2
  193. pos = nx.nx_agraph.pygraphviz_layout(G, prog="neato")
  194. pos = list(pos.values())
  195. assert len(pos) == 5
  196. assert len(pos[0]) == 2
  197. def test_3d_layout(self):
  198. G = nx.Graph()
  199. G = self.build_graph(G)
  200. G.graph["dimen"] = 3
  201. pos = nx.nx_agraph.pygraphviz_layout(G, prog="neato")
  202. pos = list(pos.values())
  203. assert len(pos) == 5
  204. assert len(pos[0]) == 3