Browse Source

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.
Simon Charette 3 months ago
parent
commit
2d34ebe49a

+ 1 - 1
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.

+ 17 - 3
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)
 

+ 0 - 1
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)

+ 3 - 0
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 <deprecated-features-6.0>` 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

+ 4 - 2
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
 =======================

+ 8 - 7
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 <test-case-serialized-rollback>`.
-
     ``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

+ 44 - 4
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"]

+ 1 - 1
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()