From 00276e0414ce796a71a28d4c675a22b041aa3450 Mon Sep 17 00:00:00 2001
From: Andrew Godwin <andrew@aeracode.org>
Date: Thu, 25 Jul 2013 13:52:35 +0100
Subject: [PATCH] Add tests for the migrate command and fix a bug they exposed

---
 django/core/management/commands/migrate.py    |  7 ++--
 tests/migrations/test_base.py                 | 39 +++++++++++++++++++
 tests/migrations/test_commands.py             | 37 ++++++++++++++++++
 tests/migrations/test_executor.py             |  1 +
 .../test_migrations/0001_initial.py           |  2 +-
 tests/migrations/test_operations.py           | 35 +----------------
 6 files changed, 84 insertions(+), 37 deletions(-)
 create mode 100644 tests/migrations/test_base.py
 create mode 100644 tests/migrations/test_commands.py

diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py
index 29b8b2c9c0..8e3c79a431 100644
--- a/django/core/management/commands/migrate.py
+++ b/django/core/management/commands/migrate.py
@@ -73,7 +73,7 @@ class Command(BaseCommand):
             if app_label not in executor.loader.migrated_apps:
                 raise CommandError("App '%s' does not have migrations (you cannot selectively sync unmigrated apps)" % app_label)
             if migration_name == "zero":
-                migration_name = None
+                targets = [(app_label, None)]
             else:
                 try:
                     migration = executor.loader.get_migration_by_prefix(app_label, migration_name)
@@ -81,7 +81,7 @@ class Command(BaseCommand):
                     raise CommandError("More than one migration matches '%s' in app '%s'. Please be more specific." % (app_label, migration_name))
                 except KeyError:
                     raise CommandError("Cannot find a migration matching '%s' from app '%s'. Is it in INSTALLED_APPS?" % (app_label, migration_name))
-            targets = [(app_label, migration.name)]
+                targets = [(app_label, migration.name)]
             target_app_labels_only = False
         elif len(args) == 1:
             app_label = args[0]
@@ -110,7 +110,8 @@ class Command(BaseCommand):
         # Run the syncdb phase.
         # If you ever manage to get rid of this, I owe you many, many drinks.
         if run_syncdb:
-            self.stdout.write(self.style.MIGRATE_HEADING("Synchronizing apps without migrations:"))
+            if self.verbosity >= 1:
+                self.stdout.write(self.style.MIGRATE_HEADING("Synchronizing apps without migrations:"))
             self.sync_apps(connection, executor.loader.unmigrated_apps)
 
         # Migrate!
diff --git a/tests/migrations/test_base.py b/tests/migrations/test_base.py
new file mode 100644
index 0000000000..01062667aa
--- /dev/null
+++ b/tests/migrations/test_base.py
@@ -0,0 +1,39 @@
+from django.test import TestCase
+from django.db import connection
+
+
+class MigrationTestBase(TestCase):
+    """
+    Contains an extended set of asserts for testing migrations and schema operations.
+    """
+
+    def assertTableExists(self, table):
+        self.assertIn(table, connection.introspection.get_table_list(connection.cursor()))
+
+    def assertTableNotExists(self, table):
+        self.assertNotIn(table, connection.introspection.get_table_list(connection.cursor()))
+
+    def assertColumnExists(self, table, column):
+        self.assertIn(column, [c.name for c in connection.introspection.get_table_description(connection.cursor(), table)])
+
+    def assertColumnNotExists(self, table, column):
+        self.assertNotIn(column, [c.name for c in connection.introspection.get_table_description(connection.cursor(), table)])
+
+    def assertColumnNull(self, table, column):
+        self.assertEqual([c.null_ok for c in connection.introspection.get_table_description(connection.cursor(), table) if c.name == column][0], True)
+
+    def assertColumnNotNull(self, table, column):
+        self.assertEqual([c.null_ok for c in connection.introspection.get_table_description(connection.cursor(), table) if c.name == column][0], False)
+
+    def assertIndexExists(self, table, columns, value=True):
+        self.assertEqual(
+            value,
+            any(
+                c["index"]
+                for c in connection.introspection.get_constraints(connection.cursor(), table).values()
+                if c['columns'] == list(columns)
+            ),
+        )
+
+    def assertIndexNotExists(self, table, columns):
+        return self.assertIndexExists(table, columns, False)
diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
new file mode 100644
index 0000000000..d775d1eba7
--- /dev/null
+++ b/tests/migrations/test_commands.py
@@ -0,0 +1,37 @@
+from django.core.management import call_command
+from django.test.utils import override_settings
+from .test_base import MigrationTestBase
+
+
+class CommandTests(MigrationTestBase):
+    """
+    Tests running the commands (migrate, makemigrations).
+    """
+
+    @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
+    def test_migrate(self):
+        """
+        Tests basic usage of the migrate command.
+        """
+        # Make sure no tables are created
+        self.assertTableNotExists("migrations_author")
+        self.assertTableNotExists("migrations_tribble")
+        self.assertTableNotExists("migrations_book")
+        # Run the migrations to 0001 only
+        call_command("migrate", "migrations", "0001", verbosity=0)
+        # Make sure the right tables exist
+        self.assertTableExists("migrations_author")
+        self.assertTableExists("migrations_tribble")
+        self.assertTableNotExists("migrations_book")
+        # Run migrations all the way
+        call_command("migrate", verbosity=0)
+        # Make sure the right tables exist
+        self.assertTableExists("migrations_author")
+        self.assertTableNotExists("migrations_tribble")
+        self.assertTableExists("migrations_book")
+        # Unmigrate everything
+        call_command("migrate", "migrations", "zero", verbosity=0)
+        # Make sure it's all gone
+        self.assertTableNotExists("migrations_author")
+        self.assertTableNotExists("migrations_tribble")
+        self.assertTableNotExists("migrations_book")
diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py
index c426defe4a..ddbfa78db7 100644
--- a/tests/migrations/test_executor.py
+++ b/tests/migrations/test_executor.py
@@ -20,6 +20,7 @@ class ExecutorTests(TransactionTestCase):
         Tests running a simple set of migrations.
         """
         executor = MigrationExecutor(connection)
+        executor.recorder.flush()
         # Let's look at the plan first and make sure it's up to scratch
         plan = executor.migration_plan([("migrations", "0002_second")])
         self.assertEqual(
diff --git a/tests/migrations/test_migrations/0001_initial.py b/tests/migrations/test_migrations/0001_initial.py
index e2ed8559a6..f20bac8aec 100644
--- a/tests/migrations/test_migrations/0001_initial.py
+++ b/tests/migrations/test_migrations/0001_initial.py
@@ -12,7 +12,7 @@ class Migration(migrations.Migration):
                 ("name", models.CharField(max_length=255)),
                 ("slug", models.SlugField(null=True)),
                 ("age", models.IntegerField(default=0)),
-                ("silly_field", models.BooleanField()),
+                ("silly_field", models.BooleanField(default=False)),
             ],
         ),
 
diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py
index 810ed0b929..c74d40e4f2 100644
--- a/tests/migrations/test_operations.py
+++ b/tests/migrations/test_operations.py
@@ -1,48 +1,17 @@
-from django.test import TestCase
 from django.db import connection, models, migrations
 from django.db.transaction import atomic
 from django.db.utils import IntegrityError
 from django.db.migrations.state import ProjectState
+from .test_base import MigrationTestBase
 
 
-class OperationTests(TestCase):
+class OperationTests(MigrationTestBase):
     """
     Tests running the operations and making sure they do what they say they do.
     Each test looks at their state changing, and then their database operation -
     both forwards and backwards.
     """
 
-    def assertTableExists(self, table):
-        self.assertIn(table, connection.introspection.get_table_list(connection.cursor()))
-
-    def assertTableNotExists(self, table):
-        self.assertNotIn(table, connection.introspection.get_table_list(connection.cursor()))
-
-    def assertColumnExists(self, table, column):
-        self.assertIn(column, [c.name for c in connection.introspection.get_table_description(connection.cursor(), table)])
-
-    def assertColumnNotExists(self, table, column):
-        self.assertNotIn(column, [c.name for c in connection.introspection.get_table_description(connection.cursor(), table)])
-
-    def assertColumnNull(self, table, column):
-        self.assertEqual([c.null_ok for c in connection.introspection.get_table_description(connection.cursor(), table) if c.name == column][0], True)
-
-    def assertColumnNotNull(self, table, column):
-        self.assertEqual([c.null_ok for c in connection.introspection.get_table_description(connection.cursor(), table) if c.name == column][0], False)
-
-    def assertIndexExists(self, table, columns, value=True):
-        self.assertEqual(
-            value,
-            any(
-                c["index"]
-                for c in connection.introspection.get_constraints(connection.cursor(), table).values()
-                if c['columns'] == list(columns)
-            ),
-        )
-
-    def assertIndexNotExists(self, table, columns):
-        return self.assertIndexExists(table, columns, False)
-
     def set_up_test_model(self, app_label):
         """
         Creates a test model state and database table.