1. from django.db import connection, transaction
    
  2. from django.test import TransactionTestCase, skipUnlessDBFeature
    
  3. 
    
  4. from .models import Thing
    
  5. 
    
  6. 
    
  7. class ForcedError(Exception):
    
  8.     pass
    
  9. 
    
  10. 
    
  11. @skipUnlessDBFeature("supports_transactions")
    
  12. class TestConnectionOnCommit(TransactionTestCase):
    
  13.     """
    
  14.     Tests for transaction.on_commit().
    
  15. 
    
  16.     Creation/checking of database objects in parallel with callback tracking is
    
  17.     to verify that the behavior of the two match in all tested cases.
    
  18.     """
    
  19. 
    
  20.     available_apps = ["transaction_hooks"]
    
  21. 
    
  22.     def setUp(self):
    
  23.         self.notified = []
    
  24. 
    
  25.     def notify(self, id_):
    
  26.         if id_ == "error":
    
  27.             raise ForcedError()
    
  28.         self.notified.append(id_)
    
  29. 
    
  30.     def do(self, num):
    
  31.         """Create a Thing instance and notify about it."""
    
  32.         Thing.objects.create(num=num)
    
  33.         transaction.on_commit(lambda: self.notify(num))
    
  34. 
    
  35.     def assertDone(self, nums):
    
  36.         self.assertNotified(nums)
    
  37.         self.assertEqual(sorted(t.num for t in Thing.objects.all()), sorted(nums))
    
  38. 
    
  39.     def assertNotified(self, nums):
    
  40.         self.assertEqual(self.notified, nums)
    
  41. 
    
  42.     def test_executes_immediately_if_no_transaction(self):
    
  43.         self.do(1)
    
  44.         self.assertDone([1])
    
  45. 
    
  46.     def test_delays_execution_until_after_transaction_commit(self):
    
  47.         with transaction.atomic():
    
  48.             self.do(1)
    
  49.             self.assertNotified([])
    
  50.         self.assertDone([1])
    
  51. 
    
  52.     def test_does_not_execute_if_transaction_rolled_back(self):
    
  53.         try:
    
  54.             with transaction.atomic():
    
  55.                 self.do(1)
    
  56.                 raise ForcedError()
    
  57.         except ForcedError:
    
  58.             pass
    
  59. 
    
  60.         self.assertDone([])
    
  61. 
    
  62.     def test_executes_only_after_final_transaction_committed(self):
    
  63.         with transaction.atomic():
    
  64.             with transaction.atomic():
    
  65.                 self.do(1)
    
  66.                 self.assertNotified([])
    
  67.             self.assertNotified([])
    
  68.         self.assertDone([1])
    
  69. 
    
  70.     def test_discards_hooks_from_rolled_back_savepoint(self):
    
  71.         with transaction.atomic():
    
  72.             # one successful savepoint
    
  73.             with transaction.atomic():
    
  74.                 self.do(1)
    
  75.             # one failed savepoint
    
  76.             try:
    
  77.                 with transaction.atomic():
    
  78.                     self.do(2)
    
  79.                     raise ForcedError()
    
  80.             except ForcedError:
    
  81.                 pass
    
  82.             # another successful savepoint
    
  83.             with transaction.atomic():
    
  84.                 self.do(3)
    
  85. 
    
  86.         # only hooks registered during successful savepoints execute
    
  87.         self.assertDone([1, 3])
    
  88. 
    
  89.     def test_no_hooks_run_from_failed_transaction(self):
    
  90.         """If outer transaction fails, no hooks from within it run."""
    
  91.         try:
    
  92.             with transaction.atomic():
    
  93.                 with transaction.atomic():
    
  94.                     self.do(1)
    
  95.                 raise ForcedError()
    
  96.         except ForcedError:
    
  97.             pass
    
  98. 
    
  99.         self.assertDone([])
    
  100. 
    
  101.     def test_inner_savepoint_rolled_back_with_outer(self):
    
  102.         with transaction.atomic():
    
  103.             try:
    
  104.                 with transaction.atomic():
    
  105.                     with transaction.atomic():
    
  106.                         self.do(1)
    
  107.                     raise ForcedError()
    
  108.             except ForcedError:
    
  109.                 pass
    
  110.             self.do(2)
    
  111. 
    
  112.         self.assertDone([2])
    
  113. 
    
  114.     def test_no_savepoints_atomic_merged_with_outer(self):
    
  115.         with transaction.atomic():
    
  116.             with transaction.atomic():
    
  117.                 self.do(1)
    
  118.                 try:
    
  119.                     with transaction.atomic(savepoint=False):
    
  120.                         raise ForcedError()
    
  121.                 except ForcedError:
    
  122.                     pass
    
  123. 
    
  124.         self.assertDone([])
    
  125. 
    
  126.     def test_inner_savepoint_does_not_affect_outer(self):
    
  127.         with transaction.atomic():
    
  128.             with transaction.atomic():
    
  129.                 self.do(1)
    
  130.                 try:
    
  131.                     with transaction.atomic():
    
  132.                         raise ForcedError()
    
  133.                 except ForcedError:
    
  134.                     pass
    
  135. 
    
  136.         self.assertDone([1])
    
  137. 
    
  138.     def test_runs_hooks_in_order_registered(self):
    
  139.         with transaction.atomic():
    
  140.             self.do(1)
    
  141.             with transaction.atomic():
    
  142.                 self.do(2)
    
  143.             self.do(3)
    
  144. 
    
  145.         self.assertDone([1, 2, 3])
    
  146. 
    
  147.     def test_hooks_cleared_after_successful_commit(self):
    
  148.         with transaction.atomic():
    
  149.             self.do(1)
    
  150.         with transaction.atomic():
    
  151.             self.do(2)
    
  152. 
    
  153.         self.assertDone([1, 2])  # not [1, 1, 2]
    
  154. 
    
  155.     def test_hooks_cleared_after_rollback(self):
    
  156.         try:
    
  157.             with transaction.atomic():
    
  158.                 self.do(1)
    
  159.                 raise ForcedError()
    
  160.         except ForcedError:
    
  161.             pass
    
  162. 
    
  163.         with transaction.atomic():
    
  164.             self.do(2)
    
  165. 
    
  166.         self.assertDone([2])
    
  167. 
    
  168.     @skipUnlessDBFeature("test_db_allows_multiple_connections")
    
  169.     def test_hooks_cleared_on_reconnect(self):
    
  170.         with transaction.atomic():
    
  171.             self.do(1)
    
  172.             connection.close()
    
  173. 
    
  174.         connection.connect()
    
  175. 
    
  176.         with transaction.atomic():
    
  177.             self.do(2)
    
  178. 
    
  179.         self.assertDone([2])
    
  180. 
    
  181.     def test_error_in_hook_doesnt_prevent_clearing_hooks(self):
    
  182.         try:
    
  183.             with transaction.atomic():
    
  184.                 transaction.on_commit(lambda: self.notify("error"))
    
  185.         except ForcedError:
    
  186.             pass
    
  187. 
    
  188.         with transaction.atomic():
    
  189.             self.do(1)
    
  190. 
    
  191.         self.assertDone([1])
    
  192. 
    
  193.     def test_db_query_in_hook(self):
    
  194.         with transaction.atomic():
    
  195.             Thing.objects.create(num=1)
    
  196.             transaction.on_commit(
    
  197.                 lambda: [self.notify(t.num) for t in Thing.objects.all()]
    
  198.             )
    
  199. 
    
  200.         self.assertDone([1])
    
  201. 
    
  202.     def test_transaction_in_hook(self):
    
  203.         def on_commit():
    
  204.             with transaction.atomic():
    
  205.                 t = Thing.objects.create(num=1)
    
  206.                 self.notify(t.num)
    
  207. 
    
  208.         with transaction.atomic():
    
  209.             transaction.on_commit(on_commit)
    
  210. 
    
  211.         self.assertDone([1])
    
  212. 
    
  213.     def test_hook_in_hook(self):
    
  214.         def on_commit(i, add_hook):
    
  215.             with transaction.atomic():
    
  216.                 if add_hook:
    
  217.                     transaction.on_commit(lambda: on_commit(i + 10, False))
    
  218.                 t = Thing.objects.create(num=i)
    
  219.                 self.notify(t.num)
    
  220. 
    
  221.         with transaction.atomic():
    
  222.             transaction.on_commit(lambda: on_commit(1, True))
    
  223.             transaction.on_commit(lambda: on_commit(2, True))
    
  224. 
    
  225.         self.assertDone([1, 11, 2, 12])
    
  226. 
    
  227.     def test_raises_exception_non_autocommit_mode(self):
    
  228.         def should_never_be_called():
    
  229.             raise AssertionError("this function should never be called")
    
  230. 
    
  231.         try:
    
  232.             connection.set_autocommit(False)
    
  233.             msg = "on_commit() cannot be used in manual transaction management"
    
  234.             with self.assertRaisesMessage(transaction.TransactionManagementError, msg):
    
  235.                 transaction.on_commit(should_never_be_called)
    
  236.         finally:
    
  237.             connection.set_autocommit(True)
    
  238. 
    
  239.     def test_raises_exception_non_callable(self):
    
  240.         msg = "on_commit()'s callback must be a callable."
    
  241.         with self.assertRaisesMessage(TypeError, msg):
    
  242.             transaction.on_commit(None)