From f5ebdfce5c417f9844e86bccc2f12577064d4bad Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 12 Nov 2019 19:49:09 -0800 Subject: [PATCH] Fixed #25388 -- Added an option to allow disabling of migrations during test database creation. --- django/db/backends/base/creation.py | 21 ++++++++++---------- django/db/utils.py | 11 +++++++++-- docs/ref/django-admin.txt | 5 +++-- docs/ref/settings.txt | 19 +++++++++++++++--- docs/releases/3.1.txt | 3 +++ tests/backends/base/test_creation.py | 29 +++++++++++++++++++++++++++- 6 files changed, 70 insertions(+), 18 deletions(-) diff --git a/django/db/backends/base/creation.py b/django/db/backends/base/creation.py index f36d60a5fe..dd3407bac3 100644 --- a/django/db/backends/base/creation.py +++ b/django/db/backends/base/creation.py @@ -61,16 +61,17 @@ class BaseDatabaseCreation: settings.DATABASES[self.connection.alias]["NAME"] = test_database_name self.connection.settings_dict["NAME"] = test_database_name - # We report migrate messages at one level lower than that requested. - # This ensures we don't get flooded with messages during testing - # (unless you really ask to be flooded). - call_command( - 'migrate', - verbosity=max(verbosity - 1, 0), - interactive=False, - database=self.connection.alias, - run_syncdb=True, - ) + if self.connection.settings_dict['TEST']['MIGRATE']: + # We report migrate messages at one level lower than that + # requested. This ensures we don't get flooded with messages during + # testing (unless you really ask to be flooded). + call_command( + 'migrate', + verbosity=max(verbosity - 1, 0), + interactive=False, + database=self.connection.alias, + run_syncdb=True, + ) # 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 diff --git a/django/db/utils.py b/django/db/utils.py index 4bd119227f..28afa6cd07 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -194,8 +194,15 @@ class ConnectionHandler: raise ConnectionDoesNotExist("The connection %s doesn't exist" % alias) test_settings = conn.setdefault('TEST', {}) - for key in ['CHARSET', 'COLLATION', 'NAME', 'MIRROR']: - test_settings.setdefault(key, None) + default_test_settings = [ + ('CHARSET', None), + ('COLLATION', None), + ('MIGRATE', True), + ('MIRROR', None), + ('NAME', None), + ] + for key, value in default_test_settings: + test_settings.setdefault(key, value) def __getitem__(self, alias): if hasattr(self._connections, alias): diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 8203220159..c08fe44346 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1371,8 +1371,9 @@ Preserves the test database between test runs. This has the advantage of skipping both the create and destroy actions which can greatly decrease the time to run tests, especially those in a large test suite. If the test database does not exist, it will be created on the first run and then preserved for each -subsequent run. Any unapplied migrations will also be applied to the test -database before running the test suite. +subsequent run. Unless the :setting:`MIGRATE ` test setting is +``False``, any unapplied migrations will also be applied to the test database +before running the test suite. .. django-admin-option:: --reverse, -r diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 97b1584765..a32cc4e2a4 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -739,6 +739,17 @@ The creation-order dependencies of the database. See the documentation on :ref:`controlling the creation order of test databases ` for details. +.. setting:: TEST_MIGRATE + +``MIGRATE`` +^^^^^^^^^^^ + +.. versionadded:: 3.1 + +Default: ``True`` + +When set to ``False``, migrations won't run when creating the test database. + .. setting:: TEST_MIRROR ``MIRROR`` @@ -2034,9 +2045,11 @@ automatically create the package if it doesn't already exist. When you supply ``None`` as a value for an app, Django will consider the app as an app without migrations regardless of an existing ``migrations`` submodule. This can be used, for example, in a test settings file to skip migrations while -testing (tables will still be created for the apps' models). If this is used in -your general project settings, remember to use the :option:`migrate ---run-syncdb` option if you want to create tables for the app. +testing (tables will still be created for the apps' models). To disable +migrations for all apps during tests, you can set the +:setting:`MIGRATE ` to ``True`` instead. If ``MIGRATION_MODULES`` +is used in your general project settings, remember to use the +:option:`migrate --run-syncdb` option if you want to create tables for the app. .. setting:: MONTH_DAY_FORMAT diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 14e00bdf56..16f9ed01be 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -237,6 +237,9 @@ Tests allow running a test without collecting the result and catching exceptions. This can be used to support running tests under a debugger. +* The new :setting:`MIGRATE ` test database setting allows + disabling of migrations during a test database creation. + URLs ~~~~ diff --git a/tests/backends/base/test_creation.py b/tests/backends/base/test_creation.py index 519b3f049c..340eaafc89 100644 --- a/tests/backends/base/test_creation.py +++ b/tests/backends/base/test_creation.py @@ -1,6 +1,7 @@ import copy +from unittest import mock -from django.db import DEFAULT_DB_ALIAS, connections +from django.db import DEFAULT_DB_ALIAS, connection, connections from django.db.backends.base.creation import ( TEST_DATABASE_PREFIX, BaseDatabaseCreation, ) @@ -40,3 +41,29 @@ class TestDbSignatureTests(SimpleTestCase): test_connection.settings_dict['TEST'] = {'NAME': test_name} signature = BaseDatabaseCreation(test_connection).test_db_signature() self.assertEqual(signature[3], test_name) + + +@mock.patch.object(connection, 'ensure_connection') +@mock.patch('django.core.management.commands.migrate.Command.handle', return_value=None) +class TestDbCreationTests(SimpleTestCase): + def test_migrate_test_setting_false(self, mocked_migrate, mocked_ensure_connection): + creation = connection.creation_class(connection) + saved_settings = copy.deepcopy(connection.settings_dict) + try: + connection.settings_dict['TEST']['MIGRATE'] = False + with mock.patch.object(creation, '_create_test_db'): + creation.create_test_db(verbosity=0, autoclobber=True, serialize=False) + mocked_migrate.assert_not_called() + finally: + connection.settings_dict = saved_settings + + def test_migrate_test_setting_true(self, mocked_migrate, mocked_ensure_connection): + creation = connection.creation_class(connection) + saved_settings = copy.deepcopy(connection.settings_dict) + try: + connection.settings_dict['TEST']['MIGRATE'] = True + with mock.patch.object(creation, '_create_test_db'): + creation.create_test_db(verbosity=0, autoclobber=True, serialize=False) + mocked_migrate.assert_called_once() + finally: + connection.settings_dict = saved_settings