Fixed #32446 -- Deprecated SERIALIZE test database setting.

Whether or not the state of a test database should be serialized can be
inferred from the set of databases allowed to be access from discovered
TestCase/TransactionTestCase enabling the serialized_rollback feature
which makes this setting unnecessary.

This should make a significant test suite bootstraping time difference
on large projects that didn't explicitly disable test database
serialization.
This commit is contained in:
Simon Charette 2021-02-14 22:42:47 -05:00 committed by Mariusz Felisiak
parent af685b5f00
commit 3089018e95
9 changed files with 103 additions and 20 deletions

View File

@ -682,14 +682,18 @@ class DiscoverRunner:
return len(result.failures) + len(result.errors) return len(result.failures) + len(result.errors)
def _get_databases(self, suite): def _get_databases(self, suite):
databases = set() databases = {}
for test in suite: for test in suite:
if isinstance(test, unittest.TestCase): if isinstance(test, unittest.TestCase):
test_databases = getattr(test, 'databases', None) test_databases = getattr(test, 'databases', None)
if test_databases == '__all__': if test_databases == '__all__':
return set(connections) test_databases = connections
if test_databases: if test_databases:
databases.update(test_databases) serialized_rollback = getattr(test, 'serialized_rollback', False)
databases.update(
(alias, serialized_rollback or databases.get(alias, False))
for alias in test_databases
)
else: else:
databases.update(self._get_databases(test)) databases.update(self._get_databases(test))
return databases return databases
@ -717,8 +721,15 @@ class DiscoverRunner:
self.setup_test_environment() self.setup_test_environment()
suite = self.build_suite(test_labels, extra_tests) suite = self.build_suite(test_labels, extra_tests)
databases = self.get_databases(suite) databases = self.get_databases(suite)
serialized_aliases = set(
alias
for alias, serialize in databases.items() if serialize
)
with self.time_keeper.timed('Total database setup'): with self.time_keeper.timed('Total database setup'):
old_config = self.setup_databases(aliases=databases) old_config = self.setup_databases(
aliases=databases,
serialized_aliases=serialized_aliases,
)
run_failed = False run_failed = False
try: try:
self.run_checks(databases) self.run_checks(databases)

View File

@ -25,6 +25,7 @@ from django.db.models.options import Options
from django.template import Template from django.template import Template
from django.test.signals import setting_changed, template_rendered from django.test.signals import setting_changed, template_rendered
from django.urls import get_script_prefix, set_script_prefix from django.urls import get_script_prefix, set_script_prefix
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.translation import deactivate from django.utils.translation import deactivate
try: try:
@ -156,8 +157,18 @@ def teardown_test_environment():
del mail.outbox del mail.outbox
def setup_databases(verbosity, interactive, *, time_keeper=None, keepdb=False, debug_sql=False, parallel=0, def setup_databases(
aliases=None, **kwargs): verbosity,
interactive,
*,
time_keeper=None,
keepdb=False,
debug_sql=False,
parallel=0,
aliases=None,
serialized_aliases=None,
**kwargs,
):
"""Create the test databases.""" """Create the test databases."""
if time_keeper is None: if time_keeper is None:
time_keeper = NullTimeKeeper() time_keeper = NullTimeKeeper()
@ -176,11 +187,29 @@ def setup_databases(verbosity, interactive, *, time_keeper=None, keepdb=False, d
if first_alias is None: if first_alias is None:
first_alias = alias first_alias = alias
with time_keeper.timed(" Creating '%s'" % alias): with time_keeper.timed(" Creating '%s'" % alias):
# RemovedInDjango50Warning: when the deprecation ends,
# replace with:
# serialize_alias = serialized_aliases is None or alias in serialized_aliases
try:
serialize_alias = connection.settings_dict['TEST']['SERIALIZE']
except KeyError:
serialize_alias = (
serialized_aliases is None or
alias in serialized_aliases
)
else:
warnings.warn(
'The SERIALIZE test database setting is '
'deprecated as it can be inferred from the '
'TestCase/TransactionTestCase.databases that '
'enable the serialized_rollback feature.',
category=RemovedInDjango50Warning,
)
connection.creation.create_test_db( connection.creation.create_test_db(
verbosity=verbosity, verbosity=verbosity,
autoclobber=not interactive, autoclobber=not interactive,
keepdb=keepdb, keepdb=keepdb,
serialize=connection.settings_dict['TEST'].get('SERIALIZE', True), serialize=serialize_alias,
) )
if parallel > 1: if parallel > 1:
for index in range(parallel): for index in range(parallel):

View File

@ -15,6 +15,8 @@ about each item can often be found in the release notes of two versions prior.
See the :ref:`Django 4.0 release notes <deprecated-features-4.0>` for more See the :ref:`Django 4.0 release notes <deprecated-features-4.0>` for more
details on these changes. details on these changes.
* The ``SERIALIZE`` test setting will be removed.
.. _deprecation-removed-in-4.1: .. _deprecation-removed-in-4.1:
4.1 4.1

View File

@ -821,6 +821,12 @@ the database state between tests if you don't have transactions). You can set
this to ``False`` to speed up creation time if you don't have any test classes this to ``False`` to speed up creation time if you don't have any test classes
with :ref:`serialized_rollback=True <test-case-serialized-rollback>`. with :ref:`serialized_rollback=True <test-case-serialized-rollback>`.
.. deprecated:: 4.0
This setting is deprecated as it can be inferred from the
:attr:`~django.test.TestCase.databases` with the
:ref:`serialized_rollback <test-case-serialized-rollback>` option enabled.
.. setting:: TEST_TEMPLATE .. setting:: TEST_TEMPLATE
``TEMPLATE`` ``TEMPLATE``

View File

@ -248,7 +248,11 @@ Templates
Tests Tests
~~~~~ ~~~~~
* ... * The new ``serialized_aliases`` argument of
:func:`django.test.utils.setup_databases` determines which
:setting:`DATABASES` aliases test databases should have their state
serialized to allow usage of the
:ref:`serialized_rollback <test-case-serialized-rollback>` feature.
URLs URLs
~~~~ ~~~~
@ -313,7 +317,9 @@ Features deprecated in 4.0
Miscellaneous Miscellaneous
------------- -------------
* ... * ``SERIALIZE`` test setting is deprecated as it can be inferred from the
:attr:`~django.test.TestCase.databases` with the
:ref:`serialized_rollback <test-case-serialized-rollback>` option enabled.
Features removed in 4.0 Features removed in 4.0
======================= =======================

View File

@ -718,7 +718,7 @@ utility methods in the ``django.test.utils`` module.
Performs global post-test teardown, such as removing instrumentation from Performs global post-test teardown, such as removing instrumentation from
the template system and restoring normal email services. the template system and restoring normal email services.
.. function:: setup_databases(verbosity, interactive, *, time_keeper=None, keepdb=False, debug_sql=False, parallel=0, aliases=None, **kwargs) .. function:: setup_databases(verbosity, interactive, *, time_keeper=None, keepdb=False, debug_sql=False, parallel=0, aliases=None, serialized_aliases=None, **kwargs)
Creates the test databases. Creates the test databases.
@ -730,11 +730,20 @@ utility methods in the ``django.test.utils`` module.
databases should be setup for. If it's not provided, it defaults to all of databases should be setup for. If it's not provided, it defaults to all of
:setting:`DATABASES` aliases. :setting:`DATABASES` aliases.
The ``serialized_aliases`` argument determines what subset of ``aliases``
test databases should have their state serialized to allow usage of the
:ref:`serialized_rollback <test-case-serialized-rollback>` feature. If
it's not provided, it defaults to ``aliases``.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
The ``time_keeper`` kwarg was added, and all kwargs were made The ``time_keeper`` kwarg was added, and all kwargs were made
keyword-only. keyword-only.
.. versionchanged:: 4.0
The ``serialized_aliases`` kwarg was added.
.. function:: teardown_databases(old_config, parallel=0, keepdb=False) .. function:: teardown_databases(old_config, parallel=0, keepdb=False)
Destroys the test databases, restoring pre-test conditions. Destroys the test databases, restoring pre-test conditions.

View File

@ -354,7 +354,7 @@ class DiscoverRunnerGetDatabasesTests(SimpleTestCase):
def assertSkippedDatabases(self, test_labels, expected_databases): def assertSkippedDatabases(self, test_labels, expected_databases):
databases, output = self.get_databases(test_labels) databases, output = self.get_databases(test_labels)
self.assertEqual(databases, expected_databases) self.assertEqual(databases, expected_databases)
skipped_databases = set(connections) - expected_databases skipped_databases = set(connections) - set(expected_databases)
if skipped_databases: if skipped_databases:
self.assertIn(self.skip_msg + ', '.join(sorted(skipped_databases)), output) self.assertIn(self.skip_msg + ', '.join(sorted(skipped_databases)), output)
else: else:
@ -362,31 +362,37 @@ class DiscoverRunnerGetDatabasesTests(SimpleTestCase):
def test_mixed(self): def test_mixed(self):
databases, output = self.get_databases(['test_runner_apps.databases.tests']) databases, output = self.get_databases(['test_runner_apps.databases.tests'])
self.assertEqual(databases, set(connections)) self.assertEqual(databases, {'default': True, 'other': False})
self.assertNotIn(self.skip_msg, output) self.assertNotIn(self.skip_msg, output)
def test_all(self): def test_all(self):
databases, output = self.get_databases(['test_runner_apps.databases.tests.AllDatabasesTests']) databases, output = self.get_databases(['test_runner_apps.databases.tests.AllDatabasesTests'])
self.assertEqual(databases, set(connections)) self.assertEqual(databases, {alias: False for alias in connections})
self.assertNotIn(self.skip_msg, output) self.assertNotIn(self.skip_msg, output)
def test_default_and_other(self): def test_default_and_other(self):
self.assertSkippedDatabases([ self.assertSkippedDatabases([
'test_runner_apps.databases.tests.DefaultDatabaseTests', 'test_runner_apps.databases.tests.DefaultDatabaseTests',
'test_runner_apps.databases.tests.OtherDatabaseTests', 'test_runner_apps.databases.tests.OtherDatabaseTests',
], {'default', 'other'}) ], {'default': False, 'other': False})
def test_default_only(self): def test_default_only(self):
self.assertSkippedDatabases([ self.assertSkippedDatabases([
'test_runner_apps.databases.tests.DefaultDatabaseTests', 'test_runner_apps.databases.tests.DefaultDatabaseTests',
], {'default'}) ], {'default': False})
def test_other_only(self): def test_other_only(self):
self.assertSkippedDatabases([ self.assertSkippedDatabases([
'test_runner_apps.databases.tests.OtherDatabaseTests' 'test_runner_apps.databases.tests.OtherDatabaseTests'
], {'other'}) ], {'other': False})
def test_no_databases_required(self): def test_no_databases_required(self):
self.assertSkippedDatabases([ self.assertSkippedDatabases([
'test_runner_apps.databases.tests.NoDatabaseTests' 'test_runner_apps.databases.tests.NoDatabaseTests'
], set()) ], {})
def test_serialize(self):
databases, _ = self.get_databases([
'test_runner_apps.databases.tests.DefaultDatabaseSerializedTests'
])
self.assertEqual(databases, {'default': True})

View File

@ -11,12 +11,15 @@ from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.management import call_command from django.core.management import call_command
from django.core.management.base import SystemCheckError from django.core.management.base import SystemCheckError
from django.test import TransactionTestCase, skipUnlessDBFeature from django.test import (
SimpleTestCase, TransactionTestCase, skipUnlessDBFeature,
)
from django.test.runner import DiscoverRunner from django.test.runner import DiscoverRunner
from django.test.testcases import connections_support_transactions from django.test.testcases import connections_support_transactions
from django.test.utils import ( from django.test.utils import (
captured_stderr, dependency_ordered, get_unique_databases_and_mirrors, captured_stderr, dependency_ordered, get_unique_databases_and_mirrors,
) )
from django.utils.deprecation import RemovedInDjango50Warning
from .models import B, Person, Through from .models import B, Person, Through
@ -315,7 +318,7 @@ class AliasedDefaultTestSetupTest(unittest.TestCase):
runner_instance.teardown_databases(old_config) runner_instance.teardown_databases(old_config)
class SetupDatabasesTests(unittest.TestCase): class SetupDatabasesTests(SimpleTestCase):
def setUp(self): def setUp(self):
self.runner_instance = DiscoverRunner(verbosity=0) self.runner_instance = DiscoverRunner(verbosity=0)
@ -398,8 +401,14 @@ class SetupDatabasesTests(unittest.TestCase):
'TEST': {'SERIALIZE': False}, 'TEST': {'SERIALIZE': False},
}, },
}) })
msg = (
'The SERIALIZE test database setting is deprecated as it can be '
'inferred from the TestCase/TransactionTestCase.databases that '
'enable the serialized_rollback feature.'
)
with mock.patch('django.db.backends.dummy.base.DatabaseWrapper.creation_class') as mocked_db_creation: with mock.patch('django.db.backends.dummy.base.DatabaseWrapper.creation_class') as mocked_db_creation:
with mock.patch('django.test.utils.connections', new=tested_connections): with mock.patch('django.test.utils.connections', new=tested_connections):
with self.assertWarnsMessage(RemovedInDjango50Warning, msg):
self.runner_instance.setup_databases() self.runner_instance.setup_databases()
mocked_db_creation.return_value.create_test_db.assert_called_once_with( mocked_db_creation.return_value.create_test_db.assert_called_once_with(
verbosity=0, autoclobber=False, serialize=False, keepdb=False verbosity=0, autoclobber=False, serialize=False, keepdb=False

View File

@ -10,6 +10,11 @@ class DefaultDatabaseTests(NoDatabaseTests):
databases = {'default'} databases = {'default'}
class DefaultDatabaseSerializedTests(NoDatabaseTests):
databases = {'default'}
serialized_rollback = True
class OtherDatabaseTests(NoDatabaseTests): class OtherDatabaseTests(NoDatabaseTests):
databases = {'other'} databases = {'other'}