Author: russellm
Date: 2010-12-04 18:44:34 -0600 (Sat, 04 Dec 2010)
New Revision: 14822

Modified:
   django/trunk/django/test/simple.py
   django/trunk/docs/topics/testing.txt
   django/trunk/tests/regressiontests/test_runner/tests.py
Log:
Fixed #14799 -- Provided a full solution for test database creation order 
problems.

Modified: django/trunk/django/test/simple.py
===================================================================
--- django/trunk/django/test/simple.py  2010-12-04 22:32:28 UTC (rev 14821)
+++ django/trunk/django/test/simple.py  2010-12-05 00:44:34 UTC (rev 14822)
@@ -2,12 +2,21 @@
 import signal
 
 from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
 from django.db.models import get_app, get_apps
 from django.test import _doctest as doctest
 from django.test.utils import setup_test_environment, teardown_test_environment
 from django.test.testcases import OutputChecker, DocTestRunner, TestCase
 from django.utils import unittest
 
+try:
+    all
+except NameError:
+    from django.utils.itercompat import all
+
+
+__all__ = ('DjangoTestRunner', 'DjangoTestSuiteRunner', 'run_tests')
+
 # The module name for tests outside models.py
 TEST_MODULE = 'tests'
 
@@ -183,7 +192,41 @@
         bins[0].addTests(bins[i+1])
     return bins[0]
 
+def dependency_ordered(test_databases, dependencies):
+    """Reorder test_databases into an order that honors the dependencies
+    described in TEST_DEPENDENCIES.
+    """
+    ordered_test_databases = []
+    resolved_databases = set()
+    while test_databases:
+        changed = False
+        deferred = []
 
+        while test_databases:
+            signature, 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:
+                ordered_test_databases.append((signature, aliases))
+                changed = True
+            else:
+                deferred.append((signature, aliases))
+
+        if not changed:
+            raise ImproperlyConfigured("Circular dependency in 
TEST_DEPENDENCIES")
+        test_databases = deferred
+    return ordered_test_databases
+
 class DjangoTestSuiteRunner(object):
     def __init__(self, verbosity=1, interactive=True, failfast=True, **kwargs):
         self.verbosity = verbosity
@@ -222,6 +265,7 @@
         # and which ones are test mirrors or duplicate entries in DATABASES
         mirrored_aliases = {}
         test_databases = {}
+        dependencies = {}
         for alias in connections:
             connection = connections[alias]
             if connection.settings_dict['TEST_MIRROR']:
@@ -238,21 +282,17 @@
                         connection.settings_dict['ENGINE'],
                         connection.settings_dict['NAME'],
                     ), []).append(alias)
-        
-        # Re-order the list of databases to create, making sure the default
-        # database is first. Otherwise, creation order is semi-random (i.e. 
-        # dict ordering dependent).
-        dbs_to_create = []
-        for dbinfo, aliases in test_databases.items():
-            if DEFAULT_DB_ALIAS in aliases:
-                dbs_to_create.insert(0, (dbinfo, aliases))
-            else:
-                dbs_to_create.append((dbinfo, aliases))
-                
-        # Final pass -- actually create the databases.
+
+                if 'TEST_DEPENDENCIES' in connection.settings_dict:
+                    dependencies[alias] = 
connection.settings_dict['TEST_DEPENDENCIES']
+                else:
+                    if alias != 'default':
+                        dependencies[alias] = 
connection.settings_dict.get('TEST_DEPENDENCIES', ['default'])
+
+        # Second pass -- actually create the databases.
         old_names = []
         mirrors = []
-        for (host, port, engine, db_name), aliases in dbs_to_create:
+        for (host, port, engine, db_name), aliases in 
dependency_ordered(test_databases.items(), dependencies):
             # Actually create the database for the first connection
             connection = connections[aliases[0]]
             old_names.append((connection, db_name, True))

Modified: django/trunk/docs/topics/testing.txt
===================================================================
--- django/trunk/docs/topics/testing.txt        2010-12-04 22:32:28 UTC (rev 
14821)
+++ django/trunk/docs/topics/testing.txt        2010-12-05 00:44:34 UTC (rev 
14822)
@@ -454,6 +454,53 @@
 the same database, not because there is data replication between the
 two databases.
 
+.. _topics-testing-creation-dependencies:
+
+Controlling creation order for test databases
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 1.3
+
+By default, Django will always create the ``default`` database first.
+However, no guarantees are made on the creation order of any other
+databases in your test setup.
+
+If your database configuration requires a specific creation order, you
+can specify the dependencies that exist using the
+:setting:`TEST_DEPENDENCIES` setting. Consider the following
+(simplified) example database configuration::
+
+    DATABASES = {
+        'default': {
+             # ... db settings
+             TEST_DEPENDENCIES = ['diamonds']
+        },
+        'diamonds': {
+            # ... db settings
+        }
+        'clubs': {
+            # ... db settings
+            TEST_DEPENDENCIES = ['diamonds']
+        }
+        'spades': {
+            # ... db settings
+            TEST_DEPENDENCIES = ['diamonds','hearts']
+        }
+        'hearts': {
+            # ... db settings
+            TEST_DEPENDENCIES = ['diamonds','clubs']
+        }
+    }
+
+Under this configuration, the ``diamonds`` database will be created first,
+as it is the only database alias without dependencies. The ``default``` and
+``clubs`` alias will be created next (although the order of creation of this
+pair is not guaranteed); then ``hearts``; and finally ``spades``.
+
+If there are any circular dependencies in the
+:setting:`TEST_DEPENDENCIES` definition, an ``ImproperlyConfigured``
+exception will be raised.
+
 Other test conditions
 ---------------------
 

Modified: django/trunk/tests/regressiontests/test_runner/tests.py
===================================================================
--- django/trunk/tests/regressiontests/test_runner/tests.py     2010-12-04 
22:32:28 UTC (rev 14821)
+++ django/trunk/tests/regressiontests/test_runner/tests.py     2010-12-05 
00:44:34 UTC (rev 14822)
@@ -3,6 +3,7 @@
 """
 import StringIO
 
+from django.core.exceptions import ImproperlyConfigured
 from django.test import simple
 from django.utils import unittest
 
@@ -27,3 +28,93 @@
         result = dtr.run(suite)
         self.assertEqual(1, result.testsRun)
         self.assertEqual(1, len(result.failures))
+
+class DependencyOrderingTests(unittest.TestCase):
+
+    def test_simple_dependencies(self):
+        raw = [
+            ('s1', ['alpha']),
+            ('s2', ['bravo']),
+            ('s3', ['charlie']),
+        ]
+        dependencies = {
+            'alpha': ['charlie'],
+            'bravo': ['charlie'],
+        }
+
+        ordered = simple.dependency_ordered(raw, dependencies=dependencies)
+        ordered_sigs = [sig for sig,aliases in ordered]
+
+        self.assertIn('s1', ordered_sigs)
+        self.assertIn('s2', ordered_sigs)
+        self.assertIn('s3', ordered_sigs)
+        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s1'))
+        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s2'))
+
+    def test_chained_dependencies(self):
+        raw = [
+            ('s1', ['alpha']),
+            ('s2', ['bravo']),
+            ('s3', ['charlie']),
+        ]
+        dependencies = {
+            'alpha': ['bravo'],
+            'bravo': ['charlie'],
+        }
+
+        ordered = simple.dependency_ordered(raw, dependencies=dependencies)
+        ordered_sigs = [sig for sig,aliases in ordered]
+
+        self.assertIn('s1', ordered_sigs)
+        self.assertIn('s2', ordered_sigs)
+        self.assertIn('s3', ordered_sigs)
+
+        # Explicit dependencies
+        self.assertLess(ordered_sigs.index('s2'), ordered_sigs.index('s1'))
+        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s2'))
+
+        # Implied dependencies
+        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s1'))
+
+    def test_multiple_dependencies(self):
+        raw = [
+            ('s1', ['alpha']),
+            ('s2', ['bravo']),
+            ('s3', ['charlie']),
+            ('s4', ['delta']),
+        ]
+        dependencies = {
+            'alpha': ['bravo','delta'],
+            'bravo': ['charlie'],
+            'delta': ['charlie'],
+        }
+
+        ordered = simple.dependency_ordered(raw, dependencies=dependencies)
+        ordered_sigs = [sig for sig,aliases in ordered]
+
+        self.assertIn('s1', ordered_sigs)
+        self.assertIn('s2', ordered_sigs)
+        self.assertIn('s3', ordered_sigs)
+        self.assertIn('s4', ordered_sigs)
+
+        # Explicit dependencies
+        self.assertLess(ordered_sigs.index('s2'), ordered_sigs.index('s1'))
+        self.assertLess(ordered_sigs.index('s4'), ordered_sigs.index('s1'))
+        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s2'))
+        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s4'))
+
+        # Implicit dependencies
+        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s1'))
+
+    def test_circular_dependencies(self):
+        raw = [
+            ('s1', ['alpha']),
+            ('s2', ['bravo']),
+        ]
+        dependencies = {
+            'bravo': ['alpha'],
+            'alpha': ['bravo'],
+        }
+
+        self.assertRaises(ImproperlyConfigured, simple.dependency_ordered, 
raw, dependencies=dependencies)
+

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-upda...@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to