test_pydot.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. """Unit tests for pydot drawing functions."""
  2. from io import StringIO
  3. import pytest
  4. import networkx as nx
  5. from networkx.utils import graphs_equal
  6. pydot = pytest.importorskip("pydot")
  7. class TestPydot:
  8. @pytest.mark.parametrize("G", (nx.Graph(), nx.DiGraph()))
  9. @pytest.mark.parametrize("prog", ("neato", "dot"))
  10. def test_pydot(self, G, prog, tmp_path):
  11. """
  12. Validate :mod:`pydot`-based usage of the passed NetworkX graph with the
  13. passed basename of an external GraphViz command (e.g., `dot`, `neato`).
  14. """
  15. # Set the name of this graph to... "G". Failing to do so will
  16. # subsequently trip an assertion expecting this name.
  17. G.graph["name"] = "G"
  18. # Add arbitrary nodes and edges to the passed empty graph.
  19. G.add_edges_from([("A", "B"), ("A", "C"), ("B", "C"), ("A", "D")])
  20. G.add_node("E")
  21. # Validate layout of this graph with the passed GraphViz command.
  22. graph_layout = nx.nx_pydot.pydot_layout(G, prog=prog)
  23. assert isinstance(graph_layout, dict)
  24. # Convert this graph into a "pydot.Dot" instance.
  25. P = nx.nx_pydot.to_pydot(G)
  26. # Convert this "pydot.Dot" instance back into a graph of the same type.
  27. G2 = G.__class__(nx.nx_pydot.from_pydot(P))
  28. # Validate the original and resulting graphs to be the same.
  29. assert graphs_equal(G, G2)
  30. fname = tmp_path / "out.dot"
  31. # Serialize this "pydot.Dot" instance to a temporary file in dot format
  32. P.write_raw(fname)
  33. # Deserialize a list of new "pydot.Dot" instances back from this file.
  34. Pin_list = pydot.graph_from_dot_file(path=fname, encoding="utf-8")
  35. # Validate this file to contain only one graph.
  36. assert len(Pin_list) == 1
  37. # The single "pydot.Dot" instance deserialized from this file.
  38. Pin = Pin_list[0]
  39. # Sorted list of all nodes in the original "pydot.Dot" instance.
  40. n1 = sorted(p.get_name() for p in P.get_node_list())
  41. # Sorted list of all nodes in the deserialized "pydot.Dot" instance.
  42. n2 = sorted(p.get_name() for p in Pin.get_node_list())
  43. # Validate these instances to contain the same nodes.
  44. assert n1 == n2
  45. # Sorted list of all edges in the original "pydot.Dot" instance.
  46. e1 = sorted((e.get_source(), e.get_destination()) for e in P.get_edge_list())
  47. # Sorted list of all edges in the original "pydot.Dot" instance.
  48. e2 = sorted((e.get_source(), e.get_destination()) for e in Pin.get_edge_list())
  49. # Validate these instances to contain the same edges.
  50. assert e1 == e2
  51. # Deserialize a new graph of the same type back from this file.
  52. Hin = nx.nx_pydot.read_dot(fname)
  53. Hin = G.__class__(Hin)
  54. # Validate the original and resulting graphs to be the same.
  55. assert graphs_equal(G, Hin)
  56. def test_read_write(self):
  57. G = nx.MultiGraph()
  58. G.graph["name"] = "G"
  59. G.add_edge("1", "2", key="0") # read assumes strings
  60. fh = StringIO()
  61. nx.nx_pydot.write_dot(G, fh)
  62. fh.seek(0)
  63. H = nx.nx_pydot.read_dot(fh)
  64. assert graphs_equal(G, H)
  65. def test_pydot_issue_7581(tmp_path):
  66. """Validate that `nx_pydot.pydot_layout` handles nodes
  67. with characters like "\n", " ".
  68. Those characters cause `pydot` to escape and quote them on output,
  69. which caused #7581.
  70. """
  71. G = nx.Graph()
  72. G.add_edges_from([("A\nbig test", "B"), ("A\nbig test", "C"), ("B", "C")])
  73. graph_layout = nx.nx_pydot.pydot_layout(G, prog="dot")
  74. assert isinstance(graph_layout, dict)
  75. # Convert the graph to pydot and back into a graph. There should be no difference.
  76. P = nx.nx_pydot.to_pydot(G)
  77. G2 = nx.Graph(nx.nx_pydot.from_pydot(P))
  78. assert graphs_equal(G, G2)
  79. @pytest.mark.parametrize(
  80. "graph_type", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
  81. )
  82. def test_hashable_pydot(graph_type):
  83. # gh-5790
  84. G = graph_type()
  85. G.add_edge("5", frozenset([1]), t='"Example:A"', l=False)
  86. G.add_edge("1", 2, w=True, t=("node1",), l=frozenset(["node1"]))
  87. G.add_edge("node", (3, 3), w="string")
  88. assert [
  89. {"t": '"Example:A"', "l": "False"},
  90. {"w": "True", "t": "('node1',)", "l": "frozenset({'node1'})"},
  91. {"w": "string"},
  92. ] == [
  93. attr
  94. for _, _, attr in nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G)).edges.data()
  95. ]
  96. assert {str(i) for i in G.nodes()} == set(
  97. nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G)).nodes
  98. )
  99. def test_pydot_numerical_name():
  100. G = nx.Graph()
  101. G.add_edges_from([("A", "B"), (0, 1)])
  102. graph_layout = nx.nx_pydot.pydot_layout(G, prog="dot")
  103. assert isinstance(graph_layout, dict)
  104. assert "0" not in graph_layout
  105. assert 0 in graph_layout
  106. assert "1" not in graph_layout
  107. assert 1 in graph_layout
  108. assert "A" in graph_layout
  109. assert "B" in graph_layout