tests.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. from __future__ import unicode_literals
  2. import sys
  3. import threading
  4. import time
  5. from unittest import skipIf, skipUnless
  6. from django.db import (
  7. DatabaseError, Error, IntegrityError, OperationalError, connection,
  8. transaction,
  9. )
  10. from django.test import (
  11. TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature,
  12. )
  13. from django.utils import six
  14. from .models import Reporter
  15. @skipUnless(connection.features.uses_savepoints,
  16. "'atomic' requires transactions and savepoints.")
  17. class AtomicTests(TransactionTestCase):
  18. """
  19. Tests for the atomic decorator and context manager.
  20. The tests make assertions on internal attributes because there isn't a
  21. robust way to ask the database for its current transaction state.
  22. Since the decorator syntax is converted into a context manager (see the
  23. implementation), there are only a few basic tests with the decorator
  24. syntax and the bulk of the tests use the context manager syntax.
  25. """
  26. available_apps = ['transactions']
  27. def test_decorator_syntax_commit(self):
  28. @transaction.atomic
  29. def make_reporter():
  30. Reporter.objects.create(first_name="Tintin")
  31. make_reporter()
  32. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  33. def test_decorator_syntax_rollback(self):
  34. @transaction.atomic
  35. def make_reporter():
  36. Reporter.objects.create(first_name="Haddock")
  37. raise Exception("Oops, that's his last name")
  38. with six.assertRaisesRegex(self, Exception, "Oops"):
  39. make_reporter()
  40. self.assertQuerysetEqual(Reporter.objects.all(), [])
  41. def test_alternate_decorator_syntax_commit(self):
  42. @transaction.atomic()
  43. def make_reporter():
  44. Reporter.objects.create(first_name="Tintin")
  45. make_reporter()
  46. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  47. def test_alternate_decorator_syntax_rollback(self):
  48. @transaction.atomic()
  49. def make_reporter():
  50. Reporter.objects.create(first_name="Haddock")
  51. raise Exception("Oops, that's his last name")
  52. with six.assertRaisesRegex(self, Exception, "Oops"):
  53. make_reporter()
  54. self.assertQuerysetEqual(Reporter.objects.all(), [])
  55. def test_commit(self):
  56. with transaction.atomic():
  57. Reporter.objects.create(first_name="Tintin")
  58. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  59. def test_rollback(self):
  60. with six.assertRaisesRegex(self, Exception, "Oops"):
  61. with transaction.atomic():
  62. Reporter.objects.create(first_name="Haddock")
  63. raise Exception("Oops, that's his last name")
  64. self.assertQuerysetEqual(Reporter.objects.all(), [])
  65. def test_nested_commit_commit(self):
  66. with transaction.atomic():
  67. Reporter.objects.create(first_name="Tintin")
  68. with transaction.atomic():
  69. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  70. self.assertQuerysetEqual(Reporter.objects.all(),
  71. ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>'])
  72. def test_nested_commit_rollback(self):
  73. with transaction.atomic():
  74. Reporter.objects.create(first_name="Tintin")
  75. with six.assertRaisesRegex(self, Exception, "Oops"):
  76. with transaction.atomic():
  77. Reporter.objects.create(first_name="Haddock")
  78. raise Exception("Oops, that's his last name")
  79. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  80. def test_nested_rollback_commit(self):
  81. with six.assertRaisesRegex(self, Exception, "Oops"):
  82. with transaction.atomic():
  83. Reporter.objects.create(last_name="Tintin")
  84. with transaction.atomic():
  85. Reporter.objects.create(last_name="Haddock")
  86. raise Exception("Oops, that's his first name")
  87. self.assertQuerysetEqual(Reporter.objects.all(), [])
  88. def test_nested_rollback_rollback(self):
  89. with six.assertRaisesRegex(self, Exception, "Oops"):
  90. with transaction.atomic():
  91. Reporter.objects.create(last_name="Tintin")
  92. with six.assertRaisesRegex(self, Exception, "Oops"):
  93. with transaction.atomic():
  94. Reporter.objects.create(first_name="Haddock")
  95. raise Exception("Oops, that's his last name")
  96. raise Exception("Oops, that's his first name")
  97. self.assertQuerysetEqual(Reporter.objects.all(), [])
  98. def test_merged_commit_commit(self):
  99. with transaction.atomic():
  100. Reporter.objects.create(first_name="Tintin")
  101. with transaction.atomic(savepoint=False):
  102. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  103. self.assertQuerysetEqual(Reporter.objects.all(),
  104. ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>'])
  105. def test_merged_commit_rollback(self):
  106. with transaction.atomic():
  107. Reporter.objects.create(first_name="Tintin")
  108. with six.assertRaisesRegex(self, Exception, "Oops"):
  109. with transaction.atomic(savepoint=False):
  110. Reporter.objects.create(first_name="Haddock")
  111. raise Exception("Oops, that's his last name")
  112. # Writes in the outer block are rolled back too.
  113. self.assertQuerysetEqual(Reporter.objects.all(), [])
  114. def test_merged_rollback_commit(self):
  115. with six.assertRaisesRegex(self, Exception, "Oops"):
  116. with transaction.atomic():
  117. Reporter.objects.create(last_name="Tintin")
  118. with transaction.atomic(savepoint=False):
  119. Reporter.objects.create(last_name="Haddock")
  120. raise Exception("Oops, that's his first name")
  121. self.assertQuerysetEqual(Reporter.objects.all(), [])
  122. def test_merged_rollback_rollback(self):
  123. with six.assertRaisesRegex(self, Exception, "Oops"):
  124. with transaction.atomic():
  125. Reporter.objects.create(last_name="Tintin")
  126. with six.assertRaisesRegex(self, Exception, "Oops"):
  127. with transaction.atomic(savepoint=False):
  128. Reporter.objects.create(first_name="Haddock")
  129. raise Exception("Oops, that's his last name")
  130. raise Exception("Oops, that's his first name")
  131. self.assertQuerysetEqual(Reporter.objects.all(), [])
  132. def test_reuse_commit_commit(self):
  133. atomic = transaction.atomic()
  134. with atomic:
  135. Reporter.objects.create(first_name="Tintin")
  136. with atomic:
  137. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  138. self.assertQuerysetEqual(Reporter.objects.all(),
  139. ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>'])
  140. def test_reuse_commit_rollback(self):
  141. atomic = transaction.atomic()
  142. with atomic:
  143. Reporter.objects.create(first_name="Tintin")
  144. with six.assertRaisesRegex(self, Exception, "Oops"):
  145. with atomic:
  146. Reporter.objects.create(first_name="Haddock")
  147. raise Exception("Oops, that's his last name")
  148. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  149. def test_reuse_rollback_commit(self):
  150. atomic = transaction.atomic()
  151. with six.assertRaisesRegex(self, Exception, "Oops"):
  152. with atomic:
  153. Reporter.objects.create(last_name="Tintin")
  154. with atomic:
  155. Reporter.objects.create(last_name="Haddock")
  156. raise Exception("Oops, that's his first name")
  157. self.assertQuerysetEqual(Reporter.objects.all(), [])
  158. def test_reuse_rollback_rollback(self):
  159. atomic = transaction.atomic()
  160. with six.assertRaisesRegex(self, Exception, "Oops"):
  161. with atomic:
  162. Reporter.objects.create(last_name="Tintin")
  163. with six.assertRaisesRegex(self, Exception, "Oops"):
  164. with atomic:
  165. Reporter.objects.create(first_name="Haddock")
  166. raise Exception("Oops, that's his last name")
  167. raise Exception("Oops, that's his first name")
  168. self.assertQuerysetEqual(Reporter.objects.all(), [])
  169. def test_force_rollback(self):
  170. with transaction.atomic():
  171. Reporter.objects.create(first_name="Tintin")
  172. # atomic block shouldn't rollback, but force it.
  173. self.assertFalse(transaction.get_rollback())
  174. transaction.set_rollback(True)
  175. self.assertQuerysetEqual(Reporter.objects.all(), [])
  176. def test_prevent_rollback(self):
  177. with transaction.atomic():
  178. Reporter.objects.create(first_name="Tintin")
  179. sid = transaction.savepoint()
  180. # trigger a database error inside an inner atomic without savepoint
  181. with self.assertRaises(DatabaseError):
  182. with transaction.atomic(savepoint=False):
  183. with connection.cursor() as cursor:
  184. cursor.execute(
  185. "SELECT no_such_col FROM transactions_reporter")
  186. # prevent atomic from rolling back since we're recovering manually
  187. self.assertTrue(transaction.get_rollback())
  188. transaction.set_rollback(False)
  189. transaction.savepoint_rollback(sid)
  190. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  191. class AtomicInsideTransactionTests(AtomicTests):
  192. """All basic tests for atomic should also pass within an existing transaction."""
  193. def setUp(self):
  194. self.atomic = transaction.atomic()
  195. self.atomic.__enter__()
  196. def tearDown(self):
  197. self.atomic.__exit__(*sys.exc_info())
  198. @skipIf(connection.features.autocommits_when_autocommit_is_off,
  199. "This test requires a non-autocommit mode that doesn't autocommit.")
  200. class AtomicWithoutAutocommitTests(AtomicTests):
  201. """All basic tests for atomic should also pass when autocommit is turned off."""
  202. def setUp(self):
  203. transaction.set_autocommit(False)
  204. def tearDown(self):
  205. # The tests access the database after exercising 'atomic', initiating
  206. # a transaction ; a rollback is required before restoring autocommit.
  207. transaction.rollback()
  208. transaction.set_autocommit(True)
  209. @skipUnless(connection.features.uses_savepoints,
  210. "'atomic' requires transactions and savepoints.")
  211. class AtomicMergeTests(TransactionTestCase):
  212. """Test merging transactions with savepoint=False."""
  213. available_apps = ['transactions']
  214. def test_merged_outer_rollback(self):
  215. with transaction.atomic():
  216. Reporter.objects.create(first_name="Tintin")
  217. with transaction.atomic(savepoint=False):
  218. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  219. with six.assertRaisesRegex(self, Exception, "Oops"):
  220. with transaction.atomic(savepoint=False):
  221. Reporter.objects.create(first_name="Calculus")
  222. raise Exception("Oops, that's his last name")
  223. # The third insert couldn't be roll back. Temporarily mark the
  224. # connection as not needing rollback to check it.
  225. self.assertTrue(transaction.get_rollback())
  226. transaction.set_rollback(False)
  227. self.assertEqual(Reporter.objects.count(), 3)
  228. transaction.set_rollback(True)
  229. # The second insert couldn't be roll back. Temporarily mark the
  230. # connection as not needing rollback to check it.
  231. self.assertTrue(transaction.get_rollback())
  232. transaction.set_rollback(False)
  233. self.assertEqual(Reporter.objects.count(), 3)
  234. transaction.set_rollback(True)
  235. # The first block has a savepoint and must roll back.
  236. self.assertQuerysetEqual(Reporter.objects.all(), [])
  237. def test_merged_inner_savepoint_rollback(self):
  238. with transaction.atomic():
  239. Reporter.objects.create(first_name="Tintin")
  240. with transaction.atomic():
  241. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  242. with six.assertRaisesRegex(self, Exception, "Oops"):
  243. with transaction.atomic(savepoint=False):
  244. Reporter.objects.create(first_name="Calculus")
  245. raise Exception("Oops, that's his last name")
  246. # The third insert couldn't be roll back. Temporarily mark the
  247. # connection as not needing rollback to check it.
  248. self.assertTrue(transaction.get_rollback())
  249. transaction.set_rollback(False)
  250. self.assertEqual(Reporter.objects.count(), 3)
  251. transaction.set_rollback(True)
  252. # The second block has a savepoint and must roll back.
  253. self.assertEqual(Reporter.objects.count(), 1)
  254. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  255. @skipUnless(connection.features.uses_savepoints,
  256. "'atomic' requires transactions and savepoints.")
  257. class AtomicErrorsTests(TransactionTestCase):
  258. available_apps = ['transactions']
  259. def test_atomic_prevents_setting_autocommit(self):
  260. autocommit = transaction.get_autocommit()
  261. with transaction.atomic():
  262. with self.assertRaises(transaction.TransactionManagementError):
  263. transaction.set_autocommit(not autocommit)
  264. # Make sure autocommit wasn't changed.
  265. self.assertEqual(connection.autocommit, autocommit)
  266. def test_atomic_prevents_calling_transaction_methods(self):
  267. with transaction.atomic():
  268. with self.assertRaises(transaction.TransactionManagementError):
  269. transaction.commit()
  270. with self.assertRaises(transaction.TransactionManagementError):
  271. transaction.rollback()
  272. def test_atomic_prevents_queries_in_broken_transaction(self):
  273. r1 = Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  274. with transaction.atomic():
  275. r2 = Reporter(first_name="Cuthbert", last_name="Calculus", id=r1.id)
  276. with self.assertRaises(IntegrityError):
  277. r2.save(force_insert=True)
  278. # The transaction is marked as needing rollback.
  279. with self.assertRaises(transaction.TransactionManagementError):
  280. r2.save(force_update=True)
  281. self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Haddock")
  282. @skipIfDBFeature('atomic_transactions')
  283. def test_atomic_allows_queries_after_fixing_transaction(self):
  284. r1 = Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  285. with transaction.atomic():
  286. r2 = Reporter(first_name="Cuthbert", last_name="Calculus", id=r1.id)
  287. with self.assertRaises(IntegrityError):
  288. r2.save(force_insert=True)
  289. # Mark the transaction as no longer needing rollback.
  290. transaction.set_rollback(False)
  291. r2.save(force_update=True)
  292. self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Calculus")
  293. @skipUnlessDBFeature('test_db_allows_multiple_connections')
  294. def test_atomic_prevents_queries_in_broken_transaction_after_client_close(self):
  295. with transaction.atomic():
  296. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  297. connection.close()
  298. # The connection is closed and the transaction is marked as
  299. # needing rollback. This will raise an InterfaceError on databases
  300. # that refuse to create cursors on closed connections (PostgreSQL)
  301. # and a TransactionManagementError on other databases.
  302. with self.assertRaises(Error):
  303. Reporter.objects.create(first_name="Cuthbert", last_name="Calculus")
  304. # The connection is usable again .
  305. self.assertEqual(Reporter.objects.count(), 0)
  306. @skipUnless(connection.vendor == 'mysql', "MySQL-specific behaviors")
  307. class AtomicMySQLTests(TransactionTestCase):
  308. available_apps = ['transactions']
  309. @skipIf(threading is None, "Test requires threading")
  310. def test_implicit_savepoint_rollback(self):
  311. """MySQL implicitly rolls back savepoints when it deadlocks (#22291)."""
  312. other_thread_ready = threading.Event()
  313. def other_thread():
  314. try:
  315. with transaction.atomic():
  316. Reporter.objects.create(id=1, first_name="Tintin")
  317. other_thread_ready.set()
  318. # We cannot synchronize the two threads with an event here
  319. # because the main thread locks. Sleep for a little while.
  320. time.sleep(1)
  321. # 2) ... and this line deadlocks. (see below for 1)
  322. Reporter.objects.exclude(id=1).update(id=2)
  323. finally:
  324. # This is the thread-local connection, not the main connection.
  325. connection.close()
  326. other_thread = threading.Thread(target=other_thread)
  327. other_thread.start()
  328. other_thread_ready.wait()
  329. with six.assertRaisesRegex(self, OperationalError, 'Deadlock found'):
  330. # Double atomic to enter a transaction and create a savepoint.
  331. with transaction.atomic():
  332. with transaction.atomic():
  333. # 1) This line locks... (see above for 2)
  334. Reporter.objects.create(id=1, first_name="Tintin")
  335. other_thread.join()
  336. class AtomicMiscTests(TransactionTestCase):
  337. available_apps = []
  338. def test_wrap_callable_instance(self):
  339. """#20028 -- Atomic must support wrapping callable instances."""
  340. class Callable(object):
  341. def __call__(self):
  342. pass
  343. # Must not raise an exception
  344. transaction.atomic(Callable())
  345. @skipUnlessDBFeature('can_release_savepoints')
  346. def test_atomic_does_not_leak_savepoints_on_failure(self):
  347. """#23074 -- Savepoints must be released after rollback."""
  348. # Expect an error when rolling back a savepoint that doesn't exist.
  349. # Done outside of the transaction block to ensure proper recovery.
  350. with self.assertRaises(Error):
  351. # Start a plain transaction.
  352. with transaction.atomic():
  353. # Swallow the intentional error raised in the sub-transaction.
  354. with six.assertRaisesRegex(self, Exception, "Oops"):
  355. # Start a sub-transaction with a savepoint.
  356. with transaction.atomic():
  357. sid = connection.savepoint_ids[-1]
  358. raise Exception("Oops")
  359. # This is expected to fail because the savepoint no longer exists.
  360. connection.savepoint_rollback(sid)
  361. @skipIf(connection.features.autocommits_when_autocommit_is_off,
  362. "This test requires a non-autocommit mode that doesn't autocommit.")
  363. def test_orm_query_without_autocommit(self):
  364. """#24921 -- ORM queries must be possible after set_autocommit(False)."""
  365. transaction.set_autocommit(False)
  366. try:
  367. Reporter.objects.create(first_name="Tintin")
  368. finally:
  369. transaction.rollback()
  370. transaction.set_autocommit(True)