test_loader.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. from __future__ import unicode_literals
  2. from unittest import skipIf
  3. from django.db import connection, connections
  4. from django.db.migrations.exceptions import AmbiguityError, NodeNotFoundError
  5. from django.db.migrations.loader import MigrationLoader
  6. from django.db.migrations.recorder import MigrationRecorder
  7. from django.test import TestCase, modify_settings, override_settings
  8. from django.utils import six
  9. class RecorderTests(TestCase):
  10. """
  11. Tests recording migrations as applied or not.
  12. """
  13. def test_apply(self):
  14. """
  15. Tests marking migrations as applied/unapplied.
  16. """
  17. recorder = MigrationRecorder(connection)
  18. self.assertEqual(
  19. set((x, y) for (x, y) in recorder.applied_migrations() if x == "myapp"),
  20. set(),
  21. )
  22. recorder.record_applied("myapp", "0432_ponies")
  23. self.assertEqual(
  24. set((x, y) for (x, y) in recorder.applied_migrations() if x == "myapp"),
  25. {("myapp", "0432_ponies")},
  26. )
  27. # That should not affect records of another database
  28. recorder_other = MigrationRecorder(connections['other'])
  29. self.assertEqual(
  30. set((x, y) for (x, y) in recorder_other.applied_migrations() if x == "myapp"),
  31. set(),
  32. )
  33. recorder.record_unapplied("myapp", "0432_ponies")
  34. self.assertEqual(
  35. set((x, y) for (x, y) in recorder.applied_migrations() if x == "myapp"),
  36. set(),
  37. )
  38. class LoaderTests(TestCase):
  39. """
  40. Tests the disk and database loader, and running through migrations
  41. in memory.
  42. """
  43. @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
  44. @modify_settings(INSTALLED_APPS={'append': 'basic'})
  45. def test_load(self):
  46. """
  47. Makes sure the loader can load the migrations for the test apps,
  48. and then render them out to a new Apps.
  49. """
  50. # Load and test the plan
  51. migration_loader = MigrationLoader(connection)
  52. self.assertEqual(
  53. migration_loader.graph.forwards_plan(("migrations", "0002_second")),
  54. [
  55. ("migrations", "0001_initial"),
  56. ("migrations", "0002_second"),
  57. ],
  58. )
  59. # Now render it out!
  60. project_state = migration_loader.project_state(("migrations", "0002_second"))
  61. self.assertEqual(len(project_state.models), 2)
  62. author_state = project_state.models["migrations", "author"]
  63. self.assertEqual(
  64. [x for x, y in author_state.fields],
  65. ["id", "name", "slug", "age", "rating"]
  66. )
  67. book_state = project_state.models["migrations", "book"]
  68. self.assertEqual(
  69. [x for x, y in book_state.fields],
  70. ["id", "author"]
  71. )
  72. # Ensure we've included unmigrated apps in there too
  73. self.assertIn("basic", project_state.real_apps)
  74. @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_unmigdep"})
  75. def test_load_unmigrated_dependency(self):
  76. """
  77. Makes sure the loader can load migrations with a dependency on an unmigrated app.
  78. """
  79. # Load and test the plan
  80. migration_loader = MigrationLoader(connection)
  81. self.assertEqual(
  82. migration_loader.graph.forwards_plan(("migrations", "0001_initial")),
  83. [
  84. ('contenttypes', '0001_initial'),
  85. ('auth', '0001_initial'),
  86. ("migrations", "0001_initial"),
  87. ],
  88. )
  89. # Now render it out!
  90. project_state = migration_loader.project_state(("migrations", "0001_initial"))
  91. self.assertEqual(len([m for a, m in project_state.models if a == "migrations"]), 1)
  92. book_state = project_state.models["migrations", "book"]
  93. self.assertEqual(
  94. [x for x, y in book_state.fields],
  95. ["id", "user"]
  96. )
  97. @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_run_before"})
  98. def test_run_before(self):
  99. """
  100. Makes sure the loader uses Migration.run_before.
  101. """
  102. # Load and test the plan
  103. migration_loader = MigrationLoader(connection)
  104. self.assertEqual(
  105. migration_loader.graph.forwards_plan(("migrations", "0002_second")),
  106. [
  107. ("migrations", "0001_initial"),
  108. ("migrations", "0003_third"),
  109. ("migrations", "0002_second"),
  110. ],
  111. )
  112. @override_settings(MIGRATION_MODULES={
  113. "migrations": "migrations.test_migrations_first",
  114. "migrations2": "migrations2.test_migrations_2_first",
  115. })
  116. @modify_settings(INSTALLED_APPS={'append': 'migrations2'})
  117. def test_first(self):
  118. """
  119. Makes sure the '__first__' migrations build correctly.
  120. """
  121. migration_loader = MigrationLoader(connection)
  122. self.assertEqual(
  123. migration_loader.graph.forwards_plan(("migrations", "second")),
  124. [
  125. ("migrations", "thefirst"),
  126. ("migrations2", "0001_initial"),
  127. ("migrations2", "0002_second"),
  128. ("migrations", "second"),
  129. ],
  130. )
  131. @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
  132. def test_name_match(self):
  133. "Tests prefix name matching"
  134. migration_loader = MigrationLoader(connection)
  135. self.assertEqual(
  136. migration_loader.get_migration_by_prefix("migrations", "0001").name,
  137. "0001_initial",
  138. )
  139. with self.assertRaises(AmbiguityError):
  140. migration_loader.get_migration_by_prefix("migrations", "0")
  141. with self.assertRaises(KeyError):
  142. migration_loader.get_migration_by_prefix("migrations", "blarg")
  143. def test_load_import_error(self):
  144. with override_settings(MIGRATION_MODULES={"migrations": "import_error_package"}):
  145. with self.assertRaises(ImportError):
  146. MigrationLoader(connection)
  147. def test_load_module_file(self):
  148. with override_settings(MIGRATION_MODULES={"migrations": "migrations.faulty_migrations.file"}):
  149. MigrationLoader(connection)
  150. @skipIf(six.PY2, "PY2 doesn't load empty dirs.")
  151. def test_load_empty_dir(self):
  152. with override_settings(MIGRATION_MODULES={"migrations": "migrations.faulty_migrations.namespace"}):
  153. MigrationLoader(connection)
  154. @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed"})
  155. def test_loading_squashed(self):
  156. "Tests loading a squashed migration"
  157. migration_loader = MigrationLoader(connection)
  158. recorder = MigrationRecorder(connection)
  159. # Loading with nothing applied should just give us the one node
  160. self.assertEqual(
  161. len([x for x in migration_loader.graph.nodes if x[0] == "migrations"]),
  162. 1,
  163. )
  164. # However, fake-apply one migration and it should now use the old two
  165. recorder.record_applied("migrations", "0001_initial")
  166. migration_loader.build_graph()
  167. self.assertEqual(
  168. len([x for x in migration_loader.graph.nodes if x[0] == "migrations"]),
  169. 2,
  170. )
  171. recorder.flush()
  172. @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed_complex"})
  173. def test_loading_squashed_complex(self):
  174. "Tests loading a complex set of squashed migrations"
  175. loader = MigrationLoader(connection)
  176. recorder = MigrationRecorder(connection)
  177. def num_nodes():
  178. plan = set(loader.graph.forwards_plan(('migrations', '7_auto')))
  179. return len(plan - loader.applied_migrations)
  180. # Empty database: use squashed migration
  181. loader.build_graph()
  182. self.assertEqual(num_nodes(), 5)
  183. # Starting at 1 or 2 should use the squashed migration too
  184. recorder.record_applied("migrations", "1_auto")
  185. loader.build_graph()
  186. self.assertEqual(num_nodes(), 4)
  187. recorder.record_applied("migrations", "2_auto")
  188. loader.build_graph()
  189. self.assertEqual(num_nodes(), 3)
  190. # However, starting at 3 to 5 cannot use the squashed migration
  191. recorder.record_applied("migrations", "3_auto")
  192. loader.build_graph()
  193. self.assertEqual(num_nodes(), 4)
  194. recorder.record_applied("migrations", "4_auto")
  195. loader.build_graph()
  196. self.assertEqual(num_nodes(), 3)
  197. # Starting at 5 to 7 we are passed the squashed migrations
  198. recorder.record_applied("migrations", "5_auto")
  199. loader.build_graph()
  200. self.assertEqual(num_nodes(), 2)
  201. recorder.record_applied("migrations", "6_auto")
  202. loader.build_graph()
  203. self.assertEqual(num_nodes(), 1)
  204. recorder.record_applied("migrations", "7_auto")
  205. loader.build_graph()
  206. self.assertEqual(num_nodes(), 0)
  207. recorder.flush()
  208. @override_settings(MIGRATION_MODULES={
  209. "app1": "migrations.test_migrations_squashed_complex_multi_apps.app1",
  210. "app2": "migrations.test_migrations_squashed_complex_multi_apps.app2",
  211. })
  212. @modify_settings(INSTALLED_APPS={'append': [
  213. "migrations.test_migrations_squashed_complex_multi_apps.app1",
  214. "migrations.test_migrations_squashed_complex_multi_apps.app2",
  215. ]})
  216. def test_loading_squashed_complex_multi_apps(self):
  217. loader = MigrationLoader(connection)
  218. loader.build_graph()
  219. plan = set(loader.graph.forwards_plan(('app1', '4_auto')))
  220. expected_plan = set([
  221. ('app1', '4_auto'),
  222. ('app1', '2_squashed_3'),
  223. ('app2', '1_squashed_2'),
  224. ('app1', '1_auto')
  225. ])
  226. self.assertEqual(plan, expected_plan)
  227. @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_squashed_erroneous"})
  228. def test_loading_squashed_erroneous(self):
  229. "Tests loading a complex but erroneous set of squashed migrations"
  230. loader = MigrationLoader(connection)
  231. recorder = MigrationRecorder(connection)
  232. def num_nodes():
  233. plan = set(loader.graph.forwards_plan(('migrations', '7_auto')))
  234. return len(plan - loader.applied_migrations)
  235. # Empty database: use squashed migration
  236. loader.build_graph()
  237. self.assertEqual(num_nodes(), 5)
  238. # Starting at 1 or 2 should use the squashed migration too
  239. recorder.record_applied("migrations", "1_auto")
  240. loader.build_graph()
  241. self.assertEqual(num_nodes(), 4)
  242. recorder.record_applied("migrations", "2_auto")
  243. loader.build_graph()
  244. self.assertEqual(num_nodes(), 3)
  245. # However, starting at 3 or 4 we'd need to use non-existing migrations
  246. msg = ("Migration migrations.6_auto depends on nonexistent node ('migrations', '5_auto'). "
  247. "Django tried to replace migration migrations.5_auto with any of "
  248. "[migrations.3_squashed_5] but wasn't able to because some of the replaced "
  249. "migrations are already applied.")
  250. recorder.record_applied("migrations", "3_auto")
  251. with self.assertRaisesMessage(NodeNotFoundError, msg):
  252. loader.build_graph()
  253. recorder.record_applied("migrations", "4_auto")
  254. with self.assertRaisesMessage(NodeNotFoundError, msg):
  255. loader.build_graph()
  256. # Starting at 5 to 7 we are passed the squashed migrations
  257. recorder.record_applied("migrations", "5_auto")
  258. loader.build_graph()
  259. self.assertEqual(num_nodes(), 2)
  260. recorder.record_applied("migrations", "6_auto")
  261. loader.build_graph()
  262. self.assertEqual(num_nodes(), 1)
  263. recorder.record_applied("migrations", "7_auto")
  264. loader.build_graph()
  265. self.assertEqual(num_nodes(), 0)
  266. recorder.flush()