Commits

cla...@bcc190cf-cafb-0310-a4f2-bffc1f526a37  committed c9f08c1

Fixed #17954 -- Fixed dependency checking for test databases. Thanks Łukasz Rekucki for the report and the patch.

  • Participants
  • Parent commits 09b95d9

Comments (0)

Files changed (2)

File django/test/simple.py

 
 
 def dependency_ordered(test_databases, dependencies):
-    """Reorder test_databases into an order that honors the dependencies
+    """
+    Reorder test_databases into an order that honors the dependencies
     described in TEST_DEPENDENCIES.
     """
     ordered_test_databases = []
     resolved_databases = set()
+
+    # Maps db signature to dependencies of all it's aliases
+    dependencies_map = {}
+
+    # sanity check - no DB can depend on it's own alias
+    for sig, (_, aliases) in test_databases:
+        all_deps = set()
+        for alias in aliases:
+            all_deps.update(dependencies.get(alias, []))
+        if not all_deps.isdisjoint(aliases):
+            raise ImproperlyConfigured(
+                "Circular dependency: databases %r depend on each other, "
+                "but are aliases." % aliases)
+        dependencies_map[sig] = all_deps
+
     while test_databases:
         changed = False
         deferred = []
 
-        while test_databases:
-            signature, (db_name, aliases) = test_databases.pop()
-            dependencies_satisfied = True
-            for alias in aliases:
-                if alias in dependencies:
-                    if all(a in resolved_databases
-                           for a in dependencies[alias]):
-                        # all dependencies for this alias are satisfied
-                        dependencies.pop(alias)
-                        resolved_databases.add(alias)
-                    else:
-                        dependencies_satisfied = False
-                else:
-                    resolved_databases.add(alias)
-
-            if dependencies_satisfied:
+        # Try to find a DB that has all it's dependencies met
+        for signature, (db_name, aliases) in test_databases:
+            if dependencies_map[signature].issubset(resolved_databases):
+                resolved_databases.update(aliases)
                 ordered_test_databases.append((signature, (db_name, aliases)))
                 changed = True
             else:
                 # we only need to create the test database once.
                 item = test_databases.setdefault(
                     connection.creation.test_db_signature(),
-                    (connection.settings_dict['NAME'], [])
+                    (connection.settings_dict['NAME'], set())
                 )
-                item[1].append(alias)
+                item[1].add(alias)
 
                 if 'TEST_DEPENDENCIES' in connection.settings_dict:
                     dependencies[alias] = (
         # Second pass -- actually create the databases.
         old_names = []
         mirrors = []
+
         for signature, (db_name, aliases) in dependency_ordered(
             test_databases.items(), dependencies):
+            test_db_name = None
             # Actually create the database for the first connection
-            connection = connections[aliases[0]]
-            old_names.append((connection, db_name, True))
-            test_db_name = connection.creation.create_test_db(
-                self.verbosity, autoclobber=not self.interactive)
-            for alias in aliases[1:]:
+
+            for alias in aliases:
                 connection = connections[alias]
-                if db_name:
-                    old_names.append((connection, db_name, False))
+                old_names.append((connection, db_name, True))
+                if test_db_name is None:
+                    test_db_name = connection.creation.create_test_db(
+                            self.verbosity, autoclobber=not self.interactive)
+                else:
                     connection.settings_dict['NAME'] = test_db_name
-                else:
-                    # If settings_dict['NAME'] isn't defined, we have a backend
-                    # where the name isn't important -- e.g., SQLite, which
-                    # uses :memory:. Force create the database instead of
-                    # assuming it's a duplicate.
-                    old_names.append((connection, db_name, True))
-                    connection.creation.create_test_db(
-                        self.verbosity, autoclobber=not self.interactive)
 
         for alias, mirror_alias in mirrored_aliases.items():
             mirrors.append((alias, connections[alias].settings_dict['NAME']))

File tests/regressiontests/test_runner/tests.py

 
         self.assertRaises(ImproperlyConfigured, simple.dependency_ordered, raw, dependencies=dependencies)
 
+    def test_own_alias_dependency(self):
+        raw = [
+            ('s1', ('s1_db', ['alpha', 'bravo']))
+        ]
+        dependencies = {
+            'alpha': ['bravo']
+        }
+
+        with self.assertRaises(ImproperlyConfigured):
+            simple.dependency_ordered(raw, dependencies=dependencies)
+
+        # reordering aliases shouldn't matter
+        raw = [
+            ('s1', ('s1_db', ['bravo', 'alpha']))
+        ]
+
+        with self.assertRaises(ImproperlyConfigured):
+            simple.dependency_ordered(raw, dependencies=dependencies)
+
 
 class MockTestRunner(object):
     invoked = False