Loading django/test/simple.py +53 −13 Original line number Diff line number Diff line Loading @@ -2,12 +2,21 @@ import sys 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' Loading Loading @@ -183,6 +192,40 @@ def reorder_suite(suite, classes): 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): Loading Loading @@ -222,6 +265,7 @@ class DjangoTestSuiteRunner(object): # 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']: Loading @@ -239,20 +283,16 @@ class DjangoTestSuiteRunner(object): 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)) if 'TEST_DEPENDENCIES' in connection.settings_dict: dependencies[alias] = connection.settings_dict['TEST_DEPENDENCIES'] else: dbs_to_create.append((dbinfo, aliases)) if alias != 'default': dependencies[alias] = connection.settings_dict.get('TEST_DEPENDENCIES', ['default']) # Final pass -- actually create the databases. # 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)) Loading docs/topics/testing.txt +47 −0 Original line number Diff line number Diff line Loading @@ -454,6 +454,53 @@ will be redirected to point at ``default``. As a result, writes to 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 --------------------- Loading tests/regressiontests/test_runner/tests.py +91 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ Tests for django test runner """ import StringIO from django.core.exceptions import ImproperlyConfigured from django.test import simple from django.utils import unittest Loading @@ -27,3 +28,93 @@ class DjangoTestRunnerTests(unittest.TestCase): 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) Loading
django/test/simple.py +53 −13 Original line number Diff line number Diff line Loading @@ -2,12 +2,21 @@ import sys 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' Loading Loading @@ -183,6 +192,40 @@ def reorder_suite(suite, classes): 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): Loading Loading @@ -222,6 +265,7 @@ class DjangoTestSuiteRunner(object): # 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']: Loading @@ -239,20 +283,16 @@ class DjangoTestSuiteRunner(object): 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)) if 'TEST_DEPENDENCIES' in connection.settings_dict: dependencies[alias] = connection.settings_dict['TEST_DEPENDENCIES'] else: dbs_to_create.append((dbinfo, aliases)) if alias != 'default': dependencies[alias] = connection.settings_dict.get('TEST_DEPENDENCIES', ['default']) # Final pass -- actually create the databases. # 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)) Loading
docs/topics/testing.txt +47 −0 Original line number Diff line number Diff line Loading @@ -454,6 +454,53 @@ will be redirected to point at ``default``. As a result, writes to 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 --------------------- Loading
tests/regressiontests/test_runner/tests.py +91 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ Tests for django test runner """ import StringIO from django.core.exceptions import ImproperlyConfigured from django.test import simple from django.utils import unittest Loading @@ -27,3 +28,93 @@ class DjangoTestRunnerTests(unittest.TestCase): 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)