Refs #28478 -- Deprecated TestCase's allow_database_queries and multi_db in favor of databases.

This commit is contained in:
Simon Charette 2018-07-12 00:12:20 -04:00 committed by Tim Graham
parent 647be06538
commit 8c775391b7
27 changed files with 391 additions and 109 deletions

View File

@ -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."""
@ -1018,10 +1104,14 @@ class TestCase(TransactionTestCase):
transaction.set_rollback(True, using=db_name)
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()):

View File

@ -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

View File

@ -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__'``.

View File

@ -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 ``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.
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
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:

View File

@ -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):

View File

@ -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):

View File

@ -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):
"""

View File

@ -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

View File

@ -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):

View File

@ -8,7 +8,7 @@ from django.test import TestCase
class DatabaseCheckTests(TestCase):
multi_db = True
databases = {'default', 'other'}
@property
def func(self):

View File

@ -214,7 +214,7 @@ class TestRouter:
@override_settings(DATABASE_ROUTERS=[TestRouter()])
class ContentTypesMultidbTests(TestCase):
multi_db = True
databases = {'default', 'other'}
def test_multidb(self):
"""

View File

@ -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/'

View File

@ -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):

View File

@ -18,7 +18,7 @@ class MigrationTestBase(TransactionTestCase):
"""
available_apps = ["migrations"]
multi_db = True
databases = {'default', 'other'}
def tearDown(self):
# Reset applied-migrations state.

View File

@ -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):

View File

@ -16,7 +16,7 @@ class RecorderTests(TestCase):
"""
Tests recording migrations as applied or not.
"""
multi_db = True
databases = {'default', 'other'}
def test_apply(self):
"""

View File

@ -38,7 +38,7 @@ class MigrateWhenFooRouter:
class MultiDBOperationTests(OperationTestBase):
multi_db = True
databases = {'default', 'other'}
def _test_create_model(self, app_label, should_run):
"""

View File

@ -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):

View File

@ -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')

View File

@ -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):

View File

@ -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):

View File

@ -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")

View File

@ -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})

View File

@ -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()

View File

@ -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()

View File

@ -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):

View File

@ -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):
"""