[3.1.x] Fixed #32012 -- Made test database creation sync apps models when migrations are disabled.

Thanks Jaap Roes for the report.
Backport of 77caeaea88 from master
This commit is contained in:
Mariusz Felisiak 2020-09-23 10:54:04 +02:00
parent 2c629b37d5
commit fbb7881956
8 changed files with 74 additions and 6 deletions

View File

@ -58,7 +58,14 @@ class BaseDatabaseCreation:
settings.DATABASES[self.connection.alias]["NAME"] = test_database_name settings.DATABASES[self.connection.alias]["NAME"] = test_database_name
self.connection.settings_dict["NAME"] = test_database_name self.connection.settings_dict["NAME"] = test_database_name
if self.connection.settings_dict['TEST']['MIGRATE']: try:
if self.connection.settings_dict['TEST']['MIGRATE'] is False:
# Disable migrations for all apps.
old_migration_modules = settings.MIGRATION_MODULES
settings.MIGRATION_MODULES = {
app.label: None
for app in apps.get_app_configs()
}
# We report migrate messages at one level lower than that # We report migrate messages at one level lower than that
# requested. This ensures we don't get flooded with messages during # requested. This ensures we don't get flooded with messages during
# testing (unless you really ask to be flooded). # testing (unless you really ask to be flooded).
@ -69,6 +76,9 @@ class BaseDatabaseCreation:
database=self.connection.alias, database=self.connection.alias,
run_syncdb=True, run_syncdb=True,
) )
finally:
if self.connection.settings_dict['TEST']['MIGRATE'] is False:
settings.MIGRATION_MODULES = old_migration_modules
# We then serialize the current state of the database into a string # We then serialize the current state of the database into a string
# and store it on the connection. This slightly horrific process is so people # and store it on the connection. This slightly horrific process is so people

View File

@ -787,6 +787,8 @@ on :ref:`controlling the creation order of test databases
Default: ``True`` Default: ``True``
When set to ``False``, migrations won't run when creating the test database. When set to ``False``, migrations won't run when creating the test database.
This is similar to setting ``None`` as a value in :setting:`MIGRATION_MODULES`,
but for all apps.
.. setting:: TEST_MIRROR .. setting:: TEST_MIRROR

View File

@ -19,3 +19,7 @@ Bugfixes
* Fixed a regression in Django 3.1 where a queryset would crash if it contained * Fixed a regression in Django 3.1 where a queryset would crash if it contained
an aggregation and a ``Q`` object annotation (:ticket:`32007`). an aggregation and a ``Q`` object annotation (:ticket:`32007`).
* Fixed a bug in Django 3.1 where a test database was not synced during
creation when using the :setting:`MIGRATE <TEST_MIGRATE>` test database
setting (:ticket:`32012`).

View File

@ -0,0 +1,17 @@
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name='Foo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
],
),
]

View File

@ -0,0 +1,8 @@
from django.db import models
class Foo(models.Model):
name = models.CharField(max_length=255)
class Meta:
app_label = 'app_unmigrated'

View File

@ -6,6 +6,7 @@ from django.db.backends.base.creation import (
TEST_DATABASE_PREFIX, BaseDatabaseCreation, TEST_DATABASE_PREFIX, BaseDatabaseCreation,
) )
from django.test import SimpleTestCase, TransactionTestCase from django.test import SimpleTestCase, TransactionTestCase
from django.test.utils import override_settings
from ..models import ( from ..models import (
CircularA, CircularB, Object, ObjectReference, ObjectSelfReference, CircularA, CircularB, Object, ObjectReference, ObjectSelfReference,
@ -49,31 +50,57 @@ class TestDbSignatureTests(SimpleTestCase):
self.assertEqual(signature[3], test_name) self.assertEqual(signature[3], test_name)
@override_settings(INSTALLED_APPS=['backends.base.app_unmigrated'])
@mock.patch.object(connection, 'ensure_connection') @mock.patch.object(connection, 'ensure_connection')
@mock.patch('django.core.management.commands.migrate.Command.handle', return_value=None) @mock.patch.object(connection, 'prepare_database')
@mock.patch('django.db.migrations.recorder.MigrationRecorder.has_table', return_value=False)
@mock.patch('django.db.migrations.executor.MigrationExecutor.migrate')
@mock.patch('django.core.management.commands.migrate.Command.sync_apps')
class TestDbCreationTests(SimpleTestCase): class TestDbCreationTests(SimpleTestCase):
def test_migrate_test_setting_false(self, mocked_migrate, mocked_ensure_connection): available_apps = ['backends.base.app_unmigrated']
def test_migrate_test_setting_false(self, mocked_sync_apps, mocked_migrate, *mocked_objects):
test_connection = get_connection_copy() test_connection = get_connection_copy()
test_connection.settings_dict['TEST']['MIGRATE'] = False test_connection.settings_dict['TEST']['MIGRATE'] = False
creation = test_connection.creation_class(test_connection) 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'] old_database_name = test_connection.settings_dict['NAME']
try: try:
with mock.patch.object(creation, '_create_test_db'): 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, serialize=False)
mocked_migrate.assert_not_called() # Migrations don't run.
mocked_migrate.assert_called()
args, kwargs = mocked_migrate.call_args
self.assertEqual(args, ([],))
self.assertEqual(kwargs['plan'], [])
# App is synced.
mocked_sync_apps.assert_called()
mocked_args, _ = mocked_sync_apps.call_args
self.assertEqual(mocked_args[1], {'app_unmigrated'})
finally: finally:
with mock.patch.object(creation, '_destroy_test_db'): with mock.patch.object(creation, '_destroy_test_db'):
creation.destroy_test_db(old_database_name, verbosity=0) creation.destroy_test_db(old_database_name, verbosity=0)
def test_migrate_test_setting_true(self, mocked_migrate, mocked_ensure_connection): def test_migrate_test_setting_true(self, mocked_sync_apps, mocked_migrate, *mocked_objects):
test_connection = get_connection_copy() test_connection = get_connection_copy()
test_connection.settings_dict['TEST']['MIGRATE'] = True test_connection.settings_dict['TEST']['MIGRATE'] = True
creation = test_connection.creation_class(test_connection) 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'] old_database_name = test_connection.settings_dict['NAME']
try: try:
with mock.patch.object(creation, '_create_test_db'): 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, serialize=False)
mocked_migrate.assert_called_once() # Migrations run.
mocked_migrate.assert_called()
args, kwargs = mocked_migrate.call_args
self.assertEqual(args, ([('app_unmigrated', '0001_initial')],))
self.assertEqual(len(kwargs['plan']), 1)
# App is not synced.
mocked_sync_apps.assert_not_called()
finally: finally:
with mock.patch.object(creation, '_destroy_test_db'): with mock.patch.object(creation, '_destroy_test_db'):
creation.destroy_test_db(old_database_name, verbosity=0) creation.destroy_test_db(old_database_name, verbosity=0)