tests.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  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. Reporter.objects.create(first_name="Tintin")
  28. make_reporter()
  29. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  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.assertQuerysetEqual(Reporter.objects.all(), [])
  38. def test_alternate_decorator_syntax_commit(self):
  39. @transaction.atomic()
  40. def make_reporter():
  41. Reporter.objects.create(first_name="Tintin")
  42. make_reporter()
  43. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  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.assertQuerysetEqual(Reporter.objects.all(), [])
  52. def test_commit(self):
  53. with transaction.atomic():
  54. Reporter.objects.create(first_name="Tintin")
  55. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  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.assertQuerysetEqual(Reporter.objects.all(), [])
  62. def test_nested_commit_commit(self):
  63. with transaction.atomic():
  64. Reporter.objects.create(first_name="Tintin")
  65. with transaction.atomic():
  66. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  67. self.assertQuerysetEqual(
  68. Reporter.objects.all(),
  69. ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>']
  70. )
  71. def test_nested_commit_rollback(self):
  72. with transaction.atomic():
  73. Reporter.objects.create(first_name="Tintin")
  74. with self.assertRaisesMessage(Exception, "Oops"):
  75. with transaction.atomic():
  76. Reporter.objects.create(first_name="Haddock")
  77. raise Exception("Oops, that's his last name")
  78. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  79. def test_nested_rollback_commit(self):
  80. with self.assertRaisesMessage(Exception, "Oops"):
  81. with transaction.atomic():
  82. Reporter.objects.create(last_name="Tintin")
  83. with transaction.atomic():
  84. Reporter.objects.create(last_name="Haddock")
  85. raise Exception("Oops, that's his first name")
  86. self.assertQuerysetEqual(Reporter.objects.all(), [])
  87. def test_nested_rollback_rollback(self):
  88. with self.assertRaisesMessage(Exception, "Oops"):
  89. with transaction.atomic():
  90. Reporter.objects.create(last_name="Tintin")
  91. with self.assertRaisesMessage(Exception, "Oops"):
  92. with transaction.atomic():
  93. Reporter.objects.create(first_name="Haddock")
  94. raise Exception("Oops, that's his last name")
  95. raise Exception("Oops, that's his first name")
  96. self.assertQuerysetEqual(Reporter.objects.all(), [])
  97. def test_merged_commit_commit(self):
  98. with transaction.atomic():
  99. Reporter.objects.create(first_name="Tintin")
  100. with transaction.atomic(savepoint=False):
  101. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  102. self.assertQuerysetEqual(
  103. Reporter.objects.all(),
  104. ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>']
  105. )
  106. def test_merged_commit_rollback(self):
  107. with transaction.atomic():
  108. Reporter.objects.create(first_name="Tintin")
  109. with self.assertRaisesMessage(Exception, "Oops"):
  110. with transaction.atomic(savepoint=False):
  111. Reporter.objects.create(first_name="Haddock")
  112. raise Exception("Oops, that's his last name")
  113. # Writes in the outer block are rolled back too.
  114. self.assertQuerysetEqual(Reporter.objects.all(), [])
  115. def test_merged_rollback_commit(self):
  116. with self.assertRaisesMessage(Exception, "Oops"):
  117. with transaction.atomic():
  118. Reporter.objects.create(last_name="Tintin")
  119. with transaction.atomic(savepoint=False):
  120. Reporter.objects.create(last_name="Haddock")
  121. raise Exception("Oops, that's his first name")
  122. self.assertQuerysetEqual(Reporter.objects.all(), [])
  123. def test_merged_rollback_rollback(self):
  124. with self.assertRaisesMessage(Exception, "Oops"):
  125. with transaction.atomic():
  126. Reporter.objects.create(last_name="Tintin")
  127. with self.assertRaisesMessage(Exception, "Oops"):
  128. with transaction.atomic(savepoint=False):
  129. Reporter.objects.create(first_name="Haddock")
  130. raise Exception("Oops, that's his last name")
  131. raise Exception("Oops, that's his first name")
  132. self.assertQuerysetEqual(Reporter.objects.all(), [])
  133. def test_reuse_commit_commit(self):
  134. atomic = transaction.atomic()
  135. with atomic:
  136. Reporter.objects.create(first_name="Tintin")
  137. with atomic:
  138. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  139. self.assertQuerysetEqual(Reporter.objects.all(), ['<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 self.assertRaisesMessage(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 self.assertRaisesMessage(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 self.assertRaisesMessage(Exception, "Oops"):
  161. with atomic:
  162. Reporter.objects.create(last_name="Tintin")
  163. with self.assertRaisesMessage(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. class AtomicWithoutAutocommitTests(AtomicTests):
  199. """All basic tests for atomic should also pass when autocommit is turned off."""
  200. def setUp(self):
  201. transaction.set_autocommit(False)
  202. def tearDown(self):
  203. # The tests access the database after exercising 'atomic', initiating
  204. # a transaction ; a rollback is required before restoring autocommit.
  205. transaction.rollback()
  206. transaction.set_autocommit(True)
  207. @skipUnlessDBFeature('uses_savepoints')
  208. class AtomicMergeTests(TransactionTestCase):
  209. """Test merging transactions with savepoint=False."""
  210. available_apps = ['transactions']
  211. def test_merged_outer_rollback(self):
  212. with transaction.atomic():
  213. Reporter.objects.create(first_name="Tintin")
  214. with transaction.atomic(savepoint=False):
  215. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  216. with self.assertRaisesMessage(Exception, "Oops"):
  217. with transaction.atomic(savepoint=False):
  218. Reporter.objects.create(first_name="Calculus")
  219. raise Exception("Oops, that's his last name")
  220. # The third 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 second insert couldn't be roll back. Temporarily mark the
  227. # connection as not needing rollback to check it.
  228. self.assertTrue(transaction.get_rollback())
  229. transaction.set_rollback(False)
  230. self.assertEqual(Reporter.objects.count(), 3)
  231. transaction.set_rollback(True)
  232. # The first block has a savepoint and must roll back.
  233. self.assertQuerysetEqual(Reporter.objects.all(), [])
  234. def test_merged_inner_savepoint_rollback(self):
  235. with transaction.atomic():
  236. Reporter.objects.create(first_name="Tintin")
  237. with transaction.atomic():
  238. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  239. with self.assertRaisesMessage(Exception, "Oops"):
  240. with transaction.atomic(savepoint=False):
  241. Reporter.objects.create(first_name="Calculus")
  242. raise Exception("Oops, that's his last name")
  243. # The third insert couldn't be roll back. Temporarily mark the
  244. # connection as not needing rollback to check it.
  245. self.assertTrue(transaction.get_rollback())
  246. transaction.set_rollback(False)
  247. self.assertEqual(Reporter.objects.count(), 3)
  248. transaction.set_rollback(True)
  249. # The second block has a savepoint and must roll back.
  250. self.assertEqual(Reporter.objects.count(), 1)
  251. self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
  252. @skipUnlessDBFeature('uses_savepoints')
  253. class AtomicErrorsTests(TransactionTestCase):
  254. available_apps = ['transactions']
  255. forbidden_atomic_msg = "This is forbidden when an 'atomic' block is active."
  256. def test_atomic_prevents_setting_autocommit(self):
  257. autocommit = transaction.get_autocommit()
  258. with transaction.atomic():
  259. with self.assertRaisesMessage(transaction.TransactionManagementError, self.forbidden_atomic_msg):
  260. transaction.set_autocommit(not autocommit)
  261. # Make sure autocommit wasn't changed.
  262. self.assertEqual(connection.autocommit, autocommit)
  263. def test_atomic_prevents_calling_transaction_methods(self):
  264. with transaction.atomic():
  265. with self.assertRaisesMessage(transaction.TransactionManagementError, self.forbidden_atomic_msg):
  266. transaction.commit()
  267. with self.assertRaisesMessage(transaction.TransactionManagementError, self.forbidden_atomic_msg):
  268. transaction.rollback()
  269. def test_atomic_prevents_queries_in_broken_transaction(self):
  270. r1 = Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  271. with transaction.atomic():
  272. r2 = Reporter(first_name="Cuthbert", last_name="Calculus", id=r1.id)
  273. with self.assertRaises(IntegrityError):
  274. r2.save(force_insert=True)
  275. # The transaction is marked as needing rollback.
  276. msg = (
  277. "An error occurred in the current transaction. You can't "
  278. "execute queries until the end of the 'atomic' block."
  279. )
  280. with self.assertRaisesMessage(transaction.TransactionManagementError, msg):
  281. r2.save(force_update=True)
  282. self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Haddock")
  283. @skipIfDBFeature('atomic_transactions')
  284. def test_atomic_allows_queries_after_fixing_transaction(self):
  285. r1 = Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  286. with transaction.atomic():
  287. r2 = Reporter(first_name="Cuthbert", last_name="Calculus", id=r1.id)
  288. with self.assertRaises(IntegrityError):
  289. r2.save(force_insert=True)
  290. # Mark the transaction as no longer needing rollback.
  291. transaction.set_rollback(False)
  292. r2.save(force_update=True)
  293. self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Calculus")
  294. @skipUnlessDBFeature('test_db_allows_multiple_connections')
  295. def test_atomic_prevents_queries_in_broken_transaction_after_client_close(self):
  296. with transaction.atomic():
  297. Reporter.objects.create(first_name="Archibald", last_name="Haddock")
  298. connection.close()
  299. # The connection is closed and the transaction is marked as
  300. # needing rollback. This will raise an InterfaceError on databases
  301. # that refuse to create cursors on closed connections (PostgreSQL)
  302. # and a TransactionManagementError on other databases.
  303. with self.assertRaises(Error):
  304. Reporter.objects.create(first_name="Cuthbert", last_name="Calculus")
  305. # The connection is usable again .
  306. self.assertEqual(Reporter.objects.count(), 0)
  307. @skipUnless(connection.vendor == 'mysql', "MySQL-specific behaviors")
  308. class AtomicMySQLTests(TransactionTestCase):
  309. available_apps = ['transactions']
  310. @skipIf(threading is None, "Test requires threading")
  311. def test_implicit_savepoint_rollback(self):
  312. """MySQL implicitly rolls back savepoints when it deadlocks (#22291)."""
  313. Reporter.objects.create(id=1)
  314. Reporter.objects.create(id=2)
  315. main_thread_ready = threading.Event()
  316. def other_thread():
  317. try:
  318. with transaction.atomic():
  319. Reporter.objects.select_for_update().get(id=1)
  320. main_thread_ready.wait()
  321. # 1) This line locks... (see below for 2)
  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. with self.assertRaisesMessage(OperationalError, 'Deadlock found'):
  329. # Double atomic to enter a transaction and create a savepoint.
  330. with transaction.atomic():
  331. with transaction.atomic():
  332. Reporter.objects.select_for_update().get(id=2)
  333. main_thread_ready.set()
  334. # The two threads can't be synchronized with an event here
  335. # because the other thread locks. Sleep for a little while.
  336. time.sleep(1)
  337. # 2) ... and this line deadlocks. (see above for 1)
  338. Reporter.objects.exclude(id=2).update(id=1)
  339. other_thread.join()
  340. class AtomicMiscTests(TransactionTestCase):
  341. available_apps = ['transactions']
  342. def test_wrap_callable_instance(self):
  343. """#20028 -- Atomic must support wrapping callable instances."""
  344. class Callable:
  345. def __call__(self):
  346. pass
  347. # Must not raise an exception
  348. transaction.atomic(Callable())
  349. @skipUnlessDBFeature('can_release_savepoints')
  350. def test_atomic_does_not_leak_savepoints_on_failure(self):
  351. """#23074 -- Savepoints must be released after rollback."""
  352. # Expect an error when rolling back a savepoint that doesn't exist.
  353. # Done outside of the transaction block to ensure proper recovery.
  354. with self.assertRaises(Error):
  355. # Start a plain transaction.
  356. with transaction.atomic():
  357. # Swallow the intentional error raised in the sub-transaction.
  358. with self.assertRaisesMessage(Exception, "Oops"):
  359. # Start a sub-transaction with a savepoint.
  360. with transaction.atomic():
  361. sid = connection.savepoint_ids[-1]
  362. raise Exception("Oops")
  363. # This is expected to fail because the savepoint no longer exists.
  364. connection.savepoint_rollback(sid)
  365. def test_mark_for_rollback_on_error_in_transaction(self):
  366. with transaction.atomic(savepoint=False):
  367. # Swallow the intentional error raised.
  368. with self.assertRaisesMessage(Exception, "Oops"):
  369. # Wrap in `mark_for_rollback_on_error` to check if the transaction is marked broken.
  370. with transaction.mark_for_rollback_on_error():
  371. # Ensure that we are still in a good state.
  372. self.assertFalse(transaction.get_rollback())
  373. raise Exception("Oops")
  374. # Ensure that `mark_for_rollback_on_error` marked the transaction as broken …
  375. self.assertTrue(transaction.get_rollback())
  376. # … and further queries fail.
  377. msg = "You can't execute queries until the end of the 'atomic' block."
  378. with self.assertRaisesMessage(transaction.TransactionManagementError, msg):
  379. Reporter.objects.create()
  380. # Transaction errors are reset at the end of an transaction, so this should just work.
  381. Reporter.objects.create()
  382. def test_mark_for_rollback_on_error_in_autocommit(self):
  383. self.assertTrue(transaction.get_autocommit())
  384. # Swallow the intentional error raised.
  385. with self.assertRaisesMessage(Exception, "Oops"):
  386. # Wrap in `mark_for_rollback_on_error` to check if the transaction is marked broken.
  387. with transaction.mark_for_rollback_on_error():
  388. # Ensure that we are still in a good state.
  389. self.assertFalse(transaction.get_connection().needs_rollback)
  390. raise Exception("Oops")
  391. # Ensure that `mark_for_rollback_on_error` did not mark the transaction
  392. # as broken, since we are in autocommit mode …
  393. self.assertFalse(transaction.get_connection().needs_rollback)
  394. # … and further queries work nicely.
  395. Reporter.objects.create()
  396. class NonAutocommitTests(TransactionTestCase):
  397. available_apps = []
  398. def test_orm_query_after_error_and_rollback(self):
  399. """
  400. ORM queries are allowed after an error and a rollback in non-autocommit
  401. mode (#27504).
  402. """
  403. transaction.set_autocommit(False)
  404. r1 = Reporter.objects.create(first_name='Archibald', last_name='Haddock')
  405. r2 = Reporter(first_name='Cuthbert', last_name='Calculus', id=r1.id)
  406. with self.assertRaises(IntegrityError):
  407. r2.save(force_insert=True)
  408. transaction.rollback()
  409. Reporter.objects.last()
  410. def test_orm_query_without_autocommit(self):
  411. """#24921 -- ORM queries must be possible after set_autocommit(False)."""
  412. transaction.set_autocommit(False)
  413. try:
  414. Reporter.objects.create(first_name="Tintin")
  415. finally:
  416. transaction.rollback()
  417. transaction.set_autocommit(True)