From 2d34ebe49a25d0974392583d5bbd954baf742a32 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Tue, 17 Dec 2024 23:38:23 -0500 Subject: [PATCH] Refs #35967 -- Deprecated BaseDatabaseCreation.create_test_db(serialize). Given there are no longer any internal usages of serialize=True and it poses a risk to non-test databases integrity it seems appropriate to deprecate it. --- django/core/management/commands/testserver.py | 2 +- django/db/backends/base/creation.py | 20 ++++++-- django/test/utils.py | 1 - docs/internals/deprecation.txt | 3 ++ docs/releases/6.0.txt | 6 ++- docs/topics/testing/advanced.txt | 15 +++--- tests/backends/base/test_creation.py | 48 +++++++++++++++++-- tests/test_runner/tests.py | 2 +- 8 files changed, 78 insertions(+), 19 deletions(-) diff --git a/django/core/management/commands/testserver.py b/django/core/management/commands/testserver.py index a8a03764c9..ee472794f7 100644 --- a/django/core/management/commands/testserver.py +++ b/django/core/management/commands/testserver.py @@ -41,7 +41,7 @@ class Command(BaseCommand): # Create a test database. db_name = connection.creation.create_test_db( - verbosity=verbosity, autoclobber=not interactive, serialize=False + verbosity=verbosity, autoclobber=not interactive ) # Import the fixture data into the test database. diff --git a/django/db/backends/base/creation.py b/django/db/backends/base/creation.py index 6856fdb596..845235bc72 100644 --- a/django/db/backends/base/creation.py +++ b/django/db/backends/base/creation.py @@ -1,5 +1,6 @@ import os import sys +import warnings from io import StringIO from django.apps import apps @@ -7,6 +8,7 @@ from django.conf import settings from django.core import serializers from django.db import router from django.db.transaction import atomic +from django.utils.deprecation import RemovedInDjango70Warning from django.utils.module_loading import import_string # The prefix to put on the default database name when creating @@ -29,8 +31,10 @@ class BaseDatabaseCreation: def log(self, msg): sys.stderr.write(msg + os.linesep) + # RemovedInDjango70Warning: When the deprecation ends, replace with: + # def create_test_db(self, verbosity=1, autoclobber=False, keepdb=False): def create_test_db( - self, verbosity=1, autoclobber=False, serialize=True, keepdb=False + self, verbosity=1, autoclobber=False, serialize=None, keepdb=False ): """ Create a test database, prompting the user for confirmation if the @@ -90,8 +94,18 @@ class BaseDatabaseCreation: # and store it on the connection. This slightly horrific process is so people # who are testing on databases without transactions or who are using # a TransactionTestCase still get a clean database on every test run. - if serialize: - self.connection._test_serialized_contents = self.serialize_db_to_string() + if serialize is not None: + warnings.warn( + "DatabaseCreation.create_test_db(serialize) is deprecated. Call " + "DatabaseCreation.serialize_test_db() once all test databases are set " + "up instead if you need fixtures persistence between tests.", + stacklevel=2, + category=RemovedInDjango70Warning, + ) + if serialize: + self.connection._test_serialized_contents = ( + self.serialize_db_to_string() + ) call_command("createcachetable", database=self.connection.alias) diff --git a/django/test/utils.py b/django/test/utils.py index a4e80b0b53..78bbb0cf65 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -205,7 +205,6 @@ def setup_databases( verbosity=verbosity, autoclobber=not interactive, keepdb=keepdb, - serialize=False, ) if serialized_aliases is None or alias in serialized_aliases: serialize_connections.append(connection) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index e29c554f95..0a8ccaa20a 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -15,6 +15,9 @@ about each item can often be found in the release notes of two versions prior. See the :ref:`Django 6.0 release notes ` for more details on these changes. +* The ``serialize`` keyword argument of + ``BaseDatabaseCreation.create_test_db()`` will be removed. + .. _deprecation-removed-in-6.1: 6.1 diff --git a/docs/releases/6.0.txt b/docs/releases/6.0.txt index 4e5f3c1f02..abc56e6886 100644 --- a/docs/releases/6.0.txt +++ b/docs/releases/6.0.txt @@ -235,7 +235,8 @@ Database backend API This section describes changes that may be needed in third-party database backends. -* ... +* ``BaseDatabaseCreation.create_test_db(serialize)`` is deprecated. Use + ``serialize_db_to_string()`` instead. Dropped support for MariaDB 10.5 -------------------------------- @@ -278,7 +279,8 @@ Features deprecated in 6.0 Miscellaneous ------------- -* ... +* ``BaseDatabaseCreation.create_test_db(serialize)`` is deprecated. Use + ``serialize_db_to_string()`` instead. Features removed in 6.0 ======================= diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index c2d1fc6c6e..79d3f97f48 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -796,7 +796,7 @@ utility methods in the ``django.test.utils`` module. The creation module of the database backend also provides some utilities that can be useful during testing. -.. function:: create_test_db(verbosity=1, autoclobber=False, serialize=True, keepdb=False) +.. function:: create_test_db(verbosity=1, autoclobber=False, keepdb=False) Creates a new test database and runs ``migrate`` against it. @@ -812,12 +812,6 @@ can be useful during testing. * If ``autoclobber`` is ``True``, the database will be destroyed without consulting the user. - ``serialize`` determines if Django serializes the database into an - in-memory JSON string before running tests (used to restore 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 - with :ref:`serialized_rollback=True `. - ``keepdb`` determines if the test run should use an existing database, or create a new one. If ``True``, the existing database will be used, or created if not present. If ``False``, @@ -830,6 +824,13 @@ can be useful during testing. :setting:`NAME` in :setting:`DATABASES` to match the name of the test database. + .. deprecated:: 6.0 + + The ``serialize`` keyword argument is deprecated. Passing + ``serialize=True`` would automatically call + :func:`serialize_db_to_string` but it was deprecated as it could result + in queries against non-test databases during serialization. + .. function:: destroy_test_db(old_database_name, verbosity=1, keepdb=False) Destroys the database whose name is the value of :setting:`NAME` in diff --git a/tests/backends/base/test_creation.py b/tests/backends/base/test_creation.py index 7e760e8884..202d8e1b40 100644 --- a/tests/backends/base/test_creation.py +++ b/tests/backends/base/test_creation.py @@ -7,6 +7,7 @@ from django.db import DEFAULT_DB_ALIAS, connection, connections from django.db.backends.base.creation import TEST_DATABASE_PREFIX, BaseDatabaseCreation from django.test import SimpleTestCase, TransactionTestCase from django.test.utils import override_settings +from django.utils.deprecation import RemovedInDjango70Warning from ..models import ( CircularA, @@ -79,7 +80,7 @@ class TestDbCreationTests(SimpleTestCase): old_database_name = test_connection.settings_dict["NAME"] try: with mock.patch.object(creation, "_create_test_db"): - creation.create_test_db(verbosity=0, autoclobber=True, serialize=False) + creation.create_test_db(verbosity=0, autoclobber=True) # Migrations don't run. mocked_migrate.assert_called() args, kwargs = mocked_migrate.call_args @@ -109,7 +110,7 @@ class TestDbCreationTests(SimpleTestCase): old_database_name = test_connection.settings_dict["NAME"] try: with mock.patch.object(creation, "_create_test_db"): - creation.create_test_db(verbosity=0, autoclobber=True, serialize=False) + creation.create_test_db(verbosity=0, autoclobber=True) # The django_migrations table is not created. mocked_ensure_schema.assert_not_called() # App is synced. @@ -133,7 +134,7 @@ class TestDbCreationTests(SimpleTestCase): old_database_name = test_connection.settings_dict["NAME"] try: with mock.patch.object(creation, "_create_test_db"): - creation.create_test_db(verbosity=0, autoclobber=True, serialize=False) + creation.create_test_db(verbosity=0, autoclobber=True) # Migrations run. mocked_migrate.assert_called() args, kwargs = mocked_migrate.call_args @@ -163,12 +164,51 @@ class TestDbCreationTests(SimpleTestCase): old_database_name = test_connection.settings_dict["NAME"] try: with mock.patch.object(creation, "_create_test_db"): - creation.create_test_db(verbosity=0, autoclobber=True, serialize=False) + creation.create_test_db(verbosity=0, autoclobber=True) self.assertIs(mark_expected_failures_and_skips.called, False) finally: with mock.patch.object(creation, "_destroy_test_db"): creation.destroy_test_db(old_database_name, verbosity=0) + @mock.patch("django.db.migrations.executor.MigrationExecutor.migrate") + @mock.patch.object(BaseDatabaseCreation, "serialize_db_to_string") + def test_serialize_deprecation(self, serialize_db_to_string, *mocked_objects): + test_connection = get_connection_copy() + creation = test_connection.creation_class(test_connection) + if connection.vendor == "oracle": + # Don't close connection on Oracle. + creation.connection.close = mock.Mock() + old_database_name = test_connection.settings_dict["NAME"] + msg = ( + "DatabaseCreation.create_test_db(serialize) is deprecated. Call " + "DatabaseCreation.serialize_test_db() once all test databases are set up " + "instead if you need fixtures persistence between tests." + ) + try: + with ( + self.assertWarnsMessage(RemovedInDjango70Warning, msg) as ctx, + mock.patch.object(creation, "_create_test_db"), + ): + creation.create_test_db(verbosity=0, serialize=True) + self.assertEqual(ctx.filename, __file__) + serialize_db_to_string.assert_called_once_with() + finally: + with mock.patch.object(creation, "_destroy_test_db"): + creation.destroy_test_db(old_database_name, verbosity=0) + # Now with `serialize` False. + serialize_db_to_string.reset_mock() + try: + with ( + self.assertWarnsMessage(RemovedInDjango70Warning, msg) as ctx, + mock.patch.object(creation, "_create_test_db"), + ): + creation.create_test_db(verbosity=0, serialize=False) + self.assertEqual(ctx.filename, __file__) + serialize_db_to_string.assert_not_called() + finally: + with mock.patch.object(creation, "_destroy_test_db"): + creation.destroy_test_db(old_database_name, verbosity=0) + class TestDeserializeDbFromString(TransactionTestCase): available_apps = ["backends"] diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py index 4209e47ebb..a9fadc872b 100644 --- a/tests/test_runner/tests.py +++ b/tests/test_runner/tests.py @@ -931,7 +931,7 @@ class SetupDatabasesTests(unittest.TestCase): with mock.patch("django.test.utils.connections", new=tested_connections): self.runner_instance.setup_databases() mocked_db_creation.return_value.create_test_db.assert_called_once_with( - verbosity=0, autoclobber=False, serialize=False, keepdb=False + verbosity=0, autoclobber=False, keepdb=False ) mocked_db_creation.return_value.serialize_db_to_string.assert_called_once_with()