Skip to content

Commit 96a2341

Browse files
authored
Make the output of the dot format deterministic and stable (#189)
Fixes #188
1 parent 0030704 commit 96a2341

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

src/pipdeptree/__init__.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,11 @@ def dump_graphviz(tree, output_format="dot", is_reverse=False):
612612

613613
# Allow output of dot format, even if GraphViz isn't installed.
614614
if output_format == "dot":
615-
return graph.source
615+
# Emulates graphviz.dot.Dot.__iter__() to force the sorting of graph.body.
616+
# Fixes https://github.com/tox-dev/pipdeptree/issues/188
617+
# That way we can guarantee the output of the dot format is deterministic
618+
# and stable.
619+
return "".join([tuple(graph)[0]] + sorted(graph.body) + [graph._tail])
616620

617621
# As it's unknown if the selected output format is binary or not, try to
618622
# decode it as UTF8 and only print it out in binary if that's not possible.

tests/test_pipdeptree.py

+48
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import platform
2+
import random
23
import subprocess
34
import sys
45
from contextlib import contextmanager
56
from itertools import chain
67
from pathlib import Path
78
from tempfile import NamedTemporaryFile
9+
from textwrap import dedent
810

911
try:
1012
from unittest import mock
@@ -304,6 +306,52 @@ def test_render_text(capsys, list_all, reverse, expected_output):
304306
# Tests for graph outputs
305307

306308

309+
def test_render_dot(capsys):
310+
# Extract the dependency graph from the package tree fixture and randomize it.
311+
randomized_graph = {}
312+
randomized_nodes = list(t._obj.keys())
313+
random.shuffle(randomized_nodes)
314+
for node in randomized_nodes:
315+
edges = t._obj[node]
316+
random.shuffle(edges)
317+
randomized_graph[node] = edges
318+
assert set(randomized_graph) == set(t._obj)
319+
320+
# Create a randomized package tree.
321+
randomized_dag = p.PackageDAG(randomized_graph)
322+
assert len(t) == len(randomized_dag)
323+
324+
# Check both the sorted and randomized package tree produces the same sorted
325+
# graphviz output.
326+
for package_tree in (t, randomized_dag):
327+
output = p.dump_graphviz(package_tree, output_format="dot")
328+
p.print_graphviz(output)
329+
out, _ = capsys.readouterr()
330+
assert out == dedent(
331+
"""\
332+
digraph {
333+
\ta -> b [label=">=2.0.0"]
334+
\ta -> c [label=">=5.7.1"]
335+
\ta [label="a\\n3.4.0"]
336+
\tb -> d [label=">=2.30,<2.42"]
337+
\tb [label="b\\n2.3.1"]
338+
\tc -> d [label=">=2.30"]
339+
\tc -> e [label=">=0.12.1"]
340+
\tc [label="c\\n5.10.0"]
341+
\td -> e [label=">=0.9.0"]
342+
\td [label="d\\n2.35"]
343+
\te [label="e\\n0.12.1"]
344+
\tf -> b [label=">=2.1.0"]
345+
\tf [label="f\\n3.1"]
346+
\tg -> e [label=">=0.9.0"]
347+
\tg -> f [label=">=3.0.0"]
348+
\tg [label="g\\n6.8.3rc1"]
349+
}
350+
351+
"""
352+
)
353+
354+
307355
def test_render_pdf():
308356
output = p.dump_graphviz(t, output_format="pdf")
309357

0 commit comments

Comments
 (0)