mirror of
https://github.com/django/django.git
synced 2024-12-23 01:25:58 +00:00
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.
This commit is contained in:
parent
7e41a7a47d
commit
e9becbe303
@ -39,6 +39,7 @@ class DatabaseClient(BaseDatabaseClient):
|
|||||||
class DatabaseCreation(BaseDatabaseCreation):
|
class DatabaseCreation(BaseDatabaseCreation):
|
||||||
create_test_db = ignore
|
create_test_db = ignore
|
||||||
destroy_test_db = ignore
|
destroy_test_db = ignore
|
||||||
|
serialize_db_to_string = ignore
|
||||||
|
|
||||||
|
|
||||||
class DatabaseIntrospection(BaseDatabaseIntrospection):
|
class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
|
@ -189,6 +189,7 @@ def setup_databases(
|
|||||||
test_databases, mirrored_aliases = get_unique_databases_and_mirrors(aliases)
|
test_databases, mirrored_aliases = get_unique_databases_and_mirrors(aliases)
|
||||||
|
|
||||||
old_names = []
|
old_names = []
|
||||||
|
serialize_connections = []
|
||||||
|
|
||||||
for db_name, aliases in test_databases.values():
|
for db_name, aliases in test_databases.values():
|
||||||
first_alias = None
|
first_alias = None
|
||||||
@ -200,15 +201,14 @@ def setup_databases(
|
|||||||
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):
|
||||||
serialize_alias = (
|
|
||||||
serialized_aliases is None or alias in serialized_aliases
|
|
||||||
)
|
|
||||||
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=serialize_alias,
|
serialize=False,
|
||||||
)
|
)
|
||||||
|
if serialized_aliases is None or alias in serialized_aliases:
|
||||||
|
serialize_connections.append(connection)
|
||||||
if parallel > 1:
|
if parallel > 1:
|
||||||
for index in range(parallel):
|
for index in range(parallel):
|
||||||
with time_keeper.timed(" Cloning '%s'" % alias):
|
with time_keeper.timed(" Cloning '%s'" % alias):
|
||||||
@ -229,6 +229,16 @@ def setup_databases(
|
|||||||
connections[mirror_alias].settings_dict
|
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:
|
if debug_sql:
|
||||||
for alias in connections:
|
for alias in connections:
|
||||||
connections[alias].force_debug_cursor = True
|
connections[alias].force_debug_cursor = True
|
||||||
|
@ -938,14 +938,17 @@ class SetupDatabasesTests(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
with mock.patch(
|
with (
|
||||||
"django.db.backends.dummy.base.DatabaseWrapper.creation_class"
|
mock.patch(
|
||||||
) as mocked_db_creation:
|
"django.db.backends.dummy.base.DatabaseWrapper.creation_class"
|
||||||
with mock.patch("django.test.utils.connections", new=tested_connections):
|
) as mocked_db_creation,
|
||||||
self.runner_instance.setup_databases()
|
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(
|
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")
|
@skipUnlessDBFeature("supports_sequence_reset")
|
||||||
|
Loading…
Reference in New Issue
Block a user