tests.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. from django.db import connection, transaction
  2. from django.test import TransactionTestCase, skipUnlessDBFeature
  3. from .models import Thing
  4. class ForcedError(Exception):
  5. pass
  6. class TestConnectionOnCommit(TransactionTestCase):
  7. """
  8. Tests for transaction.on_commit().
  9. Creation/checking of database objects in parallel with callback tracking is
  10. to verify that the behavior of the two match in all tested cases.
  11. """
  12. available_apps = ['transaction_hooks']
  13. def setUp(self):
  14. self.notified = []
  15. def notify(self, id_):
  16. if id_ == 'error':
  17. raise ForcedError()
  18. self.notified.append(id_)
  19. def do(self, num):
  20. """Create a Thing instance and notify about it."""
  21. Thing.objects.create(num=num)
  22. transaction.on_commit(lambda: self.notify(num))
  23. def assertDone(self, nums):
  24. self.assertNotified(nums)
  25. self.assertEqual(sorted(t.num for t in Thing.objects.all()), sorted(nums))
  26. def assertNotified(self, nums):
  27. self.assertEqual(self.notified, nums)
  28. def test_executes_immediately_if_no_transaction(self):
  29. self.do(1)
  30. self.assertDone([1])
  31. def test_delays_execution_until_after_transaction_commit(self):
  32. with transaction.atomic():
  33. self.do(1)
  34. self.assertNotified([])
  35. self.assertDone([1])
  36. def test_does_not_execute_if_transaction_rolled_back(self):
  37. try:
  38. with transaction.atomic():
  39. self.do(1)
  40. raise ForcedError()
  41. except ForcedError:
  42. pass
  43. self.assertDone([])
  44. def test_executes_only_after_final_transaction_committed(self):
  45. with transaction.atomic():
  46. with transaction.atomic():
  47. self.do(1)
  48. self.assertNotified([])
  49. self.assertNotified([])
  50. self.assertDone([1])
  51. def test_discards_hooks_from_rolled_back_savepoint(self):
  52. with transaction.atomic():
  53. # one successful savepoint
  54. with transaction.atomic():
  55. self.do(1)
  56. # one failed savepoint
  57. try:
  58. with transaction.atomic():
  59. self.do(2)
  60. raise ForcedError()
  61. except ForcedError:
  62. pass
  63. # another successful savepoint
  64. with transaction.atomic():
  65. self.do(3)
  66. # only hooks registered during successful savepoints execute
  67. self.assertDone([1, 3])
  68. def test_no_hooks_run_from_failed_transaction(self):
  69. """If outer transaction fails, no hooks from within it run."""
  70. try:
  71. with transaction.atomic():
  72. with transaction.atomic():
  73. self.do(1)
  74. raise ForcedError()
  75. except ForcedError:
  76. pass
  77. self.assertDone([])
  78. def test_inner_savepoint_rolled_back_with_outer(self):
  79. with transaction.atomic():
  80. try:
  81. with transaction.atomic():
  82. with transaction.atomic():
  83. self.do(1)
  84. raise ForcedError()
  85. except ForcedError:
  86. pass
  87. self.do(2)
  88. self.assertDone([2])
  89. def test_no_savepoints_atomic_merged_with_outer(self):
  90. with transaction.atomic():
  91. with transaction.atomic():
  92. self.do(1)
  93. try:
  94. with transaction.atomic(savepoint=False):
  95. raise ForcedError()
  96. except ForcedError:
  97. pass
  98. self.assertDone([])
  99. def test_inner_savepoint_does_not_affect_outer(self):
  100. with transaction.atomic():
  101. with transaction.atomic():
  102. self.do(1)
  103. try:
  104. with transaction.atomic():
  105. raise ForcedError()
  106. except ForcedError:
  107. pass
  108. self.assertDone([1])
  109. def test_runs_hooks_in_order_registered(self):
  110. with transaction.atomic():
  111. self.do(1)
  112. with transaction.atomic():
  113. self.do(2)
  114. self.do(3)
  115. self.assertDone([1, 2, 3])
  116. def test_hooks_cleared_after_successful_commit(self):
  117. with transaction.atomic():
  118. self.do(1)
  119. with transaction.atomic():
  120. self.do(2)
  121. self.assertDone([1, 2]) # not [1, 1, 2]
  122. def test_hooks_cleared_after_rollback(self):
  123. try:
  124. with transaction.atomic():
  125. self.do(1)
  126. raise ForcedError()
  127. except ForcedError:
  128. pass
  129. with transaction.atomic():
  130. self.do(2)
  131. self.assertDone([2])
  132. @skipUnlessDBFeature('test_db_allows_multiple_connections')
  133. def test_hooks_cleared_on_reconnect(self):
  134. with transaction.atomic():
  135. self.do(1)
  136. connection.close()
  137. connection.connect()
  138. with transaction.atomic():
  139. self.do(2)
  140. self.assertDone([2])
  141. def test_error_in_hook_doesnt_prevent_clearing_hooks(self):
  142. try:
  143. with transaction.atomic():
  144. transaction.on_commit(lambda: self.notify('error'))
  145. except ForcedError:
  146. pass
  147. with transaction.atomic():
  148. self.do(1)
  149. self.assertDone([1])
  150. def test_db_query_in_hook(self):
  151. with transaction.atomic():
  152. Thing.objects.create(num=1)
  153. transaction.on_commit(
  154. lambda: [self.notify(t.num) for t in Thing.objects.all()]
  155. )
  156. self.assertDone([1])
  157. def test_transaction_in_hook(self):
  158. def on_commit():
  159. with transaction.atomic():
  160. t = Thing.objects.create(num=1)
  161. self.notify(t.num)
  162. with transaction.atomic():
  163. transaction.on_commit(on_commit)
  164. self.assertDone([1])
  165. def test_raises_exception_non_autocommit_mode(self):
  166. def should_never_be_called():
  167. raise AssertionError('this function should never be called')
  168. try:
  169. connection.set_autocommit(False)
  170. with self.assertRaises(transaction.TransactionManagementError):
  171. transaction.on_commit(should_never_be_called)
  172. finally:
  173. connection.set_autocommit(True)