test_loader.py 12 KB

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