tests.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. from django.core.exceptions import ImproperlyConfigured
  2. from django.db import connection, transaction
  3. from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError
  4. from django.test import TransactionTestCase, skipUnlessDBFeature
  5. from models import Mod
  6. class TestTransactionClosing(TransactionTestCase):
  7. """
  8. Tests to make sure that transactions are properly closed
  9. when they should be, and aren't left pending after operations
  10. have been performed in them. Refs #9964.
  11. """
  12. def test_raw_committed_on_success(self):
  13. """
  14. Make sure a transaction consisting of raw SQL execution gets
  15. committed by the commit_on_success decorator.
  16. """
  17. @commit_on_success
  18. def raw_sql():
  19. "Write a record using raw sql under a commit_on_success decorator"
  20. cursor = connection.cursor()
  21. cursor.execute("INSERT into transactions_regress_mod (id,fld) values (17,18)")
  22. raw_sql()
  23. # Rollback so that if the decorator didn't commit, the record is unwritten
  24. transaction.rollback()
  25. try:
  26. # Check that the record is in the DB
  27. obj = Mod.objects.get(pk=17)
  28. self.assertEqual(obj.fld, 18)
  29. except Mod.DoesNotExist:
  30. self.fail("transaction with raw sql not committed")
  31. def test_commit_manually_enforced(self):
  32. """
  33. Make sure that under commit_manually, even "read-only" transaction require closure
  34. (commit or rollback), and a transaction left pending is treated as an error.
  35. """
  36. @commit_manually
  37. def non_comitter():
  38. "Execute a managed transaction with read-only operations and fail to commit"
  39. _ = Mod.objects.count()
  40. self.assertRaises(TransactionManagementError, non_comitter)
  41. def test_commit_manually_commit_ok(self):
  42. """
  43. Test that under commit_manually, a committed transaction is accepted by the transaction
  44. management mechanisms
  45. """
  46. @commit_manually
  47. def committer():
  48. """
  49. Perform a database query, then commit the transaction
  50. """
  51. _ = Mod.objects.count()
  52. transaction.commit()
  53. try:
  54. committer()
  55. except TransactionManagementError:
  56. self.fail("Commit did not clear the transaction state")
  57. def test_commit_manually_rollback_ok(self):
  58. """
  59. Test that under commit_manually, a rolled-back transaction is accepted by the transaction
  60. management mechanisms
  61. """
  62. @commit_manually
  63. def roller_back():
  64. """
  65. Perform a database query, then rollback the transaction
  66. """
  67. _ = Mod.objects.count()
  68. transaction.rollback()
  69. try:
  70. roller_back()
  71. except TransactionManagementError:
  72. self.fail("Rollback did not clear the transaction state")
  73. def test_commit_manually_enforced_after_commit(self):
  74. """
  75. Test that under commit_manually, if a transaction is committed and an operation is
  76. performed later, we still require the new transaction to be closed
  77. """
  78. @commit_manually
  79. def fake_committer():
  80. "Query, commit, then query again, leaving with a pending transaction"
  81. _ = Mod.objects.count()
  82. transaction.commit()
  83. _ = Mod.objects.count()
  84. self.assertRaises(TransactionManagementError, fake_committer)
  85. @skipUnlessDBFeature('supports_transactions')
  86. def test_reuse_cursor_reference(self):
  87. """
  88. Make sure transaction closure is enforced even when the queries are performed
  89. through a single cursor reference retrieved in the beginning
  90. (this is to show why it is wrong to set the transaction dirty only when a cursor
  91. is fetched from the connection).
  92. """
  93. @commit_on_success
  94. def reuse_cursor_ref():
  95. """
  96. Fetch a cursor, perform an query, rollback to close the transaction,
  97. then write a record (in a new transaction) using the same cursor object
  98. (reference). All this under commit_on_success, so the second insert should
  99. be committed.
  100. """
  101. cursor = connection.cursor()
  102. cursor.execute("INSERT into transactions_regress_mod (id,fld) values (1,2)")
  103. transaction.rollback()
  104. cursor.execute("INSERT into transactions_regress_mod (id,fld) values (1,2)")
  105. reuse_cursor_ref()
  106. # Rollback so that if the decorator didn't commit, the record is unwritten
  107. transaction.rollback()
  108. try:
  109. # Check that the record is in the DB
  110. obj = Mod.objects.get(pk=1)
  111. self.assertEqual(obj.fld, 2)
  112. except Mod.DoesNotExist:
  113. self.fail("After ending a transaction, cursor use no longer sets dirty")
  114. def test_failing_query_transaction_closed(self):
  115. """
  116. Make sure that under commit_on_success, a transaction is rolled back even if
  117. the first database-modifying operation fails.
  118. This is prompted by http://code.djangoproject.com/ticket/6669 (and based on sample
  119. code posted there to exemplify the problem): Before Django 1.3,
  120. transactions were only marked "dirty" by the save() function after it successfully
  121. wrote the object to the database.
  122. """
  123. from django.contrib.auth.models import User
  124. @transaction.commit_on_success
  125. def create_system_user():
  126. "Create a user in a transaction"
  127. user = User.objects.create_user(username='system', password='iamr00t', email='root@SITENAME.com')
  128. # Redundant, just makes sure the user id was read back from DB
  129. Mod.objects.create(fld=user.id)
  130. # Create a user
  131. create_system_user()
  132. try:
  133. # The second call to create_system_user should fail for violating a unique constraint
  134. # (it's trying to re-create the same user)
  135. create_system_user()
  136. except:
  137. pass
  138. else:
  139. raise ImproperlyConfigured('Unique constraint not enforced on django.contrib.auth.models.User')
  140. try:
  141. # Try to read the database. If the last transaction was indeed closed,
  142. # this should cause no problems
  143. _ = User.objects.all()[0]
  144. except:
  145. self.fail("A transaction consisting of a failed operation was not closed.")