Made migrations tests write to a temporary directory.
This is preferrable to writing in the current working directory because it eliminates the risk to leak unwanted files, which can result in very weird test failures. Also this will help if we ever try to run these tests concurrently.
This commit is contained in:
parent
b9c619abc1
commit
903d1a57ab
|
@ -1,8 +1,14 @@
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.test import TransactionTestCase
|
from django.test import TransactionTestCase
|
||||||
from django.utils._os import upath
|
from django.test.utils import extend_sys_path
|
||||||
|
from django.utils.module_loading import module_dir
|
||||||
|
|
||||||
|
|
||||||
class MigrationTestBase(TransactionTestCase):
|
class MigrationTestBase(TransactionTestCase):
|
||||||
|
@ -11,7 +17,6 @@ class MigrationTestBase(TransactionTestCase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
available_apps = ["migrations"]
|
available_apps = ["migrations"]
|
||||||
test_dir = os.path.abspath(os.path.dirname(upath(__file__)))
|
|
||||||
|
|
||||||
def get_table_description(self, table):
|
def get_table_description(self, table):
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
|
@ -64,3 +69,46 @@ class MigrationTestBase(TransactionTestCase):
|
||||||
|
|
||||||
def assertFKNotExists(self, table, columns, to, value=True):
|
def assertFKNotExists(self, table, columns, to, value=True):
|
||||||
return self.assertFKExists(table, columns, to, False)
|
return self.assertFKExists(table, columns, to, False)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def temporary_migration_module(self, app_label='migrations', module=None):
|
||||||
|
"""
|
||||||
|
Allows testing management commands in a temporary migrations module.
|
||||||
|
|
||||||
|
Wrap all invocations to makemigrations and squashmigrations with this
|
||||||
|
context manager in order to avoid creating migration files in your
|
||||||
|
source tree inadvertently.
|
||||||
|
|
||||||
|
Takes the application label that will be passed to makemigrations or
|
||||||
|
squashmigrations and the Python path to a migrations module.
|
||||||
|
|
||||||
|
The migrations module is used as a template for creating the temporary
|
||||||
|
migrations module. If it isn't provided, the application's migrations
|
||||||
|
module is used, if it exists.
|
||||||
|
|
||||||
|
Returns the filesystem path to the temporary migrations module.
|
||||||
|
"""
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
try:
|
||||||
|
target_dir = tempfile.mkdtemp(dir=temp_dir)
|
||||||
|
with open(os.path.join(target_dir, '__init__.py'), 'w'):
|
||||||
|
pass
|
||||||
|
target_migrations_dir = os.path.join(target_dir, 'migrations')
|
||||||
|
|
||||||
|
if module is None:
|
||||||
|
module = apps.get_app_config(app_label).name + '.migrations'
|
||||||
|
|
||||||
|
try:
|
||||||
|
source_migrations_dir = module_dir(import_module(module))
|
||||||
|
except (ImportError, ValueError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
shutil.copytree(source_migrations_dir, target_migrations_dir)
|
||||||
|
|
||||||
|
with extend_sys_path(temp_dir):
|
||||||
|
new_module = os.path.basename(target_dir) + '.migrations'
|
||||||
|
with self.settings(MIGRATION_MODULES={app_label: new_module}):
|
||||||
|
yield target_migrations_dir
|
||||||
|
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import unicode_literals
|
||||||
import codecs
|
import codecs
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.management import CommandError, call_command
|
from django.core.management import CommandError, call_command
|
||||||
|
@ -344,109 +343,79 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
Tests running the makemigrations command.
|
Tests running the makemigrations command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Because the `import_module` performed in `MigrationLoader` will cache
|
|
||||||
# the migrations package, we can't reuse the same migration package
|
|
||||||
# between tests. This is only a problem for testing, since `makemigrations`
|
|
||||||
# is normally called in its own process.
|
|
||||||
creation_counter = 0
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
MakeMigrationsTests.creation_counter += 1
|
super(MigrationTestBase, self).setUp()
|
||||||
self.migration_dir = os.path.join(self.test_dir, 'migrations_%d' % self.creation_counter)
|
|
||||||
self.migration_pkg = "migrations.migrations_%d" % self.creation_counter
|
|
||||||
self._old_models = apps.app_configs['migrations'].models.copy()
|
self._old_models = apps.app_configs['migrations'].models.copy()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
apps.app_configs['migrations'].models = self._old_models
|
apps.app_configs['migrations'].models = self._old_models
|
||||||
apps.all_models['migrations'] = self._old_models
|
apps.all_models['migrations'] = self._old_models
|
||||||
apps.clear_cache()
|
apps.clear_cache()
|
||||||
|
super(MigrationTestBase, self).tearDown()
|
||||||
_cwd = os.getcwd()
|
|
||||||
os.chdir(self.test_dir)
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
self._rmrf(self.migration_dir)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._rmrf(os.path.join(self.test_dir,
|
|
||||||
"test_migrations_path_doesnt_exist"))
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
os.chdir(_cwd)
|
|
||||||
|
|
||||||
def _rmrf(self, dname):
|
|
||||||
if os.path.commonprefix([self.test_dir, os.path.abspath(dname)]) != self.test_dir:
|
|
||||||
return
|
|
||||||
shutil.rmtree(dname)
|
|
||||||
|
|
||||||
def test_files_content(self):
|
def test_files_content(self):
|
||||||
self.assertTableNotExists("migrations_unicodemodel")
|
self.assertTableNotExists("migrations_unicodemodel")
|
||||||
apps.register_model('migrations', UnicodeModel)
|
apps.register_model('migrations', UnicodeModel)
|
||||||
with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}):
|
with self.temporary_migration_module() as migration_dir:
|
||||||
call_command("makemigrations", "migrations", verbosity=0)
|
call_command("makemigrations", "migrations", verbosity=0)
|
||||||
|
|
||||||
init_file = os.path.join(self.migration_dir, "__init__.py")
|
# Check for empty __init__.py file in migrations folder
|
||||||
|
init_file = os.path.join(migration_dir, "__init__.py")
|
||||||
|
self.assertTrue(os.path.exists(init_file))
|
||||||
|
|
||||||
# Check for existing __init__.py file in migrations folder
|
with open(init_file, 'r') as fp:
|
||||||
self.assertTrue(os.path.exists(init_file))
|
content = force_text(fp.read())
|
||||||
|
|
||||||
with open(init_file, 'r') as fp:
|
|
||||||
content = force_text(fp.read())
|
|
||||||
self.assertEqual(content, '')
|
self.assertEqual(content, '')
|
||||||
|
|
||||||
initial_file = os.path.join(self.migration_dir, "0001_initial.py")
|
# Check for existing 0001_initial.py file in migration folder
|
||||||
|
initial_file = os.path.join(migration_dir, "0001_initial.py")
|
||||||
|
self.assertTrue(os.path.exists(initial_file))
|
||||||
|
|
||||||
# Check for existing 0001_initial.py file in migration folder
|
with codecs.open(initial_file, 'r', encoding='utf-8') as fp:
|
||||||
self.assertTrue(os.path.exists(initial_file))
|
content = fp.read()
|
||||||
|
self.assertIn('# -*- coding: utf-8 -*-', content)
|
||||||
|
self.assertIn('migrations.CreateModel', content)
|
||||||
|
|
||||||
with codecs.open(initial_file, 'r', encoding='utf-8') as fp:
|
if six.PY3:
|
||||||
content = fp.read()
|
self.assertIn('úñí©óðé µóðéø', content) # Meta.verbose_name
|
||||||
self.assertIn('# -*- coding: utf-8 -*-', content)
|
self.assertIn('úñí©óðé µóðéøß', content) # Meta.verbose_name_plural
|
||||||
self.assertIn('migrations.CreateModel', content)
|
self.assertIn('ÚÑÍ¢ÓÐÉ', content) # title.verbose_name
|
||||||
|
self.assertIn('“Ðjáñgó”', content) # title.default
|
||||||
if six.PY3:
|
else:
|
||||||
self.assertIn('úñí©óðé µóðéø', content) # Meta.verbose_name
|
self.assertIn('\\xfa\\xf1\\xed\\xa9\\xf3\\xf0\\xe9 \\xb5\\xf3\\xf0\\xe9\\xf8', content) # Meta.verbose_name
|
||||||
self.assertIn('úñí©óðé µóðéøß', content) # Meta.verbose_name_plural
|
self.assertIn('\\xfa\\xf1\\xed\\xa9\\xf3\\xf0\\xe9 \\xb5\\xf3\\xf0\\xe9\\xf8\\xdf', content) # Meta.verbose_name_plural
|
||||||
self.assertIn('ÚÑÍ¢ÓÐÉ', content) # title.verbose_name
|
self.assertIn('\\xda\\xd1\\xcd\\xa2\\xd3\\xd0\\xc9', content) # title.verbose_name
|
||||||
self.assertIn('“Ðjáñgó”', content) # title.default
|
self.assertIn('\\u201c\\xd0j\\xe1\\xf1g\\xf3\\u201d', content) # title.default
|
||||||
else:
|
|
||||||
self.assertIn('\\xfa\\xf1\\xed\\xa9\\xf3\\xf0\\xe9 \\xb5\\xf3\\xf0\\xe9\\xf8', content) # Meta.verbose_name
|
|
||||||
self.assertIn('\\xfa\\xf1\\xed\\xa9\\xf3\\xf0\\xe9 \\xb5\\xf3\\xf0\\xe9\\xf8\\xdf', content) # Meta.verbose_name_plural
|
|
||||||
self.assertIn('\\xda\\xd1\\xcd\\xa2\\xd3\\xd0\\xc9', content) # title.verbose_name
|
|
||||||
self.assertIn('\\u201c\\xd0j\\xe1\\xf1g\\xf3\\u201d', content) # title.default
|
|
||||||
|
|
||||||
def test_failing_migration(self):
|
def test_failing_migration(self):
|
||||||
# If a migration fails to serialize, it shouldn't generate an empty file. #21280
|
# If a migration fails to serialize, it shouldn't generate an empty file. #21280
|
||||||
apps.register_model('migrations', UnserializableModel)
|
apps.register_model('migrations', UnserializableModel)
|
||||||
|
|
||||||
with six.assertRaisesRegex(self, ValueError, r'Cannot serialize'):
|
with self.temporary_migration_module() as migration_dir:
|
||||||
with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}):
|
with six.assertRaisesRegex(self, ValueError, r'Cannot serialize'):
|
||||||
call_command("makemigrations", "migrations", verbosity=0)
|
call_command("makemigrations", "migrations", verbosity=0)
|
||||||
|
|
||||||
initial_file = os.path.join(self.migration_dir, "0001_initial.py")
|
initial_file = os.path.join(migration_dir, "0001_initial.py")
|
||||||
self.assertFalse(os.path.exists(initial_file))
|
self.assertFalse(os.path.exists(initial_file))
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_conflict"})
|
|
||||||
def test_makemigrations_conflict_exit(self):
|
def test_makemigrations_conflict_exit(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that makemigrations exits if it detects a conflict.
|
Makes sure that makemigrations exits if it detects a conflict.
|
||||||
"""
|
"""
|
||||||
with self.assertRaises(CommandError):
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict"):
|
||||||
call_command("makemigrations")
|
with self.assertRaises(CommandError):
|
||||||
|
call_command("makemigrations")
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
|
||||||
def test_makemigrations_merge_no_conflict(self):
|
def test_makemigrations_merge_no_conflict(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that makemigrations exits if in merge mode with no conflicts.
|
Makes sure that makemigrations exits if in merge mode with no conflicts.
|
||||||
"""
|
"""
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
try:
|
with self.temporary_migration_module(module="migrations.test_migrations"):
|
||||||
call_command("makemigrations", merge=True, stdout=out)
|
try:
|
||||||
except CommandError:
|
call_command("makemigrations", merge=True, stdout=out)
|
||||||
self.fail("Makemigrations errored in merge mode with no conflicts")
|
except CommandError:
|
||||||
|
self.fail("Makemigrations errored in merge mode with no conflicts")
|
||||||
self.assertIn("No conflicts detected to merge.", out.getvalue())
|
self.assertIn("No conflicts detected to merge.", out.getvalue())
|
||||||
|
|
||||||
def test_makemigrations_no_app_sys_exit(self):
|
def test_makemigrations_no_app_sys_exit(self):
|
||||||
|
@ -462,32 +431,31 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
"""
|
"""
|
||||||
Makes sure that makemigrations exits if no app is specified with 'empty' mode.
|
Makes sure that makemigrations exits if no app is specified with 'empty' mode.
|
||||||
"""
|
"""
|
||||||
with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}):
|
with self.assertRaises(CommandError):
|
||||||
self.assertRaises(CommandError, call_command, "makemigrations", empty=True)
|
call_command("makemigrations", empty=True)
|
||||||
|
|
||||||
def test_makemigrations_empty_migration(self):
|
def test_makemigrations_empty_migration(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that makemigrations properly constructs an empty migration.
|
Makes sure that makemigrations properly constructs an empty migration.
|
||||||
"""
|
"""
|
||||||
with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}):
|
with self.temporary_migration_module() as migration_dir:
|
||||||
try:
|
try:
|
||||||
call_command("makemigrations", "migrations", empty=True, verbosity=0)
|
call_command("makemigrations", "migrations", empty=True, verbosity=0)
|
||||||
except CommandError:
|
except CommandError:
|
||||||
self.fail("Makemigrations errored in creating empty migration for a proper app.")
|
self.fail("Makemigrations errored in creating empty migration for a proper app.")
|
||||||
|
|
||||||
initial_file = os.path.join(self.migration_dir, "0001_initial.py")
|
# Check for existing 0001_initial.py file in migration folder
|
||||||
|
initial_file = os.path.join(migration_dir, "0001_initial.py")
|
||||||
|
self.assertTrue(os.path.exists(initial_file))
|
||||||
|
|
||||||
# Check for existing 0001_initial.py file in migration folder
|
with codecs.open(initial_file, 'r', encoding='utf-8') as fp:
|
||||||
self.assertTrue(os.path.exists(initial_file))
|
content = fp.read()
|
||||||
|
self.assertIn('# -*- coding: utf-8 -*-', content)
|
||||||
|
|
||||||
with codecs.open(initial_file, 'r', encoding='utf-8') as fp:
|
# Remove all whitespace to check for empty dependencies and operations
|
||||||
content = fp.read()
|
content = content.replace(' ', '')
|
||||||
self.assertIn('# -*- coding: utf-8 -*-', content)
|
self.assertIn('dependencies=[\n]', content)
|
||||||
|
self.assertIn('operations=[\n]', content)
|
||||||
# Remove all whitespace to check for empty dependencies and operations
|
|
||||||
content = content.replace(' ', '')
|
|
||||||
self.assertIn('dependencies=[\n]', content)
|
|
||||||
self.assertIn('operations=[\n]', content)
|
|
||||||
|
|
||||||
def test_makemigrations_no_changes_no_apps(self):
|
def test_makemigrations_no_changes_no_apps(self):
|
||||||
"""
|
"""
|
||||||
|
@ -497,13 +465,13 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
call_command("makemigrations", stdout=out)
|
call_command("makemigrations", stdout=out)
|
||||||
self.assertIn("No changes detected", out.getvalue())
|
self.assertIn("No changes detected", out.getvalue())
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_changes"})
|
|
||||||
def test_makemigrations_no_changes(self):
|
def test_makemigrations_no_changes(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that makemigrations exits when there are no changes to an app.
|
Makes sure that makemigrations exits when there are no changes to an app.
|
||||||
"""
|
"""
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
call_command("makemigrations", "migrations", stdout=out)
|
with self.temporary_migration_module(module="migrations.test_migrations_no_changes"):
|
||||||
|
call_command("makemigrations", "migrations", stdout=out)
|
||||||
self.assertIn("No changes detected in app 'migrations'", out.getvalue())
|
self.assertIn("No changes detected in app 'migrations'", out.getvalue())
|
||||||
|
|
||||||
def test_makemigrations_migrations_announce(self):
|
def test_makemigrations_migrations_announce(self):
|
||||||
|
@ -511,23 +479,22 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
Makes sure that makemigrations announces the migration at the default verbosity level.
|
Makes sure that makemigrations announces the migration at the default verbosity level.
|
||||||
"""
|
"""
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}):
|
with self.temporary_migration_module():
|
||||||
call_command("makemigrations", "migrations", stdout=out)
|
call_command("makemigrations", "migrations", stdout=out)
|
||||||
self.assertIn("Migrations for 'migrations'", out.getvalue())
|
self.assertIn("Migrations for 'migrations'", out.getvalue())
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_ancestor"})
|
|
||||||
def test_makemigrations_no_common_ancestor(self):
|
def test_makemigrations_no_common_ancestor(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that makemigrations fails to merge migrations with no common ancestor.
|
Makes sure that makemigrations fails to merge migrations with no common ancestor.
|
||||||
"""
|
"""
|
||||||
with self.assertRaises(ValueError) as context:
|
with self.assertRaises(ValueError) as context:
|
||||||
call_command("makemigrations", "migrations", merge=True)
|
with self.temporary_migration_module(module="migrations.test_migrations_no_ancestor"):
|
||||||
|
call_command("makemigrations", "migrations", merge=True)
|
||||||
exception_message = str(context.exception)
|
exception_message = str(context.exception)
|
||||||
self.assertIn("Could not find common ancestor of", exception_message)
|
self.assertIn("Could not find common ancestor of", exception_message)
|
||||||
self.assertIn("0002_second", exception_message)
|
self.assertIn("0002_second", exception_message)
|
||||||
self.assertIn("0002_conflicting_second", exception_message)
|
self.assertIn("0002_conflicting_second", exception_message)
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_conflict"})
|
|
||||||
def test_makemigrations_interactive_reject(self):
|
def test_makemigrations_interactive_reject(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that makemigrations enters and exits interactive mode properly.
|
Makes sure that makemigrations enters and exits interactive mode properly.
|
||||||
|
@ -536,15 +503,15 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
old_input = questioner.input
|
old_input = questioner.input
|
||||||
questioner.input = lambda _: "N"
|
questioner.input = lambda _: "N"
|
||||||
try:
|
try:
|
||||||
call_command("makemigrations", "migrations", merge=True, interactive=True, verbosity=0)
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
merge_file = os.path.join(self.test_dir, 'test_migrations_conflict', '0003_merge.py')
|
call_command("makemigrations", "migrations", merge=True, interactive=True, verbosity=0)
|
||||||
self.assertFalse(os.path.exists(merge_file))
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
|
self.assertFalse(os.path.exists(merge_file))
|
||||||
except CommandError:
|
except CommandError:
|
||||||
self.fail("Makemigrations failed while running interactive questioner")
|
self.fail("Makemigrations failed while running interactive questioner")
|
||||||
finally:
|
finally:
|
||||||
questioner.input = old_input
|
questioner.input = old_input
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_conflict"})
|
|
||||||
def test_makemigrations_interactive_accept(self):
|
def test_makemigrations_interactive_accept(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that makemigrations enters interactive mode and merges properly.
|
Makes sure that makemigrations enters interactive mode and merges properly.
|
||||||
|
@ -554,35 +521,31 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
questioner.input = lambda _: "y"
|
questioner.input = lambda _: "y"
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
try:
|
try:
|
||||||
call_command("makemigrations", "migrations", merge=True, interactive=True, stdout=out)
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
merge_file = os.path.join(self.test_dir, 'test_migrations_conflict', '0003_merge.py')
|
call_command("makemigrations", "migrations", merge=True, interactive=True, stdout=out)
|
||||||
self.assertTrue(os.path.exists(merge_file))
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
os.remove(merge_file)
|
self.assertTrue(os.path.exists(merge_file))
|
||||||
self.assertFalse(os.path.exists(merge_file))
|
|
||||||
except CommandError:
|
except CommandError:
|
||||||
self.fail("Makemigrations failed while running interactive questioner")
|
self.fail("Makemigrations failed while running interactive questioner")
|
||||||
finally:
|
finally:
|
||||||
questioner.input = old_input
|
questioner.input = old_input
|
||||||
self.assertIn("Created new merge migration", force_text(out.getvalue()))
|
self.assertIn("Created new merge migration", force_text(out.getvalue()))
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_conflict"})
|
|
||||||
def test_makemigrations_handle_merge(self):
|
def test_makemigrations_handle_merge(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that makemigrations properly merges the conflicting migrations with --noinput.
|
Makes sure that makemigrations properly merges the conflicting migrations with --noinput.
|
||||||
"""
|
"""
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
call_command("makemigrations", "migrations", merge=True, interactive=False, stdout=out)
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
|
call_command("makemigrations", "migrations", merge=True, interactive=False, stdout=out)
|
||||||
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
|
self.assertTrue(os.path.exists(merge_file))
|
||||||
output = force_text(out.getvalue())
|
output = force_text(out.getvalue())
|
||||||
self.assertIn("Merging migrations", output)
|
self.assertIn("Merging migrations", output)
|
||||||
self.assertIn("Branch 0002_second", output)
|
self.assertIn("Branch 0002_second", output)
|
||||||
self.assertIn("Branch 0002_conflicting_second", output)
|
self.assertIn("Branch 0002_conflicting_second", output)
|
||||||
merge_file = os.path.join(self.test_dir, 'test_migrations_conflict', '0003_merge.py')
|
|
||||||
self.assertTrue(os.path.exists(merge_file))
|
|
||||||
os.remove(merge_file)
|
|
||||||
self.assertFalse(os.path.exists(merge_file))
|
|
||||||
self.assertIn("Created new merge migration", output)
|
self.assertIn("Created new merge migration", output)
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_default"})
|
|
||||||
def test_makemigrations_dry_run(self):
|
def test_makemigrations_dry_run(self):
|
||||||
"""
|
"""
|
||||||
Ticket #22676 -- `makemigrations --dry-run` should not ask for defaults.
|
Ticket #22676 -- `makemigrations --dry-run` should not ask for defaults.
|
||||||
|
@ -596,11 +559,11 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
app_label = "migrations"
|
app_label = "migrations"
|
||||||
|
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
call_command("makemigrations", "migrations", dry_run=True, stdout=out)
|
with self.temporary_migration_module(module="migrations.test_migrations_no_default"):
|
||||||
|
call_command("makemigrations", "migrations", dry_run=True, stdout=out)
|
||||||
# Output the expected changes directly, without asking for defaults
|
# Output the expected changes directly, without asking for defaults
|
||||||
self.assertIn("Add field silly_date to sillymodel", out.getvalue())
|
self.assertIn("Add field silly_date to sillymodel", out.getvalue())
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_default"})
|
|
||||||
def test_makemigrations_dry_run_verbosity_3(self):
|
def test_makemigrations_dry_run_verbosity_3(self):
|
||||||
"""
|
"""
|
||||||
Ticket #22675 -- Allow `makemigrations --dry-run` to output the
|
Ticket #22675 -- Allow `makemigrations --dry-run` to output the
|
||||||
|
@ -615,7 +578,8 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
app_label = "migrations"
|
app_label = "migrations"
|
||||||
|
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
call_command("makemigrations", "migrations", dry_run=True, stdout=out, verbosity=3)
|
with self.temporary_migration_module(module="migrations.test_migrations_no_default"):
|
||||||
|
call_command("makemigrations", "migrations", dry_run=True, stdout=out, verbosity=3)
|
||||||
|
|
||||||
# Normal --dry-run output
|
# Normal --dry-run output
|
||||||
self.assertIn("- Add field silly_char to sillymodel", out.getvalue())
|
self.assertIn("- Add field silly_char to sillymodel", out.getvalue())
|
||||||
|
@ -630,7 +594,6 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
self.assertIn("model_name='sillymodel',", out.getvalue())
|
self.assertIn("model_name='sillymodel',", out.getvalue())
|
||||||
self.assertIn("name='silly_char',", out.getvalue())
|
self.assertIn("name='silly_char',", out.getvalue())
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_path_doesnt_exist.foo.bar"})
|
|
||||||
def test_makemigrations_migrations_modules_path_not_exist(self):
|
def test_makemigrations_migrations_modules_path_not_exist(self):
|
||||||
"""
|
"""
|
||||||
Ticket #22682 -- Makemigrations fails when specifying custom location
|
Ticket #22682 -- Makemigrations fails when specifying custom location
|
||||||
|
@ -645,17 +608,16 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
app_label = "migrations"
|
app_label = "migrations"
|
||||||
|
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
call_command("makemigrations", "migrations", stdout=out)
|
with self.temporary_migration_module(module="migrations.test_migrations_path_doesnt_exist.foo.bar") as migration_dir:
|
||||||
|
call_command("makemigrations", "migrations", stdout=out)
|
||||||
|
|
||||||
|
# Migrations file is actually created in the expected path.
|
||||||
|
initial_file = os.path.join(migration_dir, "0001_initial.py")
|
||||||
|
self.assertTrue(os.path.exists(initial_file))
|
||||||
|
|
||||||
# Command output indicates the migration is created.
|
# Command output indicates the migration is created.
|
||||||
self.assertIn(" - Create model SillyModel", out.getvalue())
|
self.assertIn(" - Create model SillyModel", out.getvalue())
|
||||||
|
|
||||||
# Migrations file is actually created in the expected path.
|
|
||||||
self.assertTrue(os.path.isfile(os.path.join(self.test_dir,
|
|
||||||
"test_migrations_path_doesnt_exist", "foo", "bar",
|
|
||||||
"0001_initial.py")))
|
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_conflict"})
|
|
||||||
def test_makemigrations_interactive_by_default(self):
|
def test_makemigrations_interactive_by_default(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that the user is prompted to merge by default if there are
|
Makes sure that the user is prompted to merge by default if there are
|
||||||
|
@ -666,21 +628,19 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
old_input = questioner.input
|
old_input = questioner.input
|
||||||
questioner.input = lambda _: "N"
|
questioner.input = lambda _: "N"
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
merge_file = os.path.join(self.test_dir, 'test_migrations_conflict', '0003_merge.py')
|
|
||||||
try:
|
try:
|
||||||
call_command("makemigrations", "migrations", merge=True, stdout=out)
|
with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
|
||||||
# This will fail if interactive is False by default
|
call_command("makemigrations", "migrations", merge=True, stdout=out)
|
||||||
self.assertFalse(os.path.exists(merge_file))
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
|
# This will fail if interactive is False by default
|
||||||
|
self.assertFalse(os.path.exists(merge_file))
|
||||||
except CommandError:
|
except CommandError:
|
||||||
self.fail("Makemigrations failed while running interactive questioner")
|
self.fail("Makemigrations failed while running interactive questioner")
|
||||||
finally:
|
finally:
|
||||||
questioner.input = old_input
|
questioner.input = old_input
|
||||||
if os.path.exists(merge_file):
|
|
||||||
os.remove(merge_file)
|
|
||||||
self.assertNotIn("Created new merge migration", out.getvalue())
|
self.assertNotIn("Created new merge migration", out.getvalue())
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_changes"},
|
|
||||||
INSTALLED_APPS=[
|
INSTALLED_APPS=[
|
||||||
"migrations",
|
"migrations",
|
||||||
"migrations.migrations_test_apps.unspecified_app_with_conflict"])
|
"migrations.migrations_test_apps.unspecified_app_with_conflict"])
|
||||||
|
@ -690,7 +650,8 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
unspecified app has conflicting migrations.
|
unspecified app has conflicting migrations.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
call_command("makemigrations", "migrations", merge=False, verbosity=0)
|
with self.temporary_migration_module(module="migrations.test_migrations_no_changes"):
|
||||||
|
call_command("makemigrations", "migrations", merge=False, verbosity=0)
|
||||||
except CommandError:
|
except CommandError:
|
||||||
self.fail("Makemigrations fails resolving conflicts in an unspecified app")
|
self.fail("Makemigrations fails resolving conflicts in an unspecified app")
|
||||||
|
|
||||||
|
@ -707,105 +668,96 @@ class MakeMigrationsTests(MigrationTestBase):
|
||||||
old_input = questioner.input
|
old_input = questioner.input
|
||||||
questioner.input = lambda _: "y"
|
questioner.input = lambda _: "y"
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
merge_file = os.path.join(self.test_dir,
|
|
||||||
'migrations_test_apps',
|
|
||||||
'unspecified_app_with_conflict',
|
|
||||||
'migrations',
|
|
||||||
'0003_merge.py')
|
|
||||||
try:
|
try:
|
||||||
call_command("makemigrations", "migrated_app", merge=True, interactive=True, stdout=out)
|
with self.temporary_migration_module(app_label="migrated_app") as migration_dir:
|
||||||
self.assertFalse(os.path.exists(merge_file))
|
call_command("makemigrations", "migrated_app", merge=True, interactive=True, stdout=out)
|
||||||
|
merge_file = os.path.join(migration_dir, '0003_merge.py')
|
||||||
|
self.assertFalse(os.path.exists(merge_file))
|
||||||
self.assertIn("No conflicts detected to merge.", out.getvalue())
|
self.assertIn("No conflicts detected to merge.", out.getvalue())
|
||||||
except CommandError:
|
except CommandError:
|
||||||
self.fail("Makemigrations fails resolving conflicts in an unspecified app")
|
self.fail("Makemigrations fails resolving conflicts in an unspecified app")
|
||||||
finally:
|
finally:
|
||||||
questioner.input = old_input
|
questioner.input = old_input
|
||||||
if os.path.exists(merge_file):
|
|
||||||
os.remove(merge_file)
|
|
||||||
|
|
||||||
def test_makemigrations_with_custom_name(self):
|
def test_makemigrations_with_custom_name(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that makemigrations generate a custom migration.
|
Makes sure that makemigrations generate a custom migration.
|
||||||
"""
|
"""
|
||||||
def cmd(migration_count, migration_name, *args):
|
with self.temporary_migration_module() as migration_dir:
|
||||||
with override_settings(MIGRATION_MODULES={"migrations": self.migration_pkg}):
|
|
||||||
|
def cmd(migration_count, migration_name, *args):
|
||||||
try:
|
try:
|
||||||
call_command("makemigrations", "migrations", "--verbosity", "0", "--name", migration_name, *args)
|
call_command("makemigrations", "migrations", "--verbosity", "0", "--name", migration_name, *args)
|
||||||
except CommandError:
|
except CommandError:
|
||||||
self.fail("Makemigrations errored in creating empty migration with custom name for a proper app.")
|
self.fail("Makemigrations errored in creating empty migration with custom name for a proper app.")
|
||||||
migration_file = os.path.join(self.migration_dir, "%s_%s.py" % (migration_count, migration_name))
|
migration_file = os.path.join(migration_dir, "%s_%s.py" % (migration_count, migration_name))
|
||||||
# Check for existing migration file in migration folder
|
# Check for existing migration file in migration folder
|
||||||
self.assertTrue(os.path.exists(migration_file))
|
self.assertTrue(os.path.exists(migration_file))
|
||||||
with codecs.open(migration_file, "r", encoding="utf-8") as fp:
|
with codecs.open(migration_file, "r", encoding="utf-8") as fp:
|
||||||
content = fp.read()
|
content = fp.read()
|
||||||
self.assertIn("# -*- coding: utf-8 -*-", content)
|
self.assertIn("# -*- coding: utf-8 -*-", content)
|
||||||
content = content.replace(" ", "")
|
content = content.replace(" ", "")
|
||||||
return content
|
return content
|
||||||
|
|
||||||
# generate an initial migration
|
# generate an initial migration
|
||||||
migration_name_0001 = "my_initial_migration"
|
migration_name_0001 = "my_initial_migration"
|
||||||
content = cmd("0001", migration_name_0001)
|
content = cmd("0001", migration_name_0001)
|
||||||
self.assertIn("dependencies=[\n]", content)
|
self.assertIn("dependencies=[\n]", content)
|
||||||
|
|
||||||
# Python 3.3+ importlib caches os.listdir() on some platforms like
|
# Python 3.3+ importlib caches os.listdir() on some platforms like
|
||||||
# Mac OS X (#23850).
|
# Mac OS X (#23850).
|
||||||
if hasattr(importlib, 'invalidate_caches'):
|
if hasattr(importlib, 'invalidate_caches'):
|
||||||
importlib.invalidate_caches()
|
importlib.invalidate_caches()
|
||||||
|
|
||||||
# generate an empty migration
|
# generate an empty migration
|
||||||
migration_name_0002 = "my_custom_migration"
|
migration_name_0002 = "my_custom_migration"
|
||||||
content = cmd("0002", migration_name_0002, "--empty")
|
content = cmd("0002", migration_name_0002, "--empty")
|
||||||
self.assertIn("dependencies=[\n('migrations','0001_%s'),\n]" % migration_name_0001, content)
|
self.assertIn("dependencies=[\n('migrations','0001_%s'),\n]" % migration_name_0001, content)
|
||||||
self.assertIn("operations=[\n]", content)
|
self.assertIn("operations=[\n]", content)
|
||||||
|
|
||||||
def test_makemigrations_exit(self):
|
def test_makemigrations_exit(self):
|
||||||
"""
|
"""
|
||||||
makemigrations --exit should exit with sys.exit(1) when there are no
|
makemigrations --exit should exit with sys.exit(1) when there are no
|
||||||
changes to an app.
|
changes to an app.
|
||||||
"""
|
"""
|
||||||
with self.settings(MIGRATION_MODULES={"migrations": self.migration_pkg}):
|
with self.temporary_migration_module():
|
||||||
call_command("makemigrations", "--exit", "migrations", verbosity=0)
|
call_command("makemigrations", "--exit", "migrations", verbosity=0)
|
||||||
|
|
||||||
with self.settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_no_changes"}):
|
with self.temporary_migration_module(module="migrations.test_migrations_no_changes"):
|
||||||
with self.assertRaises(SystemExit):
|
with self.assertRaises(SystemExit):
|
||||||
call_command("makemigrations", "--exit", "migrations", verbosity=0)
|
call_command("makemigrations", "--exit", "migrations", verbosity=0)
|
||||||
|
|
||||||
|
|
||||||
class SquashMigrationsTest(MigrationTestBase):
|
class SquashMigrationsTests(MigrationTestBase):
|
||||||
"""
|
"""
|
||||||
Tests running the squashmigrations command.
|
Tests running the squashmigrations command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path = "test_migrations/0001_squashed_0002_second.py"
|
|
||||||
path = os.path.join(MigrationTestBase.test_dir, path)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
if os.path.exists(self.path):
|
|
||||||
os.remove(self.path)
|
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
|
||||||
def test_squashmigrations_squashes(self):
|
def test_squashmigrations_squashes(self):
|
||||||
"""
|
"""
|
||||||
Tests that squashmigrations squashes migrations.
|
Tests that squashmigrations squashes migrations.
|
||||||
"""
|
"""
|
||||||
call_command("squashmigrations", "migrations", "0002", interactive=False, verbosity=0)
|
with self.temporary_migration_module(module="migrations.test_migrations") as migration_dir:
|
||||||
self.assertTrue(os.path.exists(self.path))
|
call_command("squashmigrations", "migrations", "0002", interactive=False, verbosity=0)
|
||||||
|
|
||||||
|
squashed_migration_file = os.path.join(migration_dir, "0001_squashed_0002_second.py")
|
||||||
|
self.assertTrue(os.path.exists(squashed_migration_file))
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
|
||||||
def test_squashmigrations_optimizes(self):
|
def test_squashmigrations_optimizes(self):
|
||||||
"""
|
"""
|
||||||
Tests that squashmigrations optimizes operations.
|
Tests that squashmigrations optimizes operations.
|
||||||
"""
|
"""
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
call_command("squashmigrations", "migrations", "0002", interactive=False, verbosity=1, stdout=out)
|
with self.temporary_migration_module(module="migrations.test_migrations"):
|
||||||
|
call_command("squashmigrations", "migrations", "0002", interactive=False, verbosity=1, stdout=out)
|
||||||
self.assertIn("Optimized from 7 operations to 5 operations.", force_text(out.getvalue()))
|
self.assertIn("Optimized from 7 operations to 5 operations.", force_text(out.getvalue()))
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
|
||||||
def test_ticket_23799_squashmigrations_no_optimize(self):
|
def test_ticket_23799_squashmigrations_no_optimize(self):
|
||||||
"""
|
"""
|
||||||
Makes sure that squashmigrations --no-optimize really doesn't optimize operations.
|
Makes sure that squashmigrations --no-optimize really doesn't optimize operations.
|
||||||
"""
|
"""
|
||||||
out = six.StringIO()
|
out = six.StringIO()
|
||||||
call_command("squashmigrations", "migrations", "0002",
|
with self.temporary_migration_module(module="migrations.test_migrations"):
|
||||||
interactive=False, verbosity=1, no_optimize=True, stdout=out)
|
call_command("squashmigrations", "migrations", "0002",
|
||||||
|
interactive=False, verbosity=1, no_optimize=True, stdout=out)
|
||||||
self.assertIn("Skipping optimization", force_text(out.getvalue()))
|
self.assertIn("Skipping optimization", force_text(out.getvalue()))
|
||||||
|
|
Loading…
Reference in New Issue