Commit b0d9eaaa authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

[1.2.X] Fixed #14799 -- Provided a full solution for test database creation order problems.

Backport of r14822, r14823 and r14824 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14825 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent 778782ea
Loading
Loading
Loading
Loading
+51 −13
Original line number Diff line number Diff line
@@ -3,11 +3,18 @@ import signal
import unittest

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


try:
    all
except NameError:
    from django.utils.itercompat import all

# The module name for tests outside models.py
TEST_MODULE = 'tests'

@@ -222,6 +229,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):
@@ -260,6 +301,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']:
@@ -277,20 +319,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))
+14 −0
Original line number Diff line number Diff line
@@ -380,6 +380,20 @@ Only supported for the ``mysql`` backend (see the `MySQL manual`_ for details).

.. _MySQL manual: MySQL_

.. setting:: TEST_DEPENDENCIES

TEST_DEPENDENCIES
~~~~~~~~~~~~~~~~~

.. versionadded:: 1.2.4

Default: ``['default']``, for all databases other than ``default``,
which has no dependencies.

The creation-order dependencies of the database. See the documentation
on :ref:`controlling the creation order of test databases
<topics-testing-creation-dependencies>` for details.

.. setting:: TEST_MIRROR

TEST_MIRROR
+39 −0
Original line number Diff line number Diff line
==========================
Django 1.2.4 release notes
==========================

Welcome to Django 1.2.4!

This is the fourth "bugfix" release in the Django 1.2 series,
improving the stability and performance of the Django 1.2 codebase.

Django 1.2.4 maintains backwards compatibility with Django
1.2.3, but contain a number of fixes and other
improvements. Django 1.2.4 is a recommended upgrade for any
development or deployment currently using or targeting Django 1.2.

For full details on the new features, backwards incompatibilities, and
deprecated features in the 1.2 branch, see the :doc:`/releases/1.2`.

One new feature
===============

Ordinarily, a point release would not include new features, but in the
case of Django 1.2.4, we have made an exception to this rule.

One of the bugs fixed in Django 1.2.4 involves a set of
circumstances whereby a running a test suite on a multiple database
configuration could cause the original source database (i.e., the
actual production database) to be dropped, causing catastrophic loss
of data. In order to provide a fix for this problem, it was necessary
to introduce a new setting -- :setting:`TEST_DEPENDENCIES` -- that
allows you to define any creation order dependencies in your database
configuration.

Most users -- even users with multiple-database configurations -- need
not be concerned about the data loss bug, or the manual configuration of
:setting:`TEST_DEPENDENCIES`. See the `original problem report`_
documentation on :ref:`controlling the creation order of test
databases <topics-testing-creation-dependencies>` for details.

.. _original problem report: http://code.djangoproject.com/ticket/14415
 No newline at end of file
+1 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ Final releases
.. toctree::
   :maxdepth: 1

   1.2.4
   1.2.2
   1.2

+47 −0
Original line number Diff line number Diff line
@@ -422,6 +422,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.2.4

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