|
@@ -2810,3 +2810,163 @@ class AppLabelErrorTests(TestCase):
|
|
|
def test_squashmigrations_app_name_specified_as_label(self):
|
|
|
with self.assertRaisesMessage(CommandError, self.did_you_mean_auth_error):
|
|
|
call_command("squashmigrations", "django.contrib.auth", "0002")
|
|
|
+
|
|
|
+ def test_optimizemigration_nonexistent_app_label(self):
|
|
|
+ with self.assertRaisesMessage(CommandError, self.nonexistent_app_error):
|
|
|
+ call_command("optimizemigration", "nonexistent_app", "0002")
|
|
|
+
|
|
|
+ def test_optimizemigration_app_name_specified_as_label(self):
|
|
|
+ with self.assertRaisesMessage(CommandError, self.did_you_mean_auth_error):
|
|
|
+ call_command("optimizemigration", "django.contrib.auth", "0002")
|
|
|
+
|
|
|
+
|
|
|
+class OptimizeMigrationTests(MigrationTestBase):
|
|
|
+ def test_no_optimization_possible(self):
|
|
|
+ out = io.StringIO()
|
|
|
+ with self.temporary_migration_module(
|
|
|
+ module="migrations.test_migrations"
|
|
|
+ ) as migration_dir:
|
|
|
+ call_command(
|
|
|
+ "optimizemigration", "migrations", "0002", stdout=out, no_color=True
|
|
|
+ )
|
|
|
+ migration_file = os.path.join(migration_dir, "0002_second.py")
|
|
|
+ self.assertTrue(os.path.exists(migration_file))
|
|
|
+ call_command(
|
|
|
+ "optimizemigration",
|
|
|
+ "migrations",
|
|
|
+ "0002",
|
|
|
+ stdout=out,
|
|
|
+ no_color=True,
|
|
|
+ verbosity=0,
|
|
|
+ )
|
|
|
+ self.assertEqual(out.getvalue(), "No optimizations possible.\n")
|
|
|
+
|
|
|
+ def test_optimization(self):
|
|
|
+ out = io.StringIO()
|
|
|
+ with self.temporary_migration_module(
|
|
|
+ module="migrations.test_migrations"
|
|
|
+ ) as migration_dir:
|
|
|
+ call_command(
|
|
|
+ "optimizemigration", "migrations", "0001", stdout=out, no_color=True
|
|
|
+ )
|
|
|
+ initial_migration_file = os.path.join(migration_dir, "0001_initial.py")
|
|
|
+ self.assertTrue(os.path.exists(initial_migration_file))
|
|
|
+ with open(initial_migration_file) as fp:
|
|
|
+ content = fp.read()
|
|
|
+ self.assertIn(
|
|
|
+ '("bool", models.BooleanField'
|
|
|
+ if HAS_BLACK
|
|
|
+ else "('bool', models.BooleanField",
|
|
|
+ content,
|
|
|
+ )
|
|
|
+ self.assertEqual(
|
|
|
+ out.getvalue(),
|
|
|
+ f"Optimizing from 4 operations to 2 operations.\n"
|
|
|
+ f"Optimized migration {initial_migration_file}\n",
|
|
|
+ )
|
|
|
+
|
|
|
+ def test_optimization_no_verbosity(self):
|
|
|
+ out = io.StringIO()
|
|
|
+ with self.temporary_migration_module(
|
|
|
+ module="migrations.test_migrations"
|
|
|
+ ) as migration_dir:
|
|
|
+ call_command(
|
|
|
+ "optimizemigration",
|
|
|
+ "migrations",
|
|
|
+ "0001",
|
|
|
+ stdout=out,
|
|
|
+ no_color=True,
|
|
|
+ verbosity=0,
|
|
|
+ )
|
|
|
+ initial_migration_file = os.path.join(migration_dir, "0001_initial.py")
|
|
|
+ self.assertTrue(os.path.exists(initial_migration_file))
|
|
|
+ with open(initial_migration_file) as fp:
|
|
|
+ content = fp.read()
|
|
|
+ self.assertIn(
|
|
|
+ '("bool", models.BooleanField'
|
|
|
+ if HAS_BLACK
|
|
|
+ else "('bool', models.BooleanField",
|
|
|
+ content,
|
|
|
+ )
|
|
|
+ self.assertEqual(out.getvalue(), "")
|
|
|
+
|
|
|
+ def test_creates_replace_migration_manual_porting(self):
|
|
|
+ out = io.StringIO()
|
|
|
+ with self.temporary_migration_module(
|
|
|
+ module="migrations.test_migrations_manual_porting"
|
|
|
+ ) as migration_dir:
|
|
|
+ call_command(
|
|
|
+ "optimizemigration", "migrations", "0003", stdout=out, no_color=True
|
|
|
+ )
|
|
|
+ optimized_migration_file = os.path.join(
|
|
|
+ migration_dir, "0003_third_optimized.py"
|
|
|
+ )
|
|
|
+ self.assertTrue(os.path.exists(optimized_migration_file))
|
|
|
+ with open(optimized_migration_file) as fp:
|
|
|
+ content = fp.read()
|
|
|
+ self.assertIn("replaces = [", content)
|
|
|
+ self.assertEqual(
|
|
|
+ out.getvalue(),
|
|
|
+ f"Optimizing from 3 operations to 2 operations.\n"
|
|
|
+ f"Manual porting required\n"
|
|
|
+ f" Your migrations contained functions that must be manually copied over,"
|
|
|
+ f"\n"
|
|
|
+ f" as we could not safely copy their implementation.\n"
|
|
|
+ f" See the comment at the top of the optimized migration for details.\n"
|
|
|
+ f"Optimized migration {optimized_migration_file}\n",
|
|
|
+ )
|
|
|
+
|
|
|
+ def test_fails_squash_migration_manual_porting(self):
|
|
|
+ out = io.StringIO()
|
|
|
+ with self.temporary_migration_module(
|
|
|
+ module="migrations.test_migrations_manual_porting"
|
|
|
+ ) as migration_dir:
|
|
|
+ msg = (
|
|
|
+ "Migration will require manual porting but is already a squashed "
|
|
|
+ "migration.\nTransition to a normal migration first: "
|
|
|
+ "https://docs.djangoproject.com/en/dev/topics/migrations/"
|
|
|
+ "#squashing-migrations"
|
|
|
+ )
|
|
|
+ with self.assertRaisesMessage(CommandError, msg):
|
|
|
+ call_command("optimizemigration", "migrations", "0004", stdout=out)
|
|
|
+ optimized_migration_file = os.path.join(
|
|
|
+ migration_dir, "0004_fourth_optimized.py"
|
|
|
+ )
|
|
|
+ self.assertFalse(os.path.exists(optimized_migration_file))
|
|
|
+ self.assertEqual(
|
|
|
+ out.getvalue(), "Optimizing from 3 operations to 2 operations.\n"
|
|
|
+ )
|
|
|
+
|
|
|
+ @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
|
|
+ def test_optimizemigration_check(self):
|
|
|
+ with self.assertRaises(SystemExit):
|
|
|
+ call_command(
|
|
|
+ "optimizemigration", "--check", "migrations", "0001", verbosity=0
|
|
|
+ )
|
|
|
+
|
|
|
+ call_command("optimizemigration", "--check", "migrations", "0002", verbosity=0)
|
|
|
+
|
|
|
+ @override_settings(
|
|
|
+ INSTALLED_APPS=["migrations.migrations_test_apps.unmigrated_app_simple"],
|
|
|
+ )
|
|
|
+ def test_app_without_migrations(self):
|
|
|
+ msg = "App 'unmigrated_app_simple' does not have migrations."
|
|
|
+ with self.assertRaisesMessage(CommandError, msg):
|
|
|
+ call_command("optimizemigration", "unmigrated_app_simple", "0001")
|
|
|
+
|
|
|
+ @override_settings(
|
|
|
+ MIGRATION_MODULES={"migrations": "migrations.test_migrations_clashing_prefix"},
|
|
|
+ )
|
|
|
+ def test_ambigious_prefix(self):
|
|
|
+ msg = (
|
|
|
+ "More than one migration matches 'a' in app 'migrations'. Please "
|
|
|
+ "be more specific."
|
|
|
+ )
|
|
|
+ with self.assertRaisesMessage(CommandError, msg):
|
|
|
+ call_command("optimizemigration", "migrations", "a")
|
|
|
+
|
|
|
+ @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
|
|
|
+ def test_unknown_prefix(self):
|
|
|
+ msg = "Cannot find a migration matching 'nonexistent' from app 'migrations'."
|
|
|
+ with self.assertRaisesMessage(CommandError, msg):
|
|
|
+ call_command("optimizemigration", "migrations", "nonexistent")
|