2015-05-01 18:46:07 +00:00
|
|
|
from django.db.migrations.exceptions import CircularDependencyError, NodeNotFoundError
|
2018-03-20 21:14:22 +00:00
|
|
|
from django.db.migrations.graph import DummyNode, MigrationGraph, Node
|
2015-04-17 21:38:20 +00:00
|
|
|
from django.test import SimpleTestCase
|
2013-05-10 11:52:04 +00:00
|
|
|
|
|
|
|
|
2015-04-17 21:38:20 +00:00
|
|
|
class GraphTests(SimpleTestCase):
|
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")),
|
2015-09-11 23:33:12 +00:00
|
|
|
[
|
|
|
|
("app_b", "0001"),
|
|
|
|
("app_b", "0002"),
|
|
|
|
("app_a", "0001"),
|
|
|
|
("app_a", "0002"),
|
|
|
|
("app_a", "0003"),
|
|
|
|
("app_a", "0004"),
|
|
|
|
],
|
2013-05-10 11:52:04 +00:00
|
|
|
)
|
|
|
|
# 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):
|
2016-09-16 16:15:00 +00:00
|
|
|
r"""
|
2013-05-10 11:52:04 +00:00
|
|
|
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")),
|
2015-09-11 23:33:12 +00:00
|
|
|
[
|
|
|
|
("app_b", "0001"),
|
|
|
|
("app_c", "0001"),
|
|
|
|
("app_a", "0001"),
|
|
|
|
("app_a", "0002"),
|
|
|
|
("app_c", "0002"),
|
|
|
|
("app_b", "0002"),
|
|
|
|
("app_a", "0003"),
|
|
|
|
("app_a", "0004"),
|
|
|
|
],
|
2013-05-10 11:52:04 +00:00
|
|
|
)
|
|
|
|
# Test reverse to b:0001
|
|
|
|
self.assertEqual(
|
|
|
|
graph.backwards_plan(("app_b", "0001")),
|
2015-09-11 23:33:12 +00:00
|
|
|
[
|
|
|
|
("app_a", "0004"),
|
|
|
|
("app_c", "0002"),
|
|
|
|
("app_c", "0001"),
|
|
|
|
("app_a", "0003"),
|
|
|
|
("app_b", "0002"),
|
|
|
|
("app_b", "0001"),
|
|
|
|
],
|
2013-05-10 11:52:04 +00:00
|
|
|
)
|
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
|
2016-01-17 11:26:39 +00:00
|
|
|
with self.assertRaises(CircularDependencyError):
|
2018-03-20 21:14:22 +00:00
|
|
|
graph.ensure_not_cyclic()
|
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
|
|
|
|
2016-01-17 11:26:39 +00:00
|
|
|
with self.assertRaises(CircularDependencyError):
|
2018-03-20 21:14:22 +00:00
|
|
|
graph.ensure_not_cyclic()
|
2014-11-25 13:29:38 +00:00
|
|
|
|
2018-03-20 21:14:22 +00:00
|
|
|
def test_iterative_dfs(self):
|
2015-02-20 04:43:45 +00:00
|
|
|
graph = MigrationGraph()
|
|
|
|
root = ("app_a", "1")
|
|
|
|
graph.add_node(root, None)
|
|
|
|
expected = [root]
|
|
|
|
for i in range(2, 750):
|
|
|
|
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)
|
2015-03-28 16:46:10 +00:00
|
|
|
leaf = expected[-1]
|
2015-02-20 04:43:45 +00:00
|
|
|
|
2015-03-28 16:46:10 +00:00
|
|
|
forwards_plan = graph.forwards_plan(leaf)
|
|
|
|
self.assertEqual(expected, forwards_plan)
|
|
|
|
|
|
|
|
backwards_plan = graph.backwards_plan(root)
|
|
|
|
self.assertEqual(expected[::-1], backwards_plan)
|
2015-02-20 04:43:45 +00:00
|
|
|
|
2018-03-20 21:14:22 +00:00
|
|
|
def test_iterative_dfs_complexity(self):
|
|
|
|
"""
|
|
|
|
In a graph with merge migrations, iterative_dfs() traverses each node
|
|
|
|
only once even if there are multiple paths leading to it.
|
|
|
|
"""
|
|
|
|
n = 50
|
2014-09-05 22:26:05 +00:00
|
|
|
graph = MigrationGraph()
|
2018-03-20 21:14:22 +00:00
|
|
|
for i in range(1, n + 1):
|
|
|
|
graph.add_node(("app_a", str(i)), None)
|
|
|
|
graph.add_node(("app_b", str(i)), None)
|
|
|
|
graph.add_node(("app_c", str(i)), None)
|
|
|
|
for i in range(1, n):
|
|
|
|
graph.add_dependency(None, ("app_b", str(i)), ("app_a", str(i)))
|
|
|
|
graph.add_dependency(None, ("app_c", str(i)), ("app_a", str(i)))
|
|
|
|
graph.add_dependency(None, ("app_a", str(i + 1)), ("app_b", str(i)))
|
|
|
|
graph.add_dependency(None, ("app_a", str(i + 1)), ("app_c", str(i)))
|
|
|
|
plan = graph.forwards_plan(("app_a", str(n)))
|
|
|
|
expected = [
|
|
|
|
(app, str(i)) for i in range(1, n) for app in ["app_a", "app_c", "app_b"]
|
|
|
|
] + [("app_a", str(n))]
|
|
|
|
self.assertEqual(plan, expected)
|
2014-09-05 22:26:05 +00:00
|
|
|
|
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')"
|
2022-02-04 07:08:27 +00:00
|
|
|
)
|
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')"
|
2022-02-04 07:08:27 +00:00
|
|
|
)
|
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
|
|
|
|
2018-03-20 21:14:22 +00:00
|
|
|
def test_validate_consistency_missing_parent(self):
|
2016-05-08 00:56:13 +00:00
|
|
|
graph = MigrationGraph()
|
|
|
|
graph.add_node(("app_a", "0001"), None)
|
|
|
|
graph.add_dependency(
|
|
|
|
"app_a.0001", ("app_a", "0001"), ("app_b", "0002"), skip_validation=True
|
|
|
|
)
|
|
|
|
msg = (
|
|
|
|
"Migration app_a.0001 dependencies reference nonexistent parent node "
|
|
|
|
"('app_b', '0002')"
|
2022-02-04 07:08:27 +00:00
|
|
|
)
|
2016-05-08 00:56:13 +00:00
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, msg):
|
|
|
|
graph.validate_consistency()
|
2018-03-20 21:14:22 +00:00
|
|
|
|
|
|
|
def test_validate_consistency_missing_child(self):
|
|
|
|
graph = MigrationGraph()
|
2016-05-08 00:56:13 +00:00
|
|
|
graph.add_node(("app_b", "0002"), None)
|
2018-03-20 21:14:22 +00:00
|
|
|
graph.add_dependency(
|
|
|
|
"app_b.0002", ("app_a", "0001"), ("app_b", "0002"), skip_validation=True
|
|
|
|
)
|
|
|
|
msg = (
|
|
|
|
"Migration app_b.0002 dependencies reference nonexistent child node "
|
|
|
|
"('app_a', '0001')"
|
2022-02-04 07:08:27 +00:00
|
|
|
)
|
2016-05-08 00:56:13 +00:00
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, msg):
|
|
|
|
graph.validate_consistency()
|
2018-03-20 21:14:22 +00:00
|
|
|
|
|
|
|
def test_validate_consistency_no_error(self):
|
|
|
|
graph = MigrationGraph()
|
|
|
|
graph.add_node(("app_a", "0001"), None)
|
|
|
|
graph.add_node(("app_b", "0002"), None)
|
|
|
|
graph.add_dependency(
|
|
|
|
"app_a.0001", ("app_a", "0001"), ("app_b", "0002"), skip_validation=True
|
|
|
|
)
|
2016-05-08 00:56:13 +00:00
|
|
|
graph.validate_consistency()
|
2018-03-20 21:14:22 +00:00
|
|
|
|
|
|
|
def test_validate_consistency_dummy(self):
|
|
|
|
"""
|
|
|
|
validate_consistency() raises an error if there's an isolated dummy
|
|
|
|
node.
|
|
|
|
"""
|
|
|
|
msg = "app_a.0001 (req'd by app_b.0002) is missing!"
|
|
|
|
graph = MigrationGraph()
|
2016-05-08 00:56:13 +00:00
|
|
|
graph.add_dummy_node(
|
|
|
|
key=("app_a", "0001"), origin="app_b.0002", error_message=msg
|
|
|
|
)
|
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, msg):
|
|
|
|
graph.validate_consistency()
|
|
|
|
|
|
|
|
def test_remove_replaced_nodes(self):
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
Replaced nodes are properly removed and dependencies remapped.
|
2016-05-08 00:56:13 +00:00
|
|
|
"""
|
|
|
|
# Add some dummy nodes to be replaced.
|
|
|
|
graph = MigrationGraph()
|
|
|
|
graph.add_dummy_node(
|
|
|
|
key=("app_a", "0001"), origin="app_a.0002", error_message="BAD!"
|
|
|
|
)
|
|
|
|
graph.add_dummy_node(
|
|
|
|
key=("app_a", "0002"), origin="app_b.0001", error_message="BAD!"
|
|
|
|
)
|
|
|
|
graph.add_dependency(
|
|
|
|
"app_a.0002", ("app_a", "0002"), ("app_a", "0001"), skip_validation=True
|
|
|
|
)
|
|
|
|
# Add some normal parent and child nodes to test dependency remapping.
|
|
|
|
graph.add_node(("app_c", "0001"), None)
|
|
|
|
graph.add_node(("app_b", "0001"), None)
|
|
|
|
graph.add_dependency(
|
|
|
|
"app_a.0001", ("app_a", "0001"), ("app_c", "0001"), skip_validation=True
|
|
|
|
)
|
|
|
|
graph.add_dependency(
|
|
|
|
"app_b.0001", ("app_b", "0001"), ("app_a", "0002"), skip_validation=True
|
|
|
|
)
|
|
|
|
# Try replacing before replacement node exists.
|
|
|
|
msg = (
|
|
|
|
"Unable to find replacement node ('app_a', '0001_squashed_0002'). It was "
|
|
|
|
"either never added to the migration graph, or has been removed."
|
|
|
|
)
|
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, msg):
|
|
|
|
graph.remove_replaced_nodes(
|
|
|
|
replacement=("app_a", "0001_squashed_0002"),
|
|
|
|
replaced=[("app_a", "0001"), ("app_a", "0002")],
|
|
|
|
)
|
|
|
|
graph.add_node(("app_a", "0001_squashed_0002"), None)
|
|
|
|
# Ensure `validate_consistency()` still raises an error at this stage.
|
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, "BAD!"):
|
|
|
|
graph.validate_consistency()
|
|
|
|
# Remove the dummy nodes.
|
|
|
|
graph.remove_replaced_nodes(
|
|
|
|
replacement=("app_a", "0001_squashed_0002"),
|
|
|
|
replaced=[("app_a", "0001"), ("app_a", "0002")],
|
|
|
|
)
|
|
|
|
# Ensure graph is now consistent and dependencies have been remapped
|
|
|
|
graph.validate_consistency()
|
|
|
|
parent_node = graph.node_map[("app_c", "0001")]
|
|
|
|
replacement_node = graph.node_map[("app_a", "0001_squashed_0002")]
|
|
|
|
child_node = graph.node_map[("app_b", "0001")]
|
|
|
|
self.assertIn(parent_node, replacement_node.parents)
|
|
|
|
self.assertIn(replacement_node, parent_node.children)
|
|
|
|
self.assertIn(child_node, replacement_node.children)
|
|
|
|
self.assertIn(replacement_node, child_node.parents)
|
|
|
|
|
|
|
|
def test_remove_replacement_node(self):
|
|
|
|
"""
|
2016-10-27 07:53:39 +00:00
|
|
|
A replacement node is properly removed and child dependencies remapped.
|
2016-05-08 00:56:13 +00:00
|
|
|
We assume parent dependencies are already correct.
|
|
|
|
"""
|
|
|
|
# Add some dummy nodes to be replaced.
|
|
|
|
graph = MigrationGraph()
|
|
|
|
graph.add_node(("app_a", "0001"), None)
|
|
|
|
graph.add_node(("app_a", "0002"), None)
|
|
|
|
graph.add_dependency("app_a.0002", ("app_a", "0002"), ("app_a", "0001"))
|
|
|
|
# Try removing replacement node before replacement node exists.
|
|
|
|
msg = (
|
|
|
|
"Unable to remove replacement node ('app_a', '0001_squashed_0002'). It was"
|
|
|
|
" either never added to the migration graph, or has been removed already."
|
|
|
|
)
|
|
|
|
with self.assertRaisesMessage(NodeNotFoundError, msg):
|
|
|
|
graph.remove_replacement_node(
|
|
|
|
replacement=("app_a", "0001_squashed_0002"),
|
|
|
|
replaced=[("app_a", "0001"), ("app_a", "0002")],
|
|
|
|
)
|
|
|
|
graph.add_node(("app_a", "0001_squashed_0002"), None)
|
|
|
|
# Add a child node to test dependency remapping.
|
|
|
|
graph.add_node(("app_b", "0001"), None)
|
|
|
|
graph.add_dependency(
|
|
|
|
"app_b.0001", ("app_b", "0001"), ("app_a", "0001_squashed_0002")
|
2022-02-03 19:24:19 +00:00
|
|
|
)
|
2016-05-08 00:56:13 +00:00
|
|
|
# Remove the replacement node.
|
|
|
|
graph.remove_replacement_node(
|
|
|
|
replacement=("app_a", "0001_squashed_0002"),
|
|
|
|
replaced=[("app_a", "0001"), ("app_a", "0002")],
|
|
|
|
)
|
|
|
|
# Ensure graph is consistent and child dependency has been remapped
|
|
|
|
graph.validate_consistency()
|
|
|
|
replaced_node = graph.node_map[("app_a", "0002")]
|
|
|
|
child_node = graph.node_map[("app_b", "0001")]
|
|
|
|
self.assertIn(child_node, replaced_node.children)
|
|
|
|
self.assertIn(replaced_node, child_node.parents)
|
|
|
|
# Child dependency hasn't also gotten remapped to the other replaced
|
|
|
|
# node.
|
|
|
|
other_replaced_node = graph.node_map[("app_a", "0001")]
|
|
|
|
self.assertNotIn(child_node, other_replaced_node.children)
|
|
|
|
self.assertNotIn(other_replaced_node, child_node.parents)
|
|
|
|
|
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")
|
2022-02-03 19:24:19 +00:00
|
|
|
)
|
2014-11-15 16:37:13 +00:00
|
|
|
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):
|
2018-03-20 21:14:22 +00:00
|
|
|
graph.ensure_not_cyclic()
|
2014-12-17 23:41:36 +00:00
|
|
|
|
|
|
|
def test_stringify(self):
|
|
|
|
graph = MigrationGraph()
|
2017-01-24 11:22:42 +00:00
|
|
|
self.assertEqual(str(graph), "Graph: 0 nodes, 0 edges")
|
2014-12-17 23:41:36 +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)
|
|
|
|
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"))
|
|
|
|
|
2017-01-24 11:22:42 +00:00
|
|
|
self.assertEqual(str(graph), "Graph: 5 nodes, 3 edges")
|
2015-06-01 12:46:45 +00:00
|
|
|
self.assertEqual(repr(graph), "<MigrationGraph: nodes=5, edges=3>")
|
2017-02-04 10:43:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
class NodeTests(SimpleTestCase):
|
|
|
|
def test_node_repr(self):
|
|
|
|
node = Node(("app_a", "0001"))
|
|
|
|
self.assertEqual(repr(node), "<Node: ('app_a', '0001')>")
|
|
|
|
|
2023-02-14 00:23:30 +00:00
|
|
|
def test_node_str(self):
|
|
|
|
node = Node(("app_a", "0001"))
|
|
|
|
self.assertEqual(str(node), "('app_a', '0001')")
|
|
|
|
|
2017-02-04 10:43:14 +00:00
|
|
|
def test_dummynode_repr(self):
|
|
|
|
node = DummyNode(
|
|
|
|
key=("app_a", "0001"),
|
|
|
|
origin="app_a.0001",
|
|
|
|
error_message="x is missing",
|
|
|
|
)
|
|
|
|
self.assertEqual(repr(node), "<DummyNode: ('app_a', '0001')>")
|