Beispiel #1
0
 def test_validate_consistency(self):
     """
     Tests for missing nodes, using `validate_consistency()` to raise the error.
     """
     # Build graph
     graph = MigrationGraph()
     graph.add_node(("app_a", "0001"), None)
     # Add dependency with missing parent node (skipping validation).
     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')"
     with self.assertRaisesMessage(NodeNotFoundError, msg):
         graph.validate_consistency()
     # Add missing parent node and ensure `validate_consistency()` no longer raises error.
     graph.add_node(("app_b", "0002"), None)
     graph.validate_consistency()
     # Add dependency with missing child node (skipping validation).
     graph.add_dependency("app_a.0002", ("app_a", "0002"), ("app_a", "0001"), skip_validation=True)
     msg = "Migration app_a.0002 dependencies reference nonexistent child node ('app_a', '0002')"
     with self.assertRaisesMessage(NodeNotFoundError, msg):
         graph.validate_consistency()
     # Add missing child node and ensure `validate_consistency()` no longer raises error.
     graph.add_node(("app_a", "0002"), None)
     graph.validate_consistency()
     # Rawly add dummy node.
     msg = "app_a.0001 (req'd by app_a.0002) is missing!"
     graph.add_dummy_node(
         key=("app_a", "0001"),
         origin="app_a.0002",
         error_message=msg
     )
     with self.assertRaisesMessage(NodeNotFoundError, msg):
         graph.validate_consistency()
Beispiel #2
0
    def test_complex_graph(self):
        r"""
        Tests a complex dependency graph:

        app_a:  0001 <-- 0002 <--- 0003 <-- 0004
                      \        \ /         /
        app_b:  0001 <-\ 0002 <-X         /
                      \          \       /
        app_c:         \ 0001 <-- 0002 <-
        """
        # 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_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)
        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"))
        # 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'),
            ],
        )
        # Test roots and leaves
        self.assertEqual(
            graph.root_nodes(),
            [('app_a', '0001'), ('app_b', '0001'), ('app_c', '0001')],
        )
        self.assertEqual(
            graph.leaf_nodes(),
            [('app_a', '0004'), ('app_b', '0002'), ('app_c', '0002')],
        )
Beispiel #3
0
    def test_backwards_nothing_to_do(self):
        r"""
        If the current state satisfies the given target, do nothing.

        a: 1 <--- 2
        b:    \- 1
        c:     \- 1

        If a1 is applied already and a2 is not, and we're asked to migrate to
        a1, don't apply or unapply b1 or c1, regardless of their current state.
        """
        a1_impl = FakeMigration('a1')
        a1 = ('a', '1')
        a2_impl = FakeMigration('a2')
        a2 = ('a', '2')
        b1_impl = FakeMigration('b1')
        b1 = ('b', '1')
        c1_impl = FakeMigration('c1')
        c1 = ('c', '1')
        graph = MigrationGraph()
        graph.add_node(a1, a1_impl)
        graph.add_node(a2, a2_impl)
        graph.add_node(b1, b1_impl)
        graph.add_node(c1, c1_impl)
        graph.add_dependency(None, a2, a1)
        graph.add_dependency(None, b1, a1)
        graph.add_dependency(None, c1, a1)

        executor = MigrationExecutor(None)
        executor.loader = FakeLoader(graph, {a1, b1})

        plan = executor.migration_plan({a1})

        self.assertEqual(plan, [])
Beispiel #4
0
    def test_minimize_rollbacks(self):
        """
        Minimize unnecessary rollbacks in connected apps.

        When you say "./manage.py migrate appA 0001", rather than migrating to
        just after appA-0001 in the linearized migration plan (which could roll
        back migrations in other apps that depend on appA 0001, but don't need
        to be rolled back since we're not rolling back appA 0001), we migrate
        to just before appA-0002.
        """
        a1_impl = FakeMigration('a1')
        a1 = ('a', '1')
        a2_impl = FakeMigration('a2')
        a2 = ('a', '2')
        b1_impl = FakeMigration('b1')
        b1 = ('b', '1')
        graph = MigrationGraph()
        graph.add_node(a1, a1_impl)
        graph.add_node(a2, a2_impl)
        graph.add_node(b1, b1_impl)
        graph.add_dependency(None, b1, a1)
        graph.add_dependency(None, a2, a1)

        executor = MigrationExecutor(None)
        executor.loader = FakeLoader(graph, {a1, b1, a2})

        plan = executor.migration_plan({a1})

        self.assertEqual(plan, [(a2_impl, True)])
Beispiel #5
0
 def test_missing_child_nodes(self):
     """
     Tests for missing child nodes.
     """
     # Build graph
     graph = MigrationGraph()
     graph.add_node(("app_a", "0001"), None)
     msg = "Migration app_a.0002 dependencies reference nonexistent child node ('app_a', '0002')"
     with self.assertRaisesMessage(NodeNotFoundError, msg):
         graph.add_dependency("app_a.0002", ("app_a", "0002"), ("app_a", "0001"))
Beispiel #6
0
    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)
        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'))

        with self.assertRaises(CircularDependencyError):
            graph.forwards_plan(('C', '0001'))
Beispiel #7
0
    def test_stringify(self):
        graph = MigrationGraph()
        self.assertEqual(str(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(str(graph), "Graph: 5 nodes, 3 edges")
        self.assertEqual(repr(graph), "<MigrationGraph: nodes=5, edges=3>")
Beispiel #8
0
    def test_graph_recursive(self):
        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)
        leaf = expected[-1]

        forwards_plan = graph.forwards_plan(leaf)
        self.assertEqual(expected, forwards_plan)

        backwards_plan = graph.backwards_plan(root)
        self.assertEqual(expected[::-1], backwards_plan)
Beispiel #9
0
 def test_remove_replaced_nodes(self):
     """
     Replaced nodes are properly removed and dependencies remapped.
     """
     # 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)
Beispiel #10
0
    def test_infinite_loop(self):
        """
        Tests a complex dependency graph:

        app_a:        0001 <-
                             \
        app_b:        0001 <- x 0002 <-
                       /               \
        app_c:   0001<-  <------------- x 0002

        And apply squashing on app_c.
        """
        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"))
Beispiel #11
0
 def test_remove_replacement_node(self):
     """
     A replacement node is properly removed and child dependencies remapped.
     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"))
     # 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)
     # Ensure 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)
Beispiel #12
0
    def test_graph_iterative(self):
        graph = MigrationGraph()
        root = ("app_a", "1")
        graph.add_node(root, None)
        expected = [root]
        for i in range(2, 1000):
            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)
        leaf = expected[-1]

        with self.assertWarnsMessage(RuntimeWarning, RECURSION_DEPTH_WARNING):
            forwards_plan = graph.forwards_plan(leaf)

        self.assertEqual(expected, forwards_plan)

        with self.assertWarnsMessage(RuntimeWarning, RECURSION_DEPTH_WARNING):
            backwards_plan = graph.backwards_plan(root)

        self.assertEqual(expected[::-1], backwards_plan)
Beispiel #13
0
    def test_minimize_rollbacks_branchy(self):
        r"""
        Minimize rollbacks when target has multiple in-app children.

        a: 1 <---- 3 <--\
              \ \- 2 <--- 4
               \       \
        b:      \- 1 <--- 2
        """
        a1_impl = FakeMigration('a1')
        a1 = ('a', '1')
        a2_impl = FakeMigration('a2')
        a2 = ('a', '2')
        a3_impl = FakeMigration('a3')
        a3 = ('a', '3')
        a4_impl = FakeMigration('a4')
        a4 = ('a', '4')
        b1_impl = FakeMigration('b1')
        b1 = ('b', '1')
        b2_impl = FakeMigration('b2')
        b2 = ('b', '2')
        graph = MigrationGraph()
        graph.add_node(a1, a1_impl)
        graph.add_node(a2, a2_impl)
        graph.add_node(a3, a3_impl)
        graph.add_node(a4, a4_impl)
        graph.add_node(b1, b1_impl)
        graph.add_node(b2, b2_impl)
        graph.add_dependency(None, a2, a1)
        graph.add_dependency(None, a3, a1)
        graph.add_dependency(None, a4, a2)
        graph.add_dependency(None, a4, a3)
        graph.add_dependency(None, b2, b1)
        graph.add_dependency(None, b1, a1)
        graph.add_dependency(None, b2, a2)

        executor = MigrationExecutor(None)
        executor.loader = FakeLoader(graph, {a1, b1, a2, b2, a3, a4})

        plan = executor.migration_plan({a1})

        should_be_rolled_back = [b2_impl, a4_impl, a2_impl, a3_impl]
        exp = [(m, True) for m in should_be_rolled_back]
        self.assertEqual(plan, exp)
Beispiel #14
0
    def test_simple_graph(self):
        """
        Tests a basic dependency graph:

        app_a:  0001 <-- 0002 <--- 0003 <-- 0004
                                 /
        app_b:  0001 <-- 0002 <-/
        """
        # 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_a", "0004"), None)
        graph.add_node(("app_b", "0001"), None)
        graph.add_node(("app_b", "0002"), None)
        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"))
        # 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')],
        )
        # Test roots and leaves
        self.assertEqual(
            graph.root_nodes(),
            [('app_a', '0001'), ('app_b', '0001')],
        )
        self.assertEqual(
            graph.leaf_nodes(),
            [('app_a', '0004'), ('app_b', '0002')],
        )
Beispiel #15
0
 def test_circular_graph(self):
     """
     Tests a circular dependency graph.
     """
     # 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_node(("app_b", "0002"), 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"))
     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"))
     # Test whole graph
     with self.assertRaises(CircularDependencyError):
         graph.forwards_plan(("app_a", "0003"))