You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

282 lines
7.8KB

  1. require 'abstract_unit'
  2. require 'fixtures/topic'
  3. require 'fixtures/reply'
  4. require 'fixtures/developer'
  5. class TransactionTest < Test::Unit::TestCase
  6. self.use_transactional_fixtures = false
  7. fixtures :topics, :developers
  8. def setup
  9. @first, @second = Topic.find(1, 2).sort_by { |t| t.id }
  10. end
  11. def test_successful
  12. Topic.transaction do
  13. @first.approved = true
  14. @second.approved = false
  15. @first.save
  16. @second.save
  17. end
  18. assert Topic.find(1).approved?, "First should have been approved"
  19. assert !Topic.find(2).approved?, "Second should have been unapproved"
  20. end
  21. def transaction_with_return
  22. Topic.transaction do
  23. @first.approved = true
  24. @second.approved = false
  25. @first.save
  26. @second.save
  27. return
  28. end
  29. end
  30. def test_successful_with_return
  31. class << Topic.connection
  32. alias :real_commit_db_transaction :commit_db_transaction
  33. def commit_db_transaction
  34. $committed = true
  35. real_commit_db_transaction
  36. end
  37. end
  38. $committed = false
  39. transaction_with_return
  40. assert $committed
  41. assert Topic.find(1).approved?, "First should have been approved"
  42. assert !Topic.find(2).approved?, "Second should have been unapproved"
  43. ensure
  44. class << Topic.connection
  45. alias :commit_db_transaction :real_commit_db_transaction rescue nil
  46. end
  47. end
  48. def test_successful_with_instance_method
  49. @first.transaction do
  50. @first.approved = true
  51. @second.approved = false
  52. @first.save
  53. @second.save
  54. end
  55. assert Topic.find(1).approved?, "First should have been approved"
  56. assert !Topic.find(2).approved?, "Second should have been unapproved"
  57. end
  58. def test_failing_on_exception
  59. begin
  60. Topic.transaction do
  61. @first.approved = true
  62. @second.approved = false
  63. @first.save
  64. @second.save
  65. raise "Bad things!"
  66. end
  67. rescue
  68. # caught it
  69. end
  70. assert @first.approved?, "First should still be changed in the objects"
  71. assert !@second.approved?, "Second should still be changed in the objects"
  72. assert !Topic.find(1).approved?, "First shouldn't have been approved"
  73. assert Topic.find(2).approved?, "Second should still be approved"
  74. end
  75. def test_callback_rollback_in_save
  76. add_exception_raising_after_save_callback_to_topic
  77. begin
  78. @first.approved = true
  79. @first.save
  80. flunk
  81. rescue => e
  82. assert_equal "Make the transaction rollback", e.message
  83. assert !Topic.find(1).approved?
  84. ensure
  85. remove_exception_raising_after_save_callback_to_topic
  86. end
  87. end
  88. def test_callback_rollback_in_create
  89. new_topic = Topic.new(
  90. :title => "A new topic",
  91. :author_name => "Ben",
  92. :author_email_address => "ben@example.com",
  93. :written_on => "2003-07-16t15:28:11.2233+01:00",
  94. :last_read => "2004-04-15",
  95. :bonus_time => "2005-01-30t15:28:00.00+01:00",
  96. :content => "Have a nice day",
  97. :approved => false)
  98. new_record_snapshot = new_topic.new_record?
  99. id_present = new_topic.has_attribute?(Topic.primary_key)
  100. id_snapshot = new_topic.id
  101. # Make sure the second save gets the after_create callback called.
  102. 2.times do
  103. begin
  104. add_exception_raising_after_create_callback_to_topic
  105. new_topic.approved = true
  106. new_topic.save
  107. flunk
  108. rescue => e
  109. assert_equal "Make the transaction rollback", e.message
  110. assert_equal new_record_snapshot, new_topic.new_record?, "The topic should have its old new_record value"
  111. assert_equal id_snapshot, new_topic.id, "The topic should have its old id"
  112. assert_equal id_present, new_topic.has_attribute?(Topic.primary_key)
  113. ensure
  114. remove_exception_raising_after_create_callback_to_topic
  115. end
  116. end
  117. end
  118. def test_nested_explicit_transactions
  119. Topic.transaction do
  120. Topic.transaction do
  121. @first.approved = true
  122. @second.approved = false
  123. @first.save
  124. @second.save
  125. end
  126. end
  127. assert Topic.find(1).approved?, "First should have been approved"
  128. assert !Topic.find(2).approved?, "Second should have been unapproved"
  129. end
  130. def test_manually_rolling_back_a_transaction
  131. Topic.transaction do
  132. @first.approved = true
  133. @second.approved = false
  134. @first.save
  135. @second.save
  136. raise ActiveRecord::Rollback
  137. end
  138. assert @first.approved?, "First should still be changed in the objects"
  139. assert !@second.approved?, "Second should still be changed in the objects"
  140. assert !Topic.find(1).approved?, "First shouldn't have been approved"
  141. assert Topic.find(2).approved?, "Second should still be approved"
  142. end
  143. uses_mocha 'mocking connection.commit_db_transaction' do
  144. def test_rollback_when_commit_raises
  145. Topic.connection.expects(:begin_db_transaction)
  146. Topic.connection.expects(:commit_db_transaction).raises('OH NOES')
  147. Topic.connection.expects(:rollback_db_transaction)
  148. assert_raise RuntimeError do
  149. Topic.transaction do
  150. # do nothing
  151. end
  152. end
  153. end
  154. end
  155. private
  156. def add_exception_raising_after_save_callback_to_topic
  157. Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
  158. end
  159. def remove_exception_raising_after_save_callback_to_topic
  160. Topic.class_eval { remove_method :after_save }
  161. end
  162. def add_exception_raising_after_create_callback_to_topic
  163. Topic.class_eval { def after_create() raise "Make the transaction rollback" end }
  164. end
  165. def remove_exception_raising_after_create_callback_to_topic
  166. Topic.class_eval { remove_method :after_create }
  167. end
  168. end
  169. if current_adapter?(:PostgreSQLAdapter)
  170. class ConcurrentTransactionTest < TransactionTest
  171. def setup
  172. @allow_concurrency = ActiveRecord::Base.allow_concurrency
  173. ActiveRecord::Base.allow_concurrency = true
  174. super
  175. end
  176. def teardown
  177. super
  178. ActiveRecord::Base.allow_concurrency = @allow_concurrency
  179. end
  180. # This will cause transactions to overlap and fail unless they are performed on
  181. # separate database connections.
  182. def test_transaction_per_thread
  183. assert_nothing_raised do
  184. threads = (1..3).map do
  185. Thread.new do
  186. Topic.transaction do
  187. topic = Topic.find(1)
  188. topic.approved = !topic.approved?
  189. topic.save!
  190. topic.approved = !topic.approved?
  191. topic.save!
  192. end
  193. end
  194. end
  195. threads.each { |t| t.join }
  196. end
  197. end
  198. # Test for dirty reads among simultaneous transactions.
  199. def test_transaction_isolation__read_committed
  200. # Should be invariant.
  201. original_salary = Developer.find(1).salary
  202. temporary_salary = 200000
  203. assert_nothing_raised do
  204. threads = (1..3).map do
  205. Thread.new do
  206. Developer.transaction do
  207. # Expect original salary.
  208. dev = Developer.find(1)
  209. assert_equal original_salary, dev.salary
  210. dev.salary = temporary_salary
  211. dev.save!
  212. # Expect temporary salary.
  213. dev = Developer.find(1)
  214. assert_equal temporary_salary, dev.salary
  215. dev.salary = original_salary
  216. dev.save!
  217. # Expect original salary.
  218. dev = Developer.find(1)
  219. assert_equal original_salary, dev.salary
  220. end
  221. end
  222. end
  223. # Keep our eyes peeled.
  224. threads << Thread.new do
  225. 10.times do
  226. sleep 0.05
  227. Developer.transaction do
  228. # Always expect original salary.
  229. assert_equal original_salary, Developer.find(1).salary
  230. end
  231. end
  232. end
  233. threads.each { |t| t.join }
  234. end
  235. assert_equal original_salary, Developer.find(1).salary
  236. end
  237. end
  238. end