2013-05-29 16:47:10 +00:00
|
|
|
from django.test import TestCase
|
2014-09-25 22:24:17 +00:00
|
|
|
from django.db.migrations.graph import CircularDependencyError, MigrationGraph, NodeNotFoundError
|
2014-12-17 23:41:36 +00:00
|
|
|
from django.utils.encoding import force_text
|
2013-05-10 11:52:04 +00:00
|
|
|
|
|
|
|
|
2013-05-29 16:47:10 +00:00
|
|
|
class GraphTests(TestCase):
|
2013-05-10 11:52:04 +00:00
|
|
|
"""
|
|
|
|
Tests the digraph structure.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def test_simple_graph(self):
|
|
|
|
"""
|
|
|
|
Tests a basic dependency graph:
|
|
|
|
|
|
|
|
app_a: 0001 <-- 0002 <--- 0003 <-- 0004
|
|
|
|
/
|
|
|
|
app_b: 0001 <-- 0002 <-/
|
|
|
|
"""
|
|
|
|
# Build graph
|
2013-05-10 15:00:55 +00:00
|
|
|
graph = MigrationGraph()
|
2013-05-30 16:56:53 +00:00
|
|
|
graph.add_node(("app_a", "0001"), None)
|
|
|
|
graph.add_node(("app_a", "0002"), None)
|
|
|
|
graph.add_node(("app_a", "0003"), None)
|
|
|
|
graph.add_node(("app_a", "0004"), None)
|
|
|
|
graph.add_node(("app_b", "0001"), None)
|
|
|
|
graph.add_node(("app_b", "0002"), None)
|
2014-08-22 07:26:06 +00:00
|
|
|
graph.add_dependency("app_a.0004", ("app_a", "0004"), ("app_a", "0003"))
|
|
|
|
graph.add_dependency("app_a.0003", ("app_a", "0003"), ("app_a", "0002"))
|
|
|
|
graph.add_dependency("app_a.0002", ("app_a", "0002"), ("app_a", "0001"))
|
|
|
|
graph.add_dependency("app_a.0003", ("app_a", "0003"), ("app_b", "0002"))
|
|
|
|
graph.add_dependency("app_b.0002", ("app_b", "0002"), ("app_b", "0001"))
|
2013-05-10 11:52:04 +00:00
|
|
|
# Test root migration case
|
|
|
|
self.assertEqual(
|
|
|
|
graph.forwards_plan(("app_a", "0001")),
|
|
|
|
[('app_a', '0001')],
|
|
|
|
)
|
|
|
|
# Test branch B only
|
|
|
|
self.assertEqual(
|
|
|
|
graph.forwards_plan(("app_b", "0002")),
|
|
|
|
[("app_b", "0001"), ("app_b", "0002")],
|
|
|
|
)
|
|
|
|
# Test whole graph
|
|
|
|
self.assertEqual(
|
|
|
|
graph.forwards_plan(("app_a", "0004")),
|
|
|
|
[('app_b', '0001'), ('app_b', '0002'), ('app_a', '0001'), ('app_a', '0002'), ('app_a', '0003'), ('app_a', '0004')],
|
|
|
|
)
|
|
|
|
# Test reverse to b:0002
|
|
|
|
self.assertEqual(
|
|
|
|
graph.backwards_plan(("app_b", "0002")),
|
|
|
|
[('app_a', '0004'), ('app_a', '0003'), ('app_b', '0002')],
|
|
|
|
)
|
2013-05-10 15:09:57 +00:00
|
|
|
# Test roots and leaves
|
|
|
|
self.assertEqual(
|
|
|
|
graph.root_nodes(),
|
2014-06-18 06:27:03 +00:00
|
|
|
[('app_a', '0001'), ('app_b', '0001')],
|
2013-05-10 15:09:57 +00:00
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
graph.leaf_nodes(),
|
2014-06-18 06:27:03 +00:00
|
|
|
[('app_a', '0004'), ('app_b', '0002')],
|
2013-05-10 15:09:57 +00:00
|
|
|
)
|
2013-05-10 11:52:04 +00:00
|
|
|
|
|
|
|
def test_complex_graph(self):
|
|
|
|
"""
|
|
|
|
Tests a complex dependency graph:
|
|
|
|
|
|
|
|
app_a: 0001 <-- 0002 <--- 0003 <-- 0004
|
|
|
|
\ \ / /
|
|
|
|
app_b: 0001 <-\ 0002 <-X /
|
|
|
|
\ \ /
|
|
|
|
app_c: \ 0001 <-- 0002 <-
|
|
|
|
"""
|
|
|
|
# Build graph
|
2013-05-10 15:00:55 +00:00
|
|
|
graph = MigrationGraph()
|
2013-05-30 16:56:53 +00:00
|
|
|
graph.add_node(("app_a", "0001"), None)
|
|
|
|
graph.add_node(("app_a", "0002"), None)
|
|
|
|
graph.add_node(("app_a", "0003"), None)
|
|
|
|
graph.add_node(("app_a", "0004"), None)
|
|
|
|
graph.add_node(("app_b", "0001"), None)
|
|
|
|
graph.add_node(("app_b", "0002"), None)
|
|
|
|
graph.add_node(("app_c", "0001"), None)
|
|
|
|
graph.add_node(("app_c", "0002"), None)
|
2014-08-22 07:26:06 +00:00
|
|
|
graph.add_dependency("app_a.0004", ("app_a", "0004"), ("app_a", "0003"))
|
|
|
|
graph.add_dependency("app_a.0003", ("app_a", "0003"), ("app_a", "0002"))
|
|
|
|
graph.add_dependency("app_a.0002", ("app_a", "0002"), ("app_a", "0001"))
|
|
|
|
graph.add_dependency("app_a.0003", ("app_a", "0003"), ("app_b", "0002"))
|
|
|
|
graph.add_dependency("app_b.0002", ("app_b", "0002"), ("app_b", "0001"))
|
|
|
|
graph.add_dependency("app_a.0004", ("app_a", "0004"), ("app_c", "0002"))
|
|
|
|
graph.add_dependency("app_c.0002", ("app_c", "0002"), ("app_c", "0001"))
|
|
|
|
graph.add_dependency("app_c.0001", ("app_c", "0001"), ("app_b", "0001"))
|
|
|
|
graph.add_dependency("app_c.0002", ("app_c", "0002"), ("app_a", "0002"))
|
2013-05-10 11:52:04 +00:00
|
|
|
# Test branch C only
|
|
|
|
self.assertEqual(
|
|
|
|
graph.forwards_plan(("app_c", "0002")),
|
|
|
|
[('app_b', '0001'), ('app_c', '0001'), ('app_a', '0001'), ('app_a', '0002'), ('app_c', '0002')],
|
|
|
|
)
|
|
|
|
# Test whole graph
|
|
|
|
self.assertEqual(
|
|
|
|
graph.forwards_plan(("app_a", "0004")),
|
|
|
|
[('app_b', '0001'), ('app_c', '0001'), ('app_a', '0001'), ('app_a', '0002'), ('app_c', '0002'), ('app_b', '0002'), ('app_a', '0003'), ('app_a', '0004')],
|
|
|
|
)
|
|
|
|
# Test reverse to b:0001
|
|
|
|
self.assertEqual(
|
|
|
|
graph.backwards_plan(("app_b", "0001")),
|
|
|
|
[('app_a', '0004'), ('app_c', '0002'), ('app_c', '0001'), ('app_a', '0003'), ('app_b', '0002'), ('app_b', '0001')],
|
|
|
|
)
|
2013-05-10 15:09:57 +00:00
|
|
|
# Test roots and leaves
|
|
|
|
self.assertEqual(
|
|
|
|
graph.root_nodes(),
|
2014-06-18 06:27:03 +00:00
|
|
|
[('app_a', '0001'), ('app_b', '0001'), ('app_c', '0001')],
|
2013-05-10 15:09:57 +00:00
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
graph.leaf_nodes(),
|
2014-06-18 06:27:03 +00:00
|
|
|
[('app_a', '0004'), ('app_b', '0002'), ('app_c', '0002')],
|
2013-05-10 15:09:57 +00:00
|
|
|
)
|
2013-05-10 11:52:04 +00:00
|
|
|
|
|
|
|
def test_circular_graph(self):
|
|
|
|
"""
|
|
|
|
Tests a circular dependency graph.
|
|
|
|
"""
|
|
|
|
# Build graph
|
2013-05-10 15:00:55 +00:00
|
|
|
graph = MigrationGraph()
|
2013-05-30 16:56:53 +00:00
|
|
|
graph.add_node(("app_a", "0001"), None)
|
|
|
|
graph.add_node(("app_a", "0002"), None)
|
|
|
|
graph.add_node(("app_a", "0003"), None)
|
|
|
|
graph.add_node(("app_b", "0001"), None)
|
|
|
|
graph.add_node(("app_b", "0002"), None)
|
2014-08-22 07:26:06 +00:00
|
|
|
graph.add_dependency("app_a.0003", ("app_a", "0003"), ("app_a", "0002"))
|
|
|
|
graph.add_dependency("app_a.0002", ("app_a", "0002"), ("app_a", "0001"))
|
|
|
|
graph.add_dependency("app_a.0001", ("app_a", "0001"), ("app_b", "0002"))
|
|
|
|
graph.add_dependency("app_b.0002", ("app_b", "0002"), ("app_b", "0001"))
|
|
|
|
graph.add_dependency("app_b.0001", ("app_b", "0001"), ("app_a", "0003"))
|
2013-05-10 11:52:04 +00:00
|
|
|
# Test whole graph
|
|
|
|
self.assertRaises(
|
2013-05-10 15:00:55 +00:00
|
|
|
CircularDependencyError,
|
2013-05-10 11:52:04 +00:00
|
|
|
graph.forwards_plan, ("app_a", "0003"),
|
|
|
|
)
|
2014-08-23 09:37:25 +00:00
|
|
|
|
2014-11-25 13:29:38 +00:00
|
|
|
def test_circular_graph_2(self):
|
|
|
|
graph = MigrationGraph()
|
|
|
|
graph.add_node(('A', '0001'), None)
|
|
|
|
graph.add_node(('C', '0001'), None)
|
|
|
|
graph.add_node(('B', '0001'), None)
|
2014-11-25 16:20:40 +00:00
|
|
|
graph.add_dependency('A.0001', ('A', '0001'), ('B', '0001'))
|
|
|
|
graph.add_dependency('B.0001', ('B', '0001'), ('A', '0001'))
|
|
|
|
graph.add_dependency('C.0001', ('C', '0001'), ('B', '0001'))
|
2014-11-25 13:29:38 +00:00
|
|
|
|
|
|
|
self.assertRaises(
|
|
|
|
CircularDependencyError,
|
|
|
|
graph.forwards_plan, ('C', '0001')
|
|
|
|
)
|
|
|
|
|
2014-09-05 22:26:05 +00:00
|
|
|
def test_dfs(self):
|
|
|
|
graph = MigrationGraph()
|
|
|
|
root = ("app_a", "1")
|
|
|
|
graph.add_node(root, None)
|
|
|
|
expected = [root]
|
2014-09-05 22:47:20 +00:00
|
|
|
for i in range(2, 1000):
|
2014-09-05 22:26:05 +00:00
|
|
|
parent = ("app_a", str(i - 1))
|
|
|
|
child = ("app_a", str(i))
|
|
|
|
graph.add_node(child, None)
|
|
|
|
graph.add_dependency(str(i), child, parent)
|
|
|
|
expected.append(child)
|
|
|
|
|
|
|
|
actual = graph.dfs(root, lambda x: graph.dependents.get(x, set()))
|
|
|
|
self.assertEqual(expected[::-1], actual)
|
|
|
|
|
2014-08-23 09:37:25 +00:00
|
|
|
def test_plan_invalid_node(self):
|
|
|
|
"""
|
|
|
|
Tests for forwards/backwards_plan of nonexistent node.
|
|
|
|
"""
|
|
|
|
graph = MigrationGraph()
|
|
|
|
message = "Node ('app_b', '0001') not a valid node"
|
|
|
|
|
2014-09-25 22:24:17 +00:00
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, message):
|
2014-08-23 09:37:25 +00:00
|
|
|
graph.forwards_plan(("app_b", "0001"))
|
|
|
|
|
2014-09-25 22:24:17 +00:00
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, message):
|
2014-08-23 09:37:25 +00:00
|
|
|
graph.backwards_plan(("app_b", "0001"))
|
2014-08-22 07:26:06 +00:00
|
|
|
|
|
|
|
def test_missing_parent_nodes(self):
|
|
|
|
"""
|
|
|
|
Tests for missing parent nodes.
|
|
|
|
"""
|
|
|
|
# Build graph
|
|
|
|
graph = MigrationGraph()
|
|
|
|
graph.add_node(("app_a", "0001"), None)
|
|
|
|
graph.add_node(("app_a", "0002"), None)
|
|
|
|
graph.add_node(("app_a", "0003"), None)
|
|
|
|
graph.add_node(("app_b", "0001"), None)
|
|
|
|
graph.add_dependency("app_a.0003", ("app_a", "0003"), ("app_a", "0002"))
|
|
|
|
graph.add_dependency("app_a.0002", ("app_a", "0002"), ("app_a", "0001"))
|
2014-09-10 13:54:28 +00:00
|
|
|
msg = "Migration app_a.0001 dependencies reference nonexistent parent node ('app_b', '0002')"
|
2014-09-25 22:24:17 +00:00
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, msg):
|
2014-08-22 07:26:06 +00:00
|
|
|
graph.add_dependency("app_a.0001", ("app_a", "0001"), ("app_b", "0002"))
|
|
|
|
|
|
|
|
def test_missing_child_nodes(self):
|
|
|
|
"""
|
|
|
|
Tests for missing child nodes.
|
|
|
|
"""
|
|
|
|
# Build graph
|
|
|
|
graph = MigrationGraph()
|
|
|
|
graph.add_node(("app_a", "0001"), None)
|
2014-09-10 13:54:28 +00:00
|
|
|
msg = "Migration app_a.0002 dependencies reference nonexistent child node ('app_a', '0002')"
|
2014-09-25 22:24:17 +00:00
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, msg):
|
2014-08-22 07:26:06 +00:00
|
|
|
graph.add_dependency("app_a.0002", ("app_a", "0002"), ("app_a", "0001"))
|
2014-11-15 16:37:13 +00:00
|
|
|
|
|
|
|
def test_infinite_loop(self):
|
|
|
|
"""
|
|
|
|
Tests a complex dependency graph:
|
|
|
|
|
|
|
|
app_a: 0001 <-
|
|
|
|
\
|
|
|
|
app_b: 0001 <- x 0002 <-
|
|
|
|
/ \
|
|
|
|
app_c: 0001<- <------------- x 0002
|
|
|
|
|
2014-12-17 23:41:36 +00:00
|
|
|
And apply squashing on app_c.
|
2014-11-15 16:37:13 +00:00
|
|
|
"""
|
|
|
|
graph = MigrationGraph()
|
|
|
|
|
|
|
|
graph.add_node(("app_a", "0001"), None)
|
|
|
|
graph.add_node(("app_b", "0001"), None)
|
|
|
|
graph.add_node(("app_b", "0002"), None)
|
|
|
|
graph.add_node(("app_c", "0001_squashed_0002"), None)
|
|
|
|
|
|
|
|
graph.add_dependency("app_b.0001", ("app_b", "0001"), ("app_c", "0001_squashed_0002"))
|
|
|
|
graph.add_dependency("app_b.0002", ("app_b", "0002"), ("app_a", "0001"))
|
|
|
|
graph.add_dependency("app_b.0002", ("app_b", "0002"), ("app_b", "0001"))
|
|
|
|
graph.add_dependency("app_c.0001_squashed_0002", ("app_c", "0001_squashed_0002"), ("app_b", "0002"))
|
|
|
|
|
|
|
|
with self.assertRaises(CircularDependencyError):
|
|
|
|
graph.forwards_plan(("app_c", "0001_squashed_0002"))
|
2014-12-17 23:41:36 +00:00
|
|
|
|
|
|
|
def test_stringify(self):
|
|
|
|
graph = MigrationGraph()
|
|
|
|
self.assertEqual(force_text(graph), "Graph: 0 nodes, 0 edges")
|
|
|
|
|
|
|
|
graph.add_node(("app_a", "0001"), None)
|
|
|
|
graph.add_node(("app_a", "0002"), None)
|
|
|
|
graph.add_node(("app_a", "0003"), None)
|
|
|
|
graph.add_node(("app_b", "0001"), None)
|
|
|
|
graph.add_node(("app_b", "0002"), None)
|
|
|
|
graph.add_dependency("app_a.0002", ("app_a", "0002"), ("app_a", "0001"))
|
|
|
|
graph.add_dependency("app_a.0003", ("app_a", "0003"), ("app_a", "0002"))
|
|
|
|
graph.add_dependency("app_a.0003", ("app_a", "0003"), ("app_b", "0002"))
|
|
|
|
|
|
|
|
self.assertEqual(force_text(graph), "Graph: 5 nodes, 3 edges")
|