From 825aea26b2c05488ffebba5f7d07a83662bc8131 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 | 3 +- django/db/backends/base/creation.py | 19 ++++++-- django/test/utils.py | 1 - docs/internals/deprecation.txt | 3 ++ docs/releases/5.2.txt | 3 ++ docs/topics/testing/advanced.txt | 27 ++++++++--- tests/backends/base/test_creation.py | 48 +++++++++++++++++-- tests/test_runner/tests.py | 2 +- 8 files changed, 89 insertions(+), 17 deletions(-) diff --git a/django/core/management/commands/testserver.py b/django/core/management/commands/testserver.py index a8a03764c9..0438b5a74d 100644 --- a/django/core/management/commands/testserver.py +++ b/django/core/management/commands/testserver.py @@ -41,7 +41,8 @@ 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..148af8cec2 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 RemovedInDjango61Warning from django.utils.module_loading import import_string # The prefix to put on the default database name when creating @@ -30,7 +32,7 @@ class BaseDatabaseCreation: sys.stderr.write(msg + os.linesep) 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 +92,19 @@ 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 setup instead if you need fixtures persistence " + "between tests.", + stacklevel=2, + category=RemovedInDjango61Warning, + ) + 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 a3594bfb6d..fb97b95751 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 171d9ecbe3..d48c7ac9fd 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -22,6 +22,9 @@ details on these changes. ``django.contrib.auth.login()`` and ``django.contrib.auth.alogin()`` will be removed. +* The ``serialize`` keyword argument of ``BaseDatabaseCreation.create_test_db`` + will be removed. + .. _deprecation-removed-in-6.0: 6.0 diff --git a/docs/releases/5.2.txt b/docs/releases/5.2.txt index aaf47ff8e8..1ab5eb51b7 100644 --- a/docs/releases/5.2.txt +++ b/docs/releases/5.2.txt @@ -414,6 +414,9 @@ backends. * ``BaseDatabaseOperations.adapt_decimalfield_value()`` is now a no-op, simply returning the given value. +* ``BaseDatabaseCreation.create_test_db(serialize)`` is deprecated. Use + ``serialize_db_to_string`` instead. + :mod:`django.contrib.gis` ------------------------- diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index 6b03f0f82b..9a1eddf4dd 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -805,7 +805,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. @@ -821,12 +821,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``, @@ -851,6 +845,25 @@ can be useful during testing. If the ``keepdb`` argument is ``True``, then the connection to the database will be closed, but the database will not be destroyed. +.. function: serialize_db_to_string() + + Serializes the database into an in-memory JSON string that can be used to + restore the database state between tests if the backend doesn't support + transactions or if your suite contains test classes with + :ref:`serialized_rollback=True ` enabled. + + This function should only be called once all test databases have been + created as the serialization process could result in queries against + non-test databases depending on your + :ref:`routing configuration `. + +.. versionchanged:: 5.2 + + The ``create_test_db`` function use to accept a ``serialize`` + parameter that would automatically call ``serialize_db_to_string`` but + it was deprecated as it could result in queries against non-test databases + during serialization. + .. _topics-testing-code-coverage: Integration with ``coverage.py`` diff --git a/tests/backends/base/test_creation.py b/tests/backends/base/test_creation.py index 7e760e8884..0f536c45db 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 RemovedInDjango61Warning 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 setup instead if you need fixtures persistence between tests." + ) + try: + with ( + self.assertWarnsMessage(RemovedInDjango61Warning, 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(RemovedInDjango61Warning, 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 d58d47b96b..2a4fdc3fca 100644 --- a/tests/test_runner/tests.py +++ b/tests/test_runner/tests.py @@ -946,7 +946,7 @@ class SetupDatabasesTests(unittest.TestCase): ): 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()