tests.py 22 KB

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