summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2018-07-12 00:12:20 -0400
committerTim Graham <timograham@gmail.com>2019-01-10 19:11:21 -0500
commit8c775391b78b2a4a2b57c5e89ed4888f36aada4b (patch)
tree3daeb3ef031be73079bd56e7c83f0f8f974d8f60
parent647be06538474078ac79c1338f02f5d9bc56a79b (diff)
Refs #28478 -- Deprecated TestCase's allow_database_queries and multi_db in favor of databases.
-rw-r--r--django/test/testcases.py180
-rw-r--r--docs/internals/deprecation.txt3
-rw-r--r--docs/releases/2.2.txt9
-rw-r--r--docs/topics/testing/tools.txt87
-rw-r--r--tests/admin_views/test_multidb.py2
-rw-r--r--tests/auth_tests/test_admin_multidb.py2
-rw-r--r--tests/auth_tests/test_management.py4
-rw-r--r--tests/auth_tests/test_models.py2
-rw-r--r--tests/cache/tests.py2
-rw-r--r--tests/check_framework/test_database.py2
-rw-r--r--tests/contenttypes_tests/test_models.py2
-rw-r--r--tests/context_processors/tests.py2
-rw-r--r--tests/gis_tests/layermap/tests.py2
-rw-r--r--tests/migrations/test_base.py2
-rw-r--r--tests/migrations/test_commands.py2
-rw-r--r--tests/migrations/test_loader.py2
-rw-r--r--tests/migrations/test_multidb.py2
-rw-r--r--tests/multiple_database/tests.py22
-rw-r--r--tests/prefetch_related/tests.py2
-rw-r--r--tests/servers/tests.py6
-rw-r--r--tests/sites_tests/tests.py4
-rw-r--r--tests/test_runner/tests.py2
-rw-r--r--tests/test_utils/test_deprecated_features.py64
-rw-r--r--tests/test_utils/test_testcase.py11
-rw-r--r--tests/test_utils/test_transactiontestcase.py20
-rw-r--r--tests/test_utils/tests.py60
-rw-r--r--tests/view_tests/tests/test_debug.py2
27 files changed, 391 insertions, 109 deletions
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 36986185ce..f820684c87 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -4,9 +4,11 @@ import posixpath
import sys
import threading
import unittest
+import warnings
from collections import Counter
from contextlib import contextmanager
from copy import copy
+from difflib import get_close_matches
from functools import wraps
from unittest.util import safe_repr
from urllib.parse import (
@@ -17,7 +19,7 @@ from urllib.request import url2pathname
from django.apps import apps
from django.conf import settings
from django.core import mail
-from django.core.exceptions import ValidationError
+from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.files import locks
from django.core.handlers.wsgi import WSGIHandler, get_path_info
from django.core.management import call_command
@@ -36,6 +38,7 @@ from django.test.utils import (
override_settings,
)
from django.utils.decorators import classproperty
+from django.utils.deprecation import RemovedInDjango31Warning
from django.views.static import serve
__all__ = ('TestCase', 'TransactionTestCase',
@@ -133,16 +136,31 @@ class _AssertTemplateNotUsedContext(_AssertTemplateUsedContext):
class _CursorFailure:
- def __init__(self, cls_name, wrapped):
- self.cls_name = cls_name
+ def __init__(self, wrapped, message):
self.wrapped = wrapped
+ self.message = message
def __call__(self):
- raise AssertionError(
- "Database queries aren't allowed in SimpleTestCase. "
- "Either use TestCase or TransactionTestCase to ensure proper test isolation or "
- "set %s.allow_database_queries to True to silence this failure." % self.cls_name
- )
+ raise AssertionError(self.message)
+
+
+class _SimpleTestCaseDatabasesDescriptor:
+ """Descriptor for SimpleTestCase.allow_database_queries deprecation."""
+ def __get__(self, instance, cls=None):
+ try:
+ allow_database_queries = cls.allow_database_queries
+ except AttributeError:
+ pass
+ else:
+ msg = (
+ '`SimpleTestCase.allow_database_queries` is deprecated. '
+ 'Restrict the databases available during the execution of '
+ '%s.%s with the `databases` attribute instead.'
+ ) % (cls.__module__, cls.__qualname__)
+ warnings.warn(msg, RemovedInDjango31Warning)
+ if allow_database_queries:
+ return {DEFAULT_DB_ALIAS}
+ return set()
class SimpleTestCase(unittest.TestCase):
@@ -153,9 +171,13 @@ class SimpleTestCase(unittest.TestCase):
_overridden_settings = None
_modified_settings = None
- # Tests shouldn't be allowed to query the database since
- # this base class doesn't enforce any isolation.
- allow_database_queries = False
+ databases = _SimpleTestCaseDatabasesDescriptor()
+ _disallowed_database_msg = (
+ 'Database queries are not allowed in SimpleTestCase subclasses. '
+ 'Either subclass TestCase or TransactionTestCase to ensure proper '
+ 'test isolation or add %(alias)r to %(test)s.databases to silence '
+ 'this failure.'
+ )
@classmethod
def setUpClass(cls):
@@ -166,19 +188,51 @@ class SimpleTestCase(unittest.TestCase):
if cls._modified_settings:
cls._cls_modified_context = modify_settings(cls._modified_settings)
cls._cls_modified_context.enable()
- if not cls.allow_database_queries:
- for alias in connections:
- connection = connections[alias]
- connection.cursor = _CursorFailure(cls.__name__, connection.cursor)
- connection.chunked_cursor = _CursorFailure(cls.__name__, connection.chunked_cursor)
+ cls._add_cursor_failures()
+
+ @classmethod
+ def _validate_databases(cls):
+ if cls.databases == '__all__':
+ return frozenset(connections)
+ for alias in cls.databases:
+ if alias not in connections:
+ message = '%s.%s.databases refers to %r which is not defined in settings.DATABASES.' % (
+ cls.__module__,
+ cls.__qualname__,
+ alias,
+ )
+ close_matches = get_close_matches(alias, list(connections))
+ if close_matches:
+ message += ' Did you mean %r?' % close_matches[0]
+ raise ImproperlyConfigured(message)
+ return frozenset(cls.databases)
+
+ @classmethod
+ def _add_cursor_failures(cls):
+ cls.databases = cls._validate_databases()
+ for alias in connections:
+ if alias in cls.databases:
+ continue
+ connection = connections[alias]
+ message = cls._disallowed_database_msg % {
+ 'test': '%s.%s' % (cls.__module__, cls.__qualname__),
+ 'alias': alias,
+ }
+ connection.cursor = _CursorFailure(connection.cursor, message)
+ connection.chunked_cursor = _CursorFailure(connection.chunked_cursor, message)
+
+ @classmethod
+ def _remove_cursor_failures(cls):
+ for alias in connections:
+ if alias in cls.databases:
+ continue
+ connection = connections[alias]
+ connection.cursor = connection.cursor.wrapped
+ connection.chunked_cursor = connection.chunked_cursor.wrapped
@classmethod
def tearDownClass(cls):
- if not cls.allow_database_queries:
- for alias in connections:
- connection = connections[alias]
- connection.cursor = connection.cursor.wrapped
- connection.chunked_cursor = connection.chunked_cursor.wrapped
+ cls._remove_cursor_failures()
if hasattr(cls, '_cls_modified_context'):
cls._cls_modified_context.disable()
delattr(cls, '_cls_modified_context')
@@ -806,6 +860,26 @@ class SimpleTestCase(unittest.TestCase):
self.fail(self._formatMessage(msg, standardMsg))
+class _TransactionTestCaseDatabasesDescriptor:
+ """Descriptor for TransactionTestCase.multi_db deprecation."""
+ msg = (
+ '`TransactionTestCase.multi_db` is deprecated. Databases available '
+ 'during this test can be defined using %s.%s.databases.'
+ )
+
+ def __get__(self, instance, cls=None):
+ try:
+ multi_db = cls.multi_db
+ except AttributeError:
+ pass
+ else:
+ msg = self.msg % (cls.__module__, cls.__qualname__)
+ warnings.warn(msg, RemovedInDjango31Warning)
+ if multi_db:
+ return set(connections)
+ return {DEFAULT_DB_ALIAS}
+
+
class TransactionTestCase(SimpleTestCase):
# Subclasses can ask for resetting of auto increment sequence before each
@@ -818,8 +892,12 @@ class TransactionTestCase(SimpleTestCase):
# Subclasses can define fixtures which will be automatically installed.
fixtures = None
- # Do the tests in this class query non-default databases?
- multi_db = False
+ databases = _TransactionTestCaseDatabasesDescriptor()
+ _disallowed_database_msg = (
+ 'Database queries to %(alias)r are not allowed in this test. Add '
+ '%(alias)r to %(test)s.databases to ensure proper test isolation '
+ 'and silence this failure.'
+ )
# If transactions aren't available, Django will serialize the database
# contents into a fixture during setup and flush and reload them
@@ -827,10 +905,6 @@ class TransactionTestCase(SimpleTestCase):
# This can be slow; this flag allows enabling on a per-case basis.
serialized_rollback = False
- # Since tests will be wrapped in a transaction, or serialized if they
- # are not available, we allow queries to be run.
- allow_database_queries = True
-
def _pre_setup(self):
"""
Perform pre-test setup:
@@ -870,15 +944,13 @@ class TransactionTestCase(SimpleTestCase):
@classmethod
def _databases_names(cls, include_mirrors=True):
- # If the test case has a multi_db=True flag, act on all databases,
- # including mirrors or not. Otherwise, just on the default DB.
- if cls.multi_db:
- return [
- alias for alias in connections
- if include_mirrors or not connections[alias].settings_dict['TEST']['MIRROR']
- ]
- else:
- return [DEFAULT_DB_ALIAS]
+ # Only consider allowed database aliases, including mirrors or not.
+ return [
+ alias for alias in connections
+ if alias in cls.databases and (
+ include_mirrors or not connections[alias].settings_dict['TEST']['MIRROR']
+ )
+ ]
def _reset_sequences(self, db_name):
conn = connections[db_name]
@@ -984,9 +1056,21 @@ class TransactionTestCase(SimpleTestCase):
func(*args, **kwargs)
-def connections_support_transactions():
- """Return True if all connections support transactions."""
- return all(conn.features.supports_transactions for conn in connections.all())
+def connections_support_transactions(aliases=None):
+ """
+ Return whether or not all (or specified) connections support
+ transactions.
+ """
+ conns = connections.all() if aliases is None else (connections[alias] for alias in aliases)
+ return all(conn.features.supports_transactions for conn in conns)
+
+
+class _TestCaseDatabasesDescriptor(_TransactionTestCaseDatabasesDescriptor):
+ """Descriptor for TestCase.multi_db deprecation."""
+ msg = (
+ '`TestCase.multi_db` is deprecated. Databases available during this '
+ 'test can be defined using %s.%s.databases.'
+ )
class TestCase(TransactionTestCase):
@@ -1002,6 +1086,8 @@ class TestCase(TransactionTestCase):
On database backends with no transaction support, TestCase behaves as
TransactionTestCase.
"""
+ databases = _TestCaseDatabasesDescriptor()
+
@classmethod
def _enter_atomics(cls):
"""Open atomic blocks for multiple databases."""
@@ -1019,9 +1105,13 @@ class TestCase(TransactionTestCase):
atomics[db_name].__exit__(None, None, None)
@classmethod
+ def _databases_support_transactions(cls):
+ return connections_support_transactions(cls.databases)
+
+ @classmethod
def setUpClass(cls):
super().setUpClass()
- if not connections_support_transactions():
+ if not cls._databases_support_transactions():
return
cls.cls_atomics = cls._enter_atomics()
@@ -1031,16 +1121,18 @@ class TestCase(TransactionTestCase):
call_command('loaddata', *cls.fixtures, **{'verbosity': 0, 'database': db_name})
except Exception:
cls._rollback_atomics(cls.cls_atomics)
+ cls._remove_cursor_failures()
raise
try:
cls.setUpTestData()
except Exception:
cls._rollback_atomics(cls.cls_atomics)
+ cls._remove_cursor_failures()
raise
@classmethod
def tearDownClass(cls):
- if connections_support_transactions():
+ if cls._databases_support_transactions():
cls._rollback_atomics(cls.cls_atomics)
for conn in connections.all():
conn.close()
@@ -1052,12 +1144,12 @@ class TestCase(TransactionTestCase):
pass
def _should_reload_connections(self):
- if connections_support_transactions():
+ if self._databases_support_transactions():
return False
return super()._should_reload_connections()
def _fixture_setup(self):
- if not connections_support_transactions():
+ if not self._databases_support_transactions():
# If the backend does not support transactions, we should reload
# class data before each test
self.setUpTestData()
@@ -1067,7 +1159,7 @@ class TestCase(TransactionTestCase):
self.atomics = self._enter_atomics()
def _fixture_teardown(self):
- if not connections_support_transactions():
+ if not self._databases_support_transactions():
return super()._fixture_teardown()
try:
for db_name in reversed(self._databases_names()):
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 0e85e59e8c..087458fa5e 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -32,6 +32,9 @@ details on these changes.
* ``RemoteUserBackend.configure_user()`` will require ``request`` as the first
positional argument.
+* Support for ``SimpleTestCase.allow_database_queries`` and
+ ``TransactionTestCase.multi_db`` will be removed.
+
.. _deprecation-removed-in-3.0:
3.0
diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt
index bc950eb88e..5c488570aa 100644
--- a/docs/releases/2.2.txt
+++ b/docs/releases/2.2.txt
@@ -513,3 +513,12 @@ Miscellaneous
* :meth:`.RemoteUserBackend.configure_user` is now passed ``request`` as the
first positional argument, if it accepts it. Support for overrides that don't
accept it will be removed in Django 3.1.
+
+* The :attr:`.SimpleTestCase.allow_database_queries`,
+ :attr:`.TransactionTestCase.multi_db`, and :attr:`.TestCase.multi_db`
+ attributes are deprecated in favor of :attr:`.SimpleTestCase.databases`,
+ :attr:`.TransactionTestCase.databases`, and :attr:`.TestCase.databases`.
+ These new attributes allow databases dependencies to be declared in order to
+ prevent unexpected queries against non-default databases to leak state
+ between tests. The previous behavior of ``allow_database_queries=True`` and
+ ``multi_db=True`` can be achieved by setting ``databases='__all__'``.
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index 38b437e18c..12f20c0144 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -722,14 +722,24 @@ A subclass of :class:`unittest.TestCase` that adds this functionality:
If your tests make any database queries, use subclasses
:class:`~django.test.TransactionTestCase` or :class:`~django.test.TestCase`.
-.. attribute:: SimpleTestCase.allow_database_queries
+.. attribute:: SimpleTestCase.databases
+
+ .. versionadded:: 2.2
:class:`~SimpleTestCase` disallows database queries by default. This
helps to avoid executing write queries which will affect other tests
since each ``SimpleTestCase`` test isn't run in a transaction. If you
aren't concerned about this problem, you can disable this behavior by
- setting the ``allow_database_queries`` class attribute to ``True`` on
- your test class.
+ setting the ``databases`` class attribute to ``'__all__'`` on your test
+ class.
+
+.. attribute:: SimpleTestCase.allow_database_queries
+
+ .. deprecated:: 2.2
+
+ This attribute is deprecated in favor of :attr:`databases`. The previous
+ behavior of ``allow_database_queries = True`` can be achieved by setting
+ ``databases = '__all__'``.
.. warning::
@@ -1101,8 +1111,8 @@ you can be certain that the outcome of a test will not be affected by another
test or by the order of test execution.
By default, fixtures are only loaded into the ``default`` database. If you are
-using multiple databases and set :attr:`multi_db=True
-<TransactionTestCase.multi_db>`, fixtures will be loaded into all databases.
+using multiple databases and set :attr:`TransactionTestCase.databases`,
+fixtures will be loaded into all specified databases.
URLconf configuration
---------------------
@@ -1119,7 +1129,9 @@ particular URL. Decorate your test class or test method with
Multi-database support
----------------------
-.. attribute:: TransactionTestCase.multi_db
+.. attribute:: TransactionTestCase.databases
+
+.. versionadded:: 2.2
Django sets up a test database corresponding to every database that is
defined in the :setting:`DATABASES` definition in your settings
@@ -1133,24 +1145,67 @@ don't need to test multi-database activity.
As an optimization, Django only flushes the ``default`` database at
the start of each test run. If your setup contains multiple databases,
and you have a test that requires every database to be clean, you can
-use the ``multi_db`` attribute on the test suite to request a full
-flush.
+use the ``databases`` attribute on the test suite to request extra databases
+to be flushed.
For example::
- class TestMyViews(TestCase):
- multi_db = True
+ class TestMyViews(TransactionTestCase):
+ databases = {'default', 'other'}
def test_index_page_view(self):
call_some_test_code()
-This test case will flush *all* the test databases before running
-``test_index_page_view``.
+This test case will flush the ``default`` and ``other`` test databases before
+running ``test_index_page_view``. You can also use ``'__all__'`` to specify
+that all of the test databases must be flushed.
+
+The ``databases`` flag also controls which databases the
+:attr:`TransactionTestCase.fixtures` are loaded into. By default, fixtures are
+only loaded into the ``default`` database.
+
+Queries against databases not in ``databases`` will give assertion errors to
+prevent state leaking between tests.
+
+.. attribute:: TransactionTestCase.multi_db
+
+.. deprecated:: 2.2
+
+This attribute is deprecated in favor of :attr:`~TransactionTestCase.databases`.
+The previous behavior of ``multi_db = True`` can be achieved by setting
+``databases = '__all__'``.
+
+.. attribute:: TestCase.databases
+
+.. versionadded:: 2.2
+
+By default, only the ``default`` database will be wrapped in a transaction
+during a ``TestCase``'s execution and attempts to query other databases will
+result in assertion errors to prevent state leaking between tests.
+
+Use the ``databases`` class attribute on the test class to request transaction
+wrapping against non-``default`` databases.
+
+For example::
+
+ class OtherDBTests(TestCase):
+ databases = {'other'}
+
+ def test_other_db_query(self):
+ ...
+
+This test will only allow queries against the ``other`` database. Just like for
+:attr:`SimpleTestCase.databases` and :attr:`TransactionTestCase.databases`, the
+``'__all__'`` constant can be used to specify that the test should allow
+queries to all databases.
+
+.. attribute:: TestCase.multi_db
+
+.. deprecated:: 2.2
-The ``multi_db`` flag also affects into which databases the
-:attr:`TransactionTestCase.fixtures` are loaded. By default (when
-``multi_db=False``), fixtures are only loaded into the ``default`` database.
-If ``multi_db=True``, fixtures are loaded into all databases.
+This attribute is deprecated in favor of :attr:`~TestCase.databases`. The
+previous behavior of ``multi_db = True`` can be achieved by setting
+``databases = '__all__'``.
.. _overriding-settings:
diff --git a/tests/admin_views/test_multidb.py b/tests/admin_views/test_multidb.py
index ec3591d1fe..a02b637d34 100644
--- a/tests/admin_views/test_multidb.py
+++ b/tests/admin_views/test_multidb.py
@@ -28,7 +28,7 @@ urlpatterns = [
@override_settings(ROOT_URLCONF=__name__, DATABASE_ROUTERS=['%s.Router' % __name__])
class MultiDatabaseTests(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
@classmethod
def setUpTestData(cls):
diff --git a/tests/auth_tests/test_admin_multidb.py b/tests/auth_tests/test_admin_multidb.py
index eff458de19..5849ef98e5 100644
--- a/tests/auth_tests/test_admin_multidb.py
+++ b/tests/auth_tests/test_admin_multidb.py
@@ -27,7 +27,7 @@ urlpatterns = [
@override_settings(ROOT_URLCONF=__name__, DATABASE_ROUTERS=['%s.Router' % __name__])
class MultiDatabaseTests(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
@classmethod
def setUpTestData(cls):
diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py
index b9bb092e78..87f2ae1790 100644
--- a/tests/auth_tests/test_management.py
+++ b/tests/auth_tests/test_management.py
@@ -213,7 +213,7 @@ class ChangepasswordManagementCommandTestCase(TestCase):
class MultiDBChangepasswordManagementCommandTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
@mock.patch.object(changepassword.Command, '_get_pass', return_value='not qwerty')
def test_that_changepassword_command_with_database_option_uses_given_db(self, mock_get_pass):
@@ -906,7 +906,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
class MultiDBCreatesuperuserTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def test_createsuperuser_command_with_database_option(self):
"""
diff --git a/tests/auth_tests/test_models.py b/tests/auth_tests/test_models.py
index 3c1b8fae64..dd3377d7a6 100644
--- a/tests/auth_tests/test_models.py
+++ b/tests/auth_tests/test_models.py
@@ -47,7 +47,7 @@ class LoadDataWithNaturalKeysTestCase(TestCase):
class LoadDataWithNaturalKeysAndMultipleDatabasesTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def test_load_data_with_user_permissions(self):
# Create test contenttypes for both databases
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index 6d957c54bb..3fbd057289 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -1085,7 +1085,7 @@ class DBCacheRouter:
},
)
class CreateCacheTableForDBCacheTests(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
@override_settings(DATABASE_ROUTERS=[DBCacheRouter()])
def test_createcachetable_observes_database_router(self):
diff --git a/tests/check_framework/test_database.py b/tests/check_framework/test_database.py
index 2dff3aaca4..06baf0e38d 100644
--- a/tests/check_framework/test_database.py
+++ b/tests/check_framework/test_database.py
@@ -8,7 +8,7 @@ from django.test import TestCase
class DatabaseCheckTests(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
@property
def func(self):
diff --git a/tests/contenttypes_tests/test_models.py b/tests/contenttypes_tests/test_models.py
index 0c263aabf0..91fdf8340f 100644
--- a/tests/contenttypes_tests/test_models.py
+++ b/tests/contenttypes_tests/test_models.py
@@ -214,7 +214,7 @@ class TestRouter:
@override_settings(DATABASE_ROUTERS=[TestRouter()])
class ContentTypesMultidbTests(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def test_multidb(self):
"""
diff --git a/tests/context_processors/tests.py b/tests/context_processors/tests.py
index 0baf806c1d..79b9ddef67 100644
--- a/tests/context_processors/tests.py
+++ b/tests/context_processors/tests.py
@@ -64,7 +64,7 @@ class DebugContextProcessorTests(TestCase):
"""
Tests for the ``django.template.context_processors.debug`` processor.
"""
- multi_db = True
+ databases = {'default', 'other'}
def test_debug(self):
url = '/debug/'
diff --git a/tests/gis_tests/layermap/tests.py b/tests/gis_tests/layermap/tests.py
index 460d6f6a4d..1efa643211 100644
--- a/tests/gis_tests/layermap/tests.py
+++ b/tests/gis_tests/layermap/tests.py
@@ -341,7 +341,7 @@ class OtherRouter:
@override_settings(DATABASE_ROUTERS=[OtherRouter()])
class LayerMapRouterTest(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
@unittest.skipUnless(len(settings.DATABASES) > 1, 'multiple databases required')
def test_layermapping_default_db(self):
diff --git a/tests/migrations/test_base.py b/tests/migrations/test_base.py
index 7fcbaffd24..970998f562 100644
--- a/tests/migrations/test_base.py
+++ b/tests/migrations/test_base.py
@@ -18,7 +18,7 @@ class MigrationTestBase(TransactionTestCase):
"""
available_apps = ["migrations"]
- multi_db = True
+ databases = {'default', 'other'}
def tearDown(self):
# Reset applied-migrations state.
diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
index c2891ed1a1..c231440949 100644
--- a/tests/migrations/test_commands.py
+++ b/tests/migrations/test_commands.py
@@ -25,7 +25,7 @@ class MigrateTests(MigrationTestBase):
"""
Tests running the migrate command.
"""
- multi_db = True
+ databases = {'default', 'other'}
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
def test_migrate(self):
diff --git a/tests/migrations/test_loader.py b/tests/migrations/test_loader.py
index a7666c5c2d..e3a635dc63 100644
--- a/tests/migrations/test_loader.py
+++ b/tests/migrations/test_loader.py
@@ -16,7 +16,7 @@ class RecorderTests(TestCase):
"""
Tests recording migrations as applied or not.
"""
- multi_db = True
+ databases = {'default', 'other'}
def test_apply(self):
"""
diff --git a/tests/migrations/test_multidb.py b/tests/migrations/test_multidb.py
index 0bed042964..b2c4320ad3 100644
--- a/tests/migrations/test_multidb.py
+++ b/tests/migrations/test_multidb.py
@@ -38,7 +38,7 @@ class MigrateWhenFooRouter:
class MultiDBOperationTests(OperationTestBase):
- multi_db = True
+ databases = {'default', 'other'}
def _test_create_model(self, app_label, should_run):
"""
diff --git a/tests/multiple_database/tests.py b/tests/multiple_database/tests.py
index b447be575c..a403b9fd88 100644
--- a/tests/multiple_database/tests.py
+++ b/tests/multiple_database/tests.py
@@ -17,7 +17,7 @@ from .routers import AuthRouter, TestRouter, WriteRouter
class QueryTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def test_db_selection(self):
"Querysets will use the default database by default"
@@ -998,7 +998,7 @@ class ConnectionRouterTestCase(SimpleTestCase):
# Make the 'other' database appear to be a replica of the 'default'
@override_settings(DATABASE_ROUTERS=[TestRouter()])
class RouterTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def test_db_selection(self):
"Querysets obey the router for db suggestions"
@@ -1526,7 +1526,7 @@ class RouterTestCase(TestCase):
@override_settings(DATABASE_ROUTERS=[AuthRouter()])
class AuthTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def test_auth_manager(self):
"The methods on the auth manager obey database hints"
@@ -1589,7 +1589,7 @@ class AntiPetRouter:
class FixtureTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
fixtures = ['multidb-common', 'multidb']
@override_settings(DATABASE_ROUTERS=[AntiPetRouter()])
@@ -1629,7 +1629,7 @@ class FixtureTestCase(TestCase):
class PickleQuerySetTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def test_pickling(self):
for db in connections:
@@ -1655,7 +1655,7 @@ class WriteToOtherRouter:
class SignalTests(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def override_router(self):
return override_settings(DATABASE_ROUTERS=[WriteToOtherRouter()])
@@ -1755,7 +1755,7 @@ class AttributeErrorRouter:
class RouterAttributeErrorTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def override_router(self):
return override_settings(DATABASE_ROUTERS=[AttributeErrorRouter()])
@@ -1807,7 +1807,7 @@ class ModelMetaRouter:
@override_settings(DATABASE_ROUTERS=[ModelMetaRouter()])
class RouterModelArgumentTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def test_m2m_collection(self):
b = Book.objects.create(title="Pro Django",
@@ -1845,7 +1845,7 @@ class MigrateTestCase(TestCase):
'django.contrib.auth',
'django.contrib.contenttypes'
]
- multi_db = True
+ databases = {'default', 'other'}
def test_migrate_to_other_database(self):
"""Regression test for #16039: migrate with --database option."""
@@ -1879,7 +1879,7 @@ class RouterUsed(Exception):
class RouteForWriteTestCase(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
class WriteCheckRouter:
def db_for_write(self, model, **hints):
@@ -2093,7 +2093,7 @@ class NoRelationRouter:
@override_settings(DATABASE_ROUTERS=[NoRelationRouter()])
class RelationAssignmentTests(SimpleTestCase):
"""allow_relation() is called with unsaved model instances."""
- multi_db = True
+ databases = {'default', 'other'}
router_prevents_msg = 'the current database router prevents this relation'
def test_foreign_key_relation(self):
diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py
index 9201ff3853..24982dda14 100644
--- a/tests/prefetch_related/tests.py
+++ b/tests/prefetch_related/tests.py
@@ -1155,7 +1155,7 @@ class NullableTest(TestCase):
class MultiDbTests(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
def test_using_is_honored_m2m(self):
B = Book.objects.using('other')
diff --git a/tests/servers/tests.py b/tests/servers/tests.py
index 5917e30d24..7f75b85d6c 100644
--- a/tests/servers/tests.py
+++ b/tests/servers/tests.py
@@ -209,8 +209,7 @@ class LiveServerPort(LiveServerBase):
"Acquired duplicate server addresses for server threads: %s" % self.live_server_url
)
finally:
- if hasattr(TestCase, 'server_thread'):
- TestCase.server_thread.terminate()
+ TestCase.tearDownClass()
def test_specified_port_bind(self):
"""LiveServerTestCase.port customizes the server's port."""
@@ -227,8 +226,7 @@ class LiveServerPort(LiveServerBase):
'Did not use specified port for LiveServerTestCase thread: %s' % TestCase.port
)
finally:
- if hasattr(TestCase, 'server_thread'):
- TestCase.server_thread.terminate()
+ TestCase.tearDownClass()
class LiverServerThreadedTests(LiveServerBase):
diff --git a/tests/sites_tests/tests.py b/tests/sites_tests/tests.py
index c5e20b4549..500a422b21 100644
--- a/tests/sites_tests/tests.py
+++ b/tests/sites_tests/tests.py
@@ -18,7 +18,7 @@ from django.test.utils import captured_stdout
@modify_settings(INSTALLED_APPS={'append': 'django.contrib.sites'})
class SitesFrameworkTests(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
@classmethod
def setUpTestData(cls):
@@ -236,7 +236,7 @@ class JustOtherRouter:
@modify_settings(INSTALLED_APPS={'append': 'django.contrib.sites'})
class CreateDefaultSiteTests(TestCase):
- multi_db = True
+ databases = {'default', 'other'}
@classmethod
def setUpTestData(cls):
diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py
index 477398da20..43c605eba6 100644
--- a/tests/test_runner/tests.py
+++ b/tests/test_runner/tests.py
@@ -241,8 +241,8 @@ class Ticket17477RegressionTests(AdminScriptTestCase):
class SQLiteInMemoryTestDbs(TransactionTestCase):
- multi_db = True
available_apps = ['test_runner']
+ databases = {'default', 'other'}
@unittest.skipUnless(all(db.connections[conn].vendor == 'sqlite' for conn in db.connections),
"This is an sqlite-specific issue")
diff --git a/tests/test_utils/test_deprecated_features.py b/tests/test_utils/test_deprecated_features.py
new file mode 100644
index 0000000000..fbed5e14c5
--- /dev/null
+++ b/tests/test_utils/test_deprecated_features.py
@@ -0,0 +1,64 @@
+from django.db import connections
+from django.db.utils import DEFAULT_DB_ALIAS
+from django.test import SimpleTestCase, TestCase, TransactionTestCase
+from django.utils.deprecation import RemovedInDjango31Warning
+
+
+class AllowDatabaseQueriesDeprecationTests(SimpleTestCase):
+ def test_enabled(self):
+ class AllowedDatabaseQueries(SimpleTestCase):
+ allow_database_queries = True
+ message = (
+ '`SimpleTestCase.allow_database_queries` is deprecated. Restrict '
+ 'the databases available during the execution of '
+ 'test_utils.test_deprecated_features.AllowDatabaseQueriesDeprecationTests.'
+ 'test_enabled.<locals>.AllowedDatabaseQueries with the '
+ '`databases` attribute instead.'
+ )
+ with self.assertWarnsMessage(RemovedInDjango31Warning, message):
+ self.assertEqual(AllowedDatabaseQueries.databases, {'default'})
+
+ def test_explicitly_disabled(self):
+ class AllowedDatabaseQueries(SimpleTestCase):
+ allow_database_queries = False
+ message = (
+ '`SimpleTestCase.allow_database_queries` is deprecated. Restrict '
+ 'the databases available during the execution of '
+ 'test_utils.test_deprecated_features.AllowDatabaseQueriesDeprecationTests.'
+ 'test_explicitly_disabled.<locals>.AllowedDatabaseQueries with '
+ 'the `databases` attribute instead.'
+ )
+ with self.assertWarnsMessage(RemovedInDjango31Warning, message):
+ self.assertEqual(AllowedDatabaseQueries.databases, set())
+
+
+class MultiDbDeprecationTests(SimpleTestCase):
+ def test_transaction_test_case(self):
+ class MultiDbTestCase(TransactionTestCase):
+ multi_db = True
+ message = (
+ '`TransactionTestCase.multi_db` is deprecated. Databases '
+ 'available during this test can be defined using '
+ 'test_utils.test_deprecated_features.MultiDbDeprecationTests.'
+ 'test_transaction_test_case.<locals>.MultiDbTestCase.databases.'
+ )
+ with self.assertWarnsMessage(RemovedInDjango31Warning, message):
+ self.assertEqual(MultiDbTestCase.databases, set(connections))
+ MultiDbTestCase.multi_db = False
+ with self.assertWarnsMessage(RemovedInDjango31Warning, message):
+ self.assertEqual(MultiDbTestCase.databases, {DEFAULT_DB_ALIAS})
+
+ def test_test_case(self):
+ class MultiDbTestCase(TestCase):
+ multi_db = True
+ message = (
+ '`TestCase.multi_db` is deprecated. Databases available during '
+ 'this test can be defined using '
+ 'test_utils.test_deprecated_features.MultiDbDeprecationTests.'
+ 'test_test_case.<locals>.MultiDbTestCase.databases.'
+ )
+ with self.assertWarnsMessage(RemovedInDjango31Warning, message):
+ self.assertEqual(MultiDbTestCase.databases, set(connections))
+ MultiDbTestCase.multi_db = False
+ with self.assertWarnsMessage(RemovedInDjango31Warning, message):
+ self.assertEqual(MultiDbTestCase.databases, {DEFAULT_DB_ALIAS})
diff --git a/tests/test_utils/test_testcase.py b/tests/test_utils/test_testcase.py
index 8a367391cb..f374549400 100644
--- a/tests/test_utils/test_testcase.py
+++ b/tests/test_utils/test_testcase.py
@@ -1,7 +1,7 @@
from django.db import IntegrityError, transaction
from django.test import TestCase, skipUnlessDBFeature
-from .models import PossessedCar
+from .models import Car, PossessedCar
class TestTestCase(TestCase):
@@ -18,3 +18,12 @@ class TestTestCase(TestCase):
car.delete()
finally:
self._rollback_atomics = rollback_atomics
+
+ def test_disallowed_database_queries(self):
+ message = (
+ "Database queries to 'other' are not allowed in this test. "
+ "Add 'other' to test_utils.test_testcase.TestTestCase.databases to "
+ "ensure proper test isolation and silence this failure."
+ )
+ with self.assertRaisesMessage(AssertionError, message):
+ Car.objects.using('other').get()
diff --git a/tests/test_utils/test_transactiontestcase.py b/tests/test_utils/test_transactiontestcase.py
index 40c9b7576f..3a9d173138 100644
--- a/tests/test_utils/test_transactiontestcase.py
+++ b/tests/test_utils/test_transactiontestcase.py
@@ -3,6 +3,8 @@ from unittest import mock
from django.db import connections
from django.test import TestCase, TransactionTestCase, override_settings
+from .models import Car
+
class TestSerializedRollbackInhibitsPostMigrate(TransactionTestCase):
"""
@@ -32,9 +34,9 @@ class TestSerializedRollbackInhibitsPostMigrate(TransactionTestCase):
@override_settings(DEBUG=True) # Enable query logging for test_queries_cleared
-class TransactionTestCaseMultiDbTests(TestCase):
+class TransactionTestCaseDatabasesTests(TestCase):
available_apps = []
- multi_db = True
+ databases = {'default', 'other'}
def test_queries_cleared(self):
"""
@@ -44,3 +46,17 @@ class TransactionTestCaseMultiDbTests(TestCase):
"""
for alias in connections:
self.assertEqual(len(connections[alias].queries_log), 0, 'Failed for alias %s' % alias)
+
+
+class DisallowedDatabaseQueriesTests(TransactionTestCase):
+ available_apps = ['test_utils']
+
+ def test_disallowed_database_queries(self):
+ message = (
+ "Database queries to 'other' are not allowed in this test. "
+ "Add 'other' to test_utils.test_transactiontestcase."
+ "DisallowedDatabaseQueriesTests.databases to ensure proper test "
+ "isolation and silence this failure."
+ )
+ with self.assertRaisesMessage(AssertionError, message):
+ Car.objects.using('other').get()
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
index e9aa9d9c98..c7e55e0711 100644
--- a/tests/test_utils/tests.py
+++ b/tests/test_utils/tests.py
@@ -7,8 +7,9 @@ from unittest import mock
from django.conf import settings
from django.contrib.staticfiles.finders import get_finder, get_finders
from django.contrib.staticfiles.storage import staticfiles_storage
+from django.core.exceptions import ImproperlyConfigured
from django.core.files.storage import default_storage
-from django.db import connection, models, router
+from django.db import connection, connections, models, router
from django.forms import EmailField, IntegerField
from django.http import HttpResponse
from django.template.loader import render_to_string
@@ -1160,32 +1161,67 @@ class TestBadSetUpTestData(TestCase):
class DisallowedDatabaseQueriesTests(SimpleTestCase):
def test_disallowed_database_queries(self):
expected_message = (
- "Database queries aren't allowed in SimpleTestCase. "
- "Either use TestCase or TransactionTestCase to ensure proper test isolation or "
- "set DisallowedDatabaseQueriesTests.allow_database_queries to True to silence this failure."
+ "Database queries are not allowed in SimpleTestCase subclasses. "
+ "Either subclass TestCase or TransactionTestCase to ensure proper "
+ "test isolation or add 'default' to "
+ "test_utils.tests.DisallowedDatabaseQueriesTests.databases to "
+ "silence this failure."
)
with self.assertRaisesMessage(AssertionError, expected_message):
Car.objects.first()
-
-class DisallowedDatabaseQueriesChunkedCursorsTests(SimpleTestCase):
- def test_disallowed_database_queries(self):
+ def test_disallowed_database_chunked_cursor_queries(self):
expected_message = (
- "Database queries aren't allowed in SimpleTestCase. Either use "
- "TestCase or TransactionTestCase to ensure proper test isolation or "
- "set DisallowedDatabaseQueriesChunkedCursorsTests.allow_database_queries "
- "to True to silence this failure."
+ "Database queries are not allowed in SimpleTestCase subclasses. "
+ "Either subclass TestCase or TransactionTestCase to ensure proper "
+ "test isolation or add 'default' to "
+ "test_utils.tests.DisallowedDatabaseQueriesTests.databases to "
+ "silence this failure."
)
with self.assertRaisesMessage(AssertionError, expected_message):
next(Car.objects.iterator())
class AllowedDatabaseQueriesTests(SimpleTestCase):
- allow_database_queries = True
+ databases = {'default'}
def test_allowed_database_queries(self):
Car.objects.first()
+ def test_allowed_database_chunked_cursor_queries(self):
+ next(Car.objects.iterator(), None)
+
+
+class DatabaseAliasTests(SimpleTestCase):
+ def setUp(self):
+ self.addCleanup(setattr, self.__class__, 'databases', self.databases)
+
+ def test_no_close_match(self):
+ self.__class__.databases = {'void'}
+ message = (
+ "test_utils.tests.DatabaseAliasTests.databases refers to 'void' which is not defined "
+ "in settings.DATABASES."
+ )
+ with self.assertRaisesMessage(ImproperlyConfigured, message):
+ self._validate_databases()
+
+ def test_close_match(self):
+ self.__class__.databases = {'defualt'}
+ message = (
+ "test_utils.tests.DatabaseAliasTests.databases refers to 'defualt' which is not defined "
+ "in settings.DATABASES. Did you mean 'default'?"
+ )
+ with self.assertRaisesMessage(ImproperlyConfigured, message):
+ self._validate_databases()
+
+ def test_match(self):
+ self.__class__.databases = {'default', 'other'}
+ self.assertEqual(self._validate_databases(), frozenset({'default', 'other'}))
+
+ def test_all(self):
+ self.__class__.databases = '__all__'
+ self.assertEqual(self._validate_databases(), frozenset(connections))
+
@isolate_apps('test_utils', attr_name='class_apps')
class IsolatedAppsTests(SimpleTestCase):
diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py
index 4b90acd05c..db23d10d32 100644
--- a/tests/view_tests/tests/test_debug.py
+++ b/tests/view_tests/tests/test_debug.py
@@ -225,7 +225,7 @@ class DebugViewTests(SimpleTestCase):
class DebugViewQueriesAllowedTests(SimpleTestCase):
# May need a query to initialize MySQL connection
- allow_database_queries = True
+ databases = {'default'}
def test_handle_db_exception(self):
"""