Beispiel #1
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
     self.assertRaises(
         CircularDependencyError,
         graph.forwards_plan,
         ("app_a", "0003"),
     )
Beispiel #2
0
    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
        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_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 warnings.catch_warnings(record=True) as w:
            warnings.simplefilter('always', RuntimeWarning)
            forwards_plan = graph.forwards_plan(leaf)

        self.assertEqual(len(w), 1)
        self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
        self.assertEqual(str(w[-1].message), RECURSION_DEPTH_WARNING)
        self.assertEqual(expected, forwards_plan)

        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter('always', RuntimeWarning)
            backwards_plan = graph.backwards_plan(root)

        self.assertEqual(len(w), 1)
        self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
        self.assertEqual(str(w[-1].message), RECURSION_DEPTH_WARNING)
        self.assertEqual(expected[::-1], backwards_plan)
Beispiel #4
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 #5
0
 def build_graph(self):
     """
     Builds a migration dependency graph using both the disk and database.
     You'll need to rebuild the graph if you apply migrations. This isn't
     usually a problem as generally migration stuff runs in a one-shot process.
     """
     # Load disk data
     self.load_disk()
     # Load database data
     recorder = MigrationRecorder(self.connection)
     self.applied_migrations = recorder.applied_migrations()
     # Do a first pass to separate out replacing and non-replacing migrations
     normal = {}
     replacing = {}
     for key, migration in self.disk_migrations.items():
         if migration.replaces:
             replacing[key] = migration
         else:
             normal[key] = migration
     # Calculate reverse dependencies - i.e., for each migration, what depends on it?
     # This is just for dependency re-pointing when applying replacements,
     # so we ignore run_before here.
     reverse_dependencies = {}
     for key, migration in normal.items():
         for parent in migration.dependencies:
             reverse_dependencies.setdefault(parent, set()).add(key)
     # Carry out replacements if we can - that is, if all replaced migrations
     # are either unapplied or missing.
     for key, migration in replacing.items():
         # Ensure this replacement migration is not in applied_migrations
         self.applied_migrations.discard(key)
         # Do the check. We can replace if all our replace targets are
         # applied, or if all of them are unapplied.
         applied_statuses = [(target in self.applied_migrations) for target in migration.replaces]
         can_replace = all(applied_statuses) or (not any(applied_statuses))
         if not can_replace:
             continue
         # Alright, time to replace. Step through the replaced migrations
         # and remove, repointing dependencies if needs be.
         for replaced in migration.replaces:
             if replaced in normal:
                 # We don't care if the replaced migration doesn't exist;
                 # the usage pattern here is to delete things after a while.
                 del normal[replaced]
             for child_key in reverse_dependencies.get(replaced, set()):
                 if child_key in migration.replaces:
                     continue
                 normal[child_key].dependencies.remove(replaced)
                 normal[child_key].dependencies.append(key)
         normal[key] = migration
         # Mark the replacement as applied if all its replaced ones are
         if all(applied_statuses):
             self.applied_migrations.add(key)
     # Finally, make a graph and load everything into it
     self.graph = MigrationGraph()
     for key, migration in normal.items():
         self.graph.add_node(key, migration)
     for key, migration in normal.items():
         for parent in migration.dependencies:
             self.graph.add_dependency(key, parent)
Beispiel #6
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"))
    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)])
    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 #9
0
 def test_arrange_for_graph(self):
     "Tests auto-naming of migrations for graph matching."
     # Make a fake graph
     graph = MigrationGraph()
     graph.add_node(("testapp", "0001_initial"), None)
     graph.add_node(("testapp", "0002_foobar"), None)
     graph.add_node(("otherapp", "0001_initial"), None)
     graph.add_dependency(("testapp", "0002_foobar"),
                          ("testapp", "0001_initial"))
     graph.add_dependency(("testapp", "0002_foobar"),
                          ("otherapp", "0001_initial"))
     # Use project state to make a new migration change set
     before = self.make_project_state([])
     after = self.make_project_state(
         [self.author_empty, self.other_pony, self.other_stable])
     autodetector = MigrationAutodetector(before, after)
     changes = autodetector._detect_changes()
     # Run through arrange_for_graph
     changes = autodetector._arrange_for_graph(changes, graph)
     # Make sure there's a new name, deps match, etc.
     self.assertEqual(changes["testapp"][0].name, "0003_author")
     self.assertEqual(changes["testapp"][0].dependencies,
                      [("testapp", "0002_foobar")])
     self.assertEqual(changes["otherapp"][0].name, "0002_pony_stable")
     self.assertEqual(changes["otherapp"][0].dependencies,
                      [("otherapp", "0001_initial")])
Beispiel #10
0
    def test_minimize_rollbacks_branchy(self):
        r"""
        Minimize rollbacks when target has multiple in-app children.

           a3---a4
          /    /
        a1---a2
          \    \
           b1---b2
        """
        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: a1_impl,
                b1: b1_impl,
                a2: a2_impl,
                b2: b2_impl,
                a3: a3_impl,
                a4: a4_impl,
            })

        plan_to_a1 = 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_to_a1, exp)

        plan_to_before_a2 = executor.migration_plan({a2}, before=True)

        should_be_rolled_back = [b2_impl, a4_impl, a2_impl]
        exp = [(m, True) for m in should_be_rolled_back]
        self.assertEqual(plan_to_before_a2, exp)
Beispiel #11
0
 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)
     graph.validate_consistency()
Beispiel #12
0
 def test_validate_consistency_missing_child(self):
     graph = MigrationGraph()
     graph.add_node(("app_b", "0002"), None)
     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')"
     with self.assertRaisesMessage(NodeNotFoundError, msg):
         graph.validate_consistency()
Beispiel #13
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 #14
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 #15
0
 def graph(self):
     """
     Builds a migration dependency graph using both the disk and database.
     """
     # Make sure we have the disk data
     if self.disk_migrations is None:
         self.load_disk()
     # And the database data
     if self.applied_migrations is None:
         recorder = MigrationRecorder(self.connection)
         self.applied_migrations = recorder.applied_migrations()
     # Do a first pass to separate out replacing and non-replacing migrations
     normal = {}
     replacing = {}
     for key, migration in self.disk_migrations.items():
         if migration.replaces:
             replacing[key] = migration
         else:
             normal[key] = migration
     # Calculate reverse dependencies - i.e., for each migration, what depends on it?
     # This is just for dependency re-pointing when applying replacements,
     # so we ignore run_before here.
     reverse_dependencies = {}
     for key, migration in normal.items():
         for parent in migration.dependencies:
             reverse_dependencies.setdefault(parent, set()).add(key)
     # Carry out replacements if we can - that is, if all replaced migrations
     # are either unapplied or missing.
     for key, migration in replacing.items():
         # Do the check
         can_replace = True
         for target in migration.replaces:
             if target in self.applied_migrations:
                 can_replace = False
                 break
         if not can_replace:
             continue
         # Alright, time to replace. Step through the replaced migrations
         # and remove, repointing dependencies if needs be.
         for replaced in migration.replaces:
             if replaced in normal:
                 del normal[replaced]
             for child_key in reverse_dependencies.get(replaced, set()):
                 normal[child_key].dependencies.remove(replaced)
                 normal[child_key].dependencies.append(key)
         normal[key] = migration
     # Finally, make a graph and load everything into it
     graph = MigrationGraph()
     for key, migration in normal.items():
         graph.add_node(key, migration)
     for key, migration in normal.items():
         for parent in migration.dependencies:
             graph.add_dependency(key, parent)
     return graph
Beispiel #16
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 #17
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: a1_impl,
                b1: b1_impl,
                a2: a2_impl,
                b2: b2_impl,
                a3: a3_impl,
                a4: a4_impl,
            },
        )

        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 #18
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.ensure_not_cyclic()
Beispiel #19
0
 def test_validate_consistency_missing_parent(self):
     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')"
     )
     with self.assertRaisesMessage(NodeNotFoundError, msg):
         graph.validate_consistency()
Beispiel #20
0
 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()
     graph.add_dummy_node(key=("app_a", "0001"),
                          origin="app_b.0002",
                          error_message=msg)
     with self.assertRaisesMessage(NodeNotFoundError, msg):
         graph.validate_consistency()
Beispiel #21
0
    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"

        with self.assertRaisesMessage(NodeNotFoundError, message):
            graph.forwards_plan(("app_b", "0001"))

        with self.assertRaisesMessage(NodeNotFoundError, message):
            graph.backwards_plan(("app_b", "0001"))
Beispiel #22
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 #23
0
    def test_dfs(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)

        actual = graph.dfs(root, lambda x: graph.dependents.get(x, set()))
        self.assertEqual(expected[::-1], actual)
Beispiel #24
0
    def test_recursion_depth(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)

        actual = graph.node_map[root].descendants()
        self.assertEqual(expected[::-1], actual)
Beispiel #25
0
    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")
        self.assertEqual(repr(graph), "<MigrationGraph: nodes=5, edges=3>")
Beispiel #26
0
 def test_trim_apps(self):
     "Tests that trim does not remove dependencies but does remove unwanted apps"
     # Use project state to make a new migration change set
     before = self.make_project_state([])
     after = self.make_project_state([self.author_empty, self.other_pony, self.other_stable, self.third_thing])
     autodetector = MigrationAutodetector(before, after, MigrationQuestioner(defaults={"ask_initial": True}))
     changes = autodetector._detect_changes()
     # Run through arrange_for_graph
     graph = MigrationGraph()
     changes = autodetector.arrange_for_graph(changes, graph)
     changes["testapp"][0].dependencies.append(("otherapp", "0001_initial"))
     changes = autodetector._trim_to_apps(changes, set(["testapp"]))
     # Make sure there's the right set of migrations
     self.assertEqual(changes["testapp"][0].name, "0001_initial")
     self.assertEqual(changes["otherapp"][0].name, "0001_initial")
     self.assertNotIn("thirdapp", changes)
Beispiel #27
0
 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"))
     msg = "Migration app_a.0001 dependencies reference nonexistent parent node ('app_b', '0002')"
     with self.assertRaisesMessage(KeyError, msg):
         graph.add_dependency("app_a.0001", ("app_a", "0001"),
                              ("app_b", "0002"))
Beispiel #28
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)
    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 #30
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.ensure_not_cyclic()