1
0
mirror of https://github.com/django/django.git synced 2025-03-12 10:22:37 +00:00

[5.2.x] Fixed #35967 -- Deferred test suite fixtures serialization after all dbs setup.

While the top-level objects fed to serialization are bound to the test
database being created nothing prevents code invoked during
serialization from performing queries against other connections entries
that haven't been swapped yet.

The reported example of that is a database router directing all reads to
a test mirror for a set of models involving auto-created many-to-many
fields. It might be tempting to address the many-to-many field case but
this a symptom of a larger problem where the test framework yields the
flow execution to user code that could interact with non-test databases
in unexpected ways.

Deferring test database fixture serialization until the point where all
connections entries have been swapped for their test equivalent ensures
that no code triggered during serialization can interact with non-test
databases.

Thanks Jake Howard for the report and Jacob Walls for the initial
investigation.

Backport of dc69a63f844b2ef3bc3371edde91644cf0bef0ee from main
This commit is contained in:
Simon Charette 2024-12-11 00:37:49 -05:00 committed by Mariusz Felisiak
parent c80c81163e
commit 923c5daa10
3 changed files with 17 additions and 5 deletions

View File

@ -39,6 +39,7 @@ class DatabaseClient(BaseDatabaseClient):
class DatabaseCreation(BaseDatabaseCreation):
create_test_db = ignore
destroy_test_db = ignore
serialize_db_to_string = ignore
class DatabaseIntrospection(BaseDatabaseIntrospection):

View File

@ -189,6 +189,7 @@ def setup_databases(
test_databases, mirrored_aliases = get_unique_databases_and_mirrors(aliases)
old_names = []
serialize_connections = []
for db_name, aliases in test_databases.values():
first_alias = None
@ -200,15 +201,14 @@ def setup_databases(
if first_alias is None:
first_alias = alias
with time_keeper.timed(" Creating '%s'" % alias):
serialize_alias = (
serialized_aliases is None or alias in serialized_aliases
)
connection.creation.create_test_db(
verbosity=verbosity,
autoclobber=not interactive,
keepdb=keepdb,
serialize=serialize_alias,
serialize=False,
)
if serialized_aliases is None or alias in serialized_aliases:
serialize_connections.append(connection)
if parallel > 1:
for index in range(parallel):
with time_keeper.timed(" Cloning '%s'" % alias):
@ -229,6 +229,16 @@ def setup_databases(
connections[mirror_alias].settings_dict
)
# Serialize content of test databases only once all of them are setup to
# account for database mirroring and routing during serialization. This
# slightly horrific process is so people who are testing on databases
# without transactions or using TransactionTestCase still get a clean
# database on every test run.
for serialize_connection in serialize_connections:
serialize_connection._test_serialized_contents = (
serialize_connection.creation.serialize_db_to_string()
)
if debug_sql:
for alias in connections:
connections[alias].force_debug_cursor = True

View File

@ -944,8 +944,9 @@ 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=True, keepdb=False
verbosity=0, autoclobber=False, serialize=False, keepdb=False
)
mocked_db_creation.return_value.serialize_db_to_string.assert_called_once_with()
@skipUnlessDBFeature("supports_sequence_reset")