250 lines
8.9 KiB
Python
250 lines
8.9 KiB
Python
|
"""Unit tests for PyGraphviz interface."""
|
||
|
import os
|
||
|
import tempfile
|
||
|
import pytest
|
||
|
|
||
|
pygraphviz = pytest.importorskip("pygraphviz")
|
||
|
|
||
|
|
||
|
from networkx.testing import assert_edges_equal, assert_nodes_equal, assert_graphs_equal
|
||
|
|
||
|
import networkx as nx
|
||
|
|
||
|
|
||
|
class TestAGraph:
|
||
|
def build_graph(self, G):
|
||
|
edges = [("A", "B"), ("A", "C"), ("A", "C"), ("B", "C"), ("A", "D")]
|
||
|
G.add_edges_from(edges)
|
||
|
G.add_node("E")
|
||
|
G.graph["metal"] = "bronze"
|
||
|
return G
|
||
|
|
||
|
def assert_equal(self, G1, G2):
|
||
|
assert_nodes_equal(G1.nodes(), G2.nodes())
|
||
|
assert_edges_equal(G1.edges(), G2.edges())
|
||
|
assert G1.graph["metal"] == G2.graph["metal"]
|
||
|
|
||
|
def agraph_checks(self, G):
|
||
|
G = self.build_graph(G)
|
||
|
A = nx.nx_agraph.to_agraph(G)
|
||
|
H = nx.nx_agraph.from_agraph(A)
|
||
|
self.assert_equal(G, H)
|
||
|
|
||
|
fname = tempfile.mktemp()
|
||
|
nx.drawing.nx_agraph.write_dot(H, fname)
|
||
|
Hin = nx.nx_agraph.read_dot(fname)
|
||
|
os.unlink(fname)
|
||
|
self.assert_equal(H, Hin)
|
||
|
|
||
|
(fd, fname) = tempfile.mkstemp()
|
||
|
with open(fname, "w") as fh:
|
||
|
nx.drawing.nx_agraph.write_dot(H, fh)
|
||
|
|
||
|
with open(fname) as fh:
|
||
|
Hin = nx.nx_agraph.read_dot(fh)
|
||
|
os.unlink(fname)
|
||
|
self.assert_equal(H, Hin)
|
||
|
|
||
|
def test_from_agraph_name(self):
|
||
|
G = nx.Graph(name="test")
|
||
|
A = nx.nx_agraph.to_agraph(G)
|
||
|
H = nx.nx_agraph.from_agraph(A)
|
||
|
assert G.name == "test"
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"graph_class", (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
|
||
|
)
|
||
|
def test_from_agraph_create_using(self, graph_class):
|
||
|
G = nx.path_graph(3)
|
||
|
A = nx.nx_agraph.to_agraph(G)
|
||
|
H = nx.nx_agraph.from_agraph(A, create_using=graph_class)
|
||
|
assert isinstance(H, graph_class)
|
||
|
|
||
|
def test_from_agraph_named_edges(self):
|
||
|
# Create an AGraph from an existing (non-multi) Graph
|
||
|
G = nx.Graph()
|
||
|
G.add_nodes_from([0, 1])
|
||
|
A = nx.nx_agraph.to_agraph(G)
|
||
|
# Add edge (+ name, given by key) to the AGraph
|
||
|
A.add_edge(0, 1, key="foo")
|
||
|
# Verify a.name roundtrips out to 'key' in from_agraph
|
||
|
H = nx.nx_agraph.from_agraph(A)
|
||
|
assert isinstance(H, nx.Graph)
|
||
|
assert ("0", "1", {"key": "foo"}) in H.edges(data=True)
|
||
|
|
||
|
def test_undirected(self):
|
||
|
self.agraph_checks(nx.Graph())
|
||
|
|
||
|
def test_directed(self):
|
||
|
self.agraph_checks(nx.DiGraph())
|
||
|
|
||
|
def test_multi_undirected(self):
|
||
|
self.agraph_checks(nx.MultiGraph())
|
||
|
|
||
|
def test_multi_directed(self):
|
||
|
self.agraph_checks(nx.MultiDiGraph())
|
||
|
|
||
|
def test_to_agraph_with_nodedata(self):
|
||
|
G = nx.Graph()
|
||
|
G.add_node(1, color="red")
|
||
|
A = nx.nx_agraph.to_agraph(G)
|
||
|
assert dict(A.nodes()[0].attr) == {"color": "red"}
|
||
|
|
||
|
@pytest.mark.parametrize("graph_class", (nx.Graph, nx.MultiGraph))
|
||
|
def test_to_agraph_with_edgedata(self, graph_class):
|
||
|
G = graph_class()
|
||
|
G.add_nodes_from([0, 1])
|
||
|
G.add_edge(0, 1, color="yellow")
|
||
|
A = nx.nx_agraph.to_agraph(G)
|
||
|
assert dict(A.edges()[0].attr) == {"color": "yellow"}
|
||
|
|
||
|
def test_view_pygraphviz_path(self, tmp_path):
|
||
|
G = nx.complete_graph(3)
|
||
|
input_path = str(tmp_path / "graph.png")
|
||
|
out_path, A = nx.nx_agraph.view_pygraphviz(G, path=input_path, show=False)
|
||
|
assert out_path == input_path
|
||
|
# Ensure file is not empty
|
||
|
with open(input_path, "rb") as fh:
|
||
|
data = fh.read()
|
||
|
assert len(data) > 0
|
||
|
|
||
|
def test_view_pygraphviz_file_suffix(self, tmp_path):
|
||
|
G = nx.complete_graph(3)
|
||
|
path, A = nx.nx_agraph.view_pygraphviz(G, suffix=1, show=False)
|
||
|
assert path[-6:] == "_1.png"
|
||
|
|
||
|
def test_view_pygraphviz(self):
|
||
|
G = nx.Graph() # "An empty graph cannot be drawn."
|
||
|
pytest.raises(nx.NetworkXException, nx.nx_agraph.view_pygraphviz, G)
|
||
|
G = nx.barbell_graph(4, 6)
|
||
|
nx.nx_agraph.view_pygraphviz(G, show=False)
|
||
|
|
||
|
def test_view_pygraphviz_edgelabel(self):
|
||
|
G = nx.Graph()
|
||
|
G.add_edge(1, 2, weight=7)
|
||
|
G.add_edge(2, 3, weight=8)
|
||
|
path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel="weight", show=False)
|
||
|
for edge in A.edges():
|
||
|
assert edge.attr["weight"] in ("7", "8")
|
||
|
|
||
|
def test_view_pygraphviz_callable_edgelabel(self):
|
||
|
G = nx.complete_graph(3)
|
||
|
|
||
|
def foo_label(data):
|
||
|
return "foo"
|
||
|
|
||
|
path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel=foo_label, show=False)
|
||
|
for edge in A.edges():
|
||
|
assert edge.attr["label"] == "foo"
|
||
|
|
||
|
def test_view_pygraphviz_multigraph_edgelabels(self):
|
||
|
G = nx.MultiGraph()
|
||
|
G.add_edge(0, 1, key=0, name="left_fork")
|
||
|
G.add_edge(0, 1, key=1, name="right_fork")
|
||
|
path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel="name", show=False)
|
||
|
edges = A.edges()
|
||
|
assert len(edges) == 2
|
||
|
for edge in edges:
|
||
|
assert edge.attr["label"].strip() in ("left_fork", "right_fork")
|
||
|
|
||
|
def test_graph_with_reserved_keywords(self):
|
||
|
# test attribute/keyword clash case for #1582
|
||
|
# node: n
|
||
|
# edges: u,v
|
||
|
G = nx.Graph()
|
||
|
G = self.build_graph(G)
|
||
|
G.nodes["E"]["n"] = "keyword"
|
||
|
G.edges[("A", "B")]["u"] = "keyword"
|
||
|
G.edges[("A", "B")]["v"] = "keyword"
|
||
|
A = nx.nx_agraph.to_agraph(G)
|
||
|
|
||
|
def test_view_pygraphviz_no_added_attrs_to_input(self):
|
||
|
G = nx.complete_graph(2)
|
||
|
path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
|
||
|
assert G.graph == {}
|
||
|
|
||
|
@pytest.mark.xfail(reason="known bug in clean_attrs")
|
||
|
def test_view_pygraphviz_leaves_input_graph_unmodified(self):
|
||
|
G = nx.complete_graph(2)
|
||
|
# Add entries to graph dict that to_agraph handles specially
|
||
|
G.graph["node"] = {"width": "0.80"}
|
||
|
G.graph["edge"] = {"fontsize": "14"}
|
||
|
path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
|
||
|
assert G.graph == {"node": {"width": "0.80"}, "edge": {"fontsize": "14"}}
|
||
|
|
||
|
def test_graph_with_AGraph_attrs(self):
|
||
|
G = nx.complete_graph(2)
|
||
|
# Add entries to graph dict that to_agraph handles specially
|
||
|
G.graph["node"] = {"width": "0.80"}
|
||
|
G.graph["edge"] = {"fontsize": "14"}
|
||
|
path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
|
||
|
# Ensure user-specified values are not lost
|
||
|
assert dict(A.node_attr)["width"] == "0.80"
|
||
|
assert dict(A.edge_attr)["fontsize"] == "14"
|
||
|
|
||
|
def test_round_trip_empty_graph(self):
|
||
|
G = nx.Graph()
|
||
|
A = nx.nx_agraph.to_agraph(G)
|
||
|
H = nx.nx_agraph.from_agraph(A)
|
||
|
# assert_graphs_equal(G, H)
|
||
|
AA = nx.nx_agraph.to_agraph(H)
|
||
|
HH = nx.nx_agraph.from_agraph(AA)
|
||
|
assert_graphs_equal(H, HH)
|
||
|
G.graph["graph"] = {}
|
||
|
G.graph["node"] = {}
|
||
|
G.graph["edge"] = {}
|
||
|
assert_graphs_equal(G, HH)
|
||
|
|
||
|
@pytest.mark.xfail(reason="integer->string node conversion in round trip")
|
||
|
def test_round_trip_integer_nodes(self):
|
||
|
G = nx.complete_graph(3)
|
||
|
A = nx.nx_agraph.to_agraph(G)
|
||
|
H = nx.nx_agraph.from_agraph(A)
|
||
|
assert_graphs_equal(G, H)
|
||
|
|
||
|
def test_graphviz_alias(self):
|
||
|
G = self.build_graph(nx.Graph())
|
||
|
pos_graphviz = nx.nx_agraph.graphviz_layout(G)
|
||
|
pos_pygraphviz = nx.nx_agraph.pygraphviz_layout(G)
|
||
|
assert pos_graphviz == pos_pygraphviz
|
||
|
|
||
|
@pytest.mark.parametrize("root", range(5))
|
||
|
def test_pygraphviz_layout_root(self, root):
|
||
|
# NOTE: test depends on layout prog being deterministic
|
||
|
G = nx.complete_graph(5)
|
||
|
A = nx.nx_agraph.to_agraph(G)
|
||
|
# Get layout with root arg is not None
|
||
|
pygv_layout = nx.nx_agraph.pygraphviz_layout(G, prog="circo", root=root)
|
||
|
# Equivalent layout directly on AGraph
|
||
|
A.layout(args=f"-Groot={root}", prog="circo")
|
||
|
# Parse AGraph layout
|
||
|
a1_pos = tuple(float(v) for v in dict(A.get_node("1").attr)["pos"].split(","))
|
||
|
assert pygv_layout[1] == a1_pos
|
||
|
|
||
|
def test_2d_layout(self):
|
||
|
G = nx.Graph()
|
||
|
G = self.build_graph(G)
|
||
|
G.graph["dimen"] = 2
|
||
|
pos = nx.nx_agraph.pygraphviz_layout(G, prog="neato")
|
||
|
pos = list(pos.values())
|
||
|
assert len(pos) == 5
|
||
|
assert len(pos[0]) == 2
|
||
|
|
||
|
def test_3d_layout(self):
|
||
|
G = nx.Graph()
|
||
|
G = self.build_graph(G)
|
||
|
G.graph["dimen"] = 3
|
||
|
pos = nx.nx_agraph.pygraphviz_layout(G, prog="neato")
|
||
|
pos = list(pos.values())
|
||
|
assert len(pos) == 5
|
||
|
assert len(pos[0]) == 3
|
||
|
|
||
|
def test_display_pygraphviz_deprecation_warning(self):
|
||
|
G = nx.complete_graph(2)
|
||
|
path_name, A = nx.nx_agraph.view_pygraphviz(G, show=False)
|
||
|
# Monkeypatch default_opener to prevent window opening
|
||
|
nx.utils.default_opener = lambda x: None
|
||
|
with pytest.warns(DeprecationWarning, match="display_pygraphviz is deprecated"):
|
||
|
with open(path_name, "wb") as fh:
|
||
|
nx.nx_agraph.display_pygraphviz(A, fh, prog="dot")
|