tests.py 20 KB

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