PageRenderTime 38ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/rails/activerecord/test/locking_test.rb

https://github.com/millbanksystems/hansard
Ruby | 241 lines | 184 code | 43 blank | 14 comment | 1 complexity | 0f2489006ceff305dba7b11f4e838233 MD5 | raw file
Possible License(s): Apache-2.0, MPL-2.0-no-copyleft-exception
  1. require 'abstract_unit'
  2. require 'fixtures/person'
  3. require 'fixtures/reader'
  4. require 'fixtures/legacy_thing'
  5. class LockWithoutDefault < ActiveRecord::Base; end
  6. class LockWithCustomColumnWithoutDefault < ActiveRecord::Base
  7. set_table_name :lock_without_defaults_cust
  8. set_locking_column :custom_lock_version
  9. end
  10. class OptimisticLockingTest < Test::Unit::TestCase
  11. fixtures :people, :legacy_things
  12. # need to disable transactional fixtures, because otherwise the sqlite3
  13. # adapter (at least) chokes when we try and change the schema in the middle
  14. # of a test (see test_increment_counter_*).
  15. self.use_transactional_fixtures = false
  16. def test_lock_existing
  17. p1 = Person.find(1)
  18. p2 = Person.find(1)
  19. assert_equal 0, p1.lock_version
  20. assert_equal 0, p2.lock_version
  21. p1.save!
  22. assert_equal 1, p1.lock_version
  23. assert_equal 0, p2.lock_version
  24. assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
  25. end
  26. def test_lock_new
  27. p1 = Person.new(:first_name => 'anika')
  28. assert_equal 0, p1.lock_version
  29. p1.save!
  30. p2 = Person.find(p1.id)
  31. assert_equal 0, p1.lock_version
  32. assert_equal 0, p2.lock_version
  33. p1.save!
  34. assert_equal 1, p1.lock_version
  35. assert_equal 0, p2.lock_version
  36. assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
  37. end
  38. def test_lock_column_name_existing
  39. t1 = LegacyThing.find(1)
  40. t2 = LegacyThing.find(1)
  41. assert_equal 0, t1.version
  42. assert_equal 0, t2.version
  43. t1.save!
  44. assert_equal 1, t1.version
  45. assert_equal 0, t2.version
  46. assert_raises(ActiveRecord::StaleObjectError) { t2.save! }
  47. end
  48. def test_lock_column_is_mass_assignable
  49. p1 = Person.create(:first_name => 'bianca')
  50. assert_equal 0, p1.lock_version
  51. assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
  52. p1.save!
  53. assert_equal 1, p1.lock_version
  54. assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
  55. end
  56. def test_lock_without_default_sets_version_to_zero
  57. t1 = LockWithoutDefault.new
  58. assert_equal 0, t1.lock_version
  59. end
  60. def test_lock_with_custom_column_without_default_sets_version_to_zero
  61. t1 = LockWithCustomColumnWithoutDefault.new
  62. assert_equal 0, t1.custom_lock_version
  63. end
  64. { :lock_version => Person, :custom_lock_version => LegacyThing }.each do |name, model|
  65. define_method("test_increment_counter_updates_#{name}") do
  66. counter_test model, 1 do |id|
  67. model.increment_counter :test_count, id
  68. end
  69. end
  70. define_method("test_decrement_counter_updates_#{name}") do
  71. counter_test model, -1 do |id|
  72. model.decrement_counter :test_count, id
  73. end
  74. end
  75. define_method("test_update_counters_updates_#{name}") do
  76. counter_test model, 1 do |id|
  77. model.update_counters id, :test_count => 1
  78. end
  79. end
  80. end
  81. private
  82. def add_counter_column_to(model)
  83. model.connection.add_column model.table_name, :test_count, :integer, :null => false, :default => 0
  84. model.reset_column_information
  85. end
  86. def remove_counter_column_from(model)
  87. model.connection.remove_column model.table_name, :test_count
  88. model.reset_column_information
  89. end
  90. def counter_test(model, expected_count)
  91. add_counter_column_to(model)
  92. object = model.find(:first)
  93. assert_equal 0, object.test_count
  94. assert_equal 0, object.send(model.locking_column)
  95. yield object.id
  96. object.reload
  97. assert_equal expected_count, object.test_count
  98. assert_equal 1, object.send(model.locking_column)
  99. ensure
  100. remove_counter_column_from(model)
  101. end
  102. end
  103. # TODO: test against the generated SQL since testing locking behavior itself
  104. # is so cumbersome. Will deadlock Ruby threads if the underlying db.execute
  105. # blocks, so separate script called by Kernel#system is needed.
  106. # (See exec vs. async_exec in the PostgreSQL adapter.)
  107. # TODO: The SQL Server and Sybase adapters currently have no support for pessimistic locking
  108. unless current_adapter?(:SQLServerAdapter, :SybaseAdapter)
  109. class PessimisticLockingTest < Test::Unit::TestCase
  110. self.use_transactional_fixtures = false
  111. fixtures :people, :readers
  112. def setup
  113. # Avoid introspection queries during tests.
  114. Person.columns; Reader.columns
  115. @allow_concurrency = ActiveRecord::Base.allow_concurrency
  116. ActiveRecord::Base.allow_concurrency = true
  117. end
  118. def teardown
  119. ActiveRecord::Base.allow_concurrency = @allow_concurrency
  120. end
  121. # Test typical find.
  122. def test_sane_find_with_lock
  123. assert_nothing_raised do
  124. Person.transaction do
  125. Person.find 1, :lock => true
  126. end
  127. end
  128. end
  129. # Test scoped lock.
  130. def test_sane_find_with_scoped_lock
  131. assert_nothing_raised do
  132. Person.transaction do
  133. Person.with_scope(:find => { :lock => true }) do
  134. Person.find 1
  135. end
  136. end
  137. end
  138. end
  139. # PostgreSQL protests SELECT ... FOR UPDATE on an outer join.
  140. unless current_adapter?(:PostgreSQLAdapter)
  141. # Test locked eager find.
  142. def test_eager_find_with_lock
  143. assert_nothing_raised do
  144. Person.transaction do
  145. Person.find 1, :include => :readers, :lock => true
  146. end
  147. end
  148. end
  149. end
  150. # Locking a record reloads it.
  151. def test_sane_lock_method
  152. assert_nothing_raised do
  153. Person.transaction do
  154. person = Person.find 1
  155. old, person.first_name = person.first_name, 'fooman'
  156. person.lock!
  157. assert_equal old, person.first_name
  158. end
  159. end
  160. end
  161. if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
  162. def test_no_locks_no_wait
  163. first, second = duel { Person.find 1 }
  164. assert first.end > second.end
  165. end
  166. def test_second_lock_waits
  167. assert [0.2, 1, 5].any? { |zzz|
  168. first, second = duel(zzz) { Person.find 1, :lock => true }
  169. second.end > first.end
  170. }
  171. end
  172. protected
  173. def duel(zzz = 5)
  174. t0, t1, t2, t3 = nil, nil, nil, nil
  175. a = Thread.new do
  176. t0 = Time.now
  177. Person.transaction do
  178. yield
  179. sleep zzz # block thread 2 for zzz seconds
  180. end
  181. t1 = Time.now
  182. end
  183. b = Thread.new do
  184. sleep zzz / 2.0 # ensure thread 1 tx starts first
  185. t2 = Time.now
  186. Person.transaction { yield }
  187. t3 = Time.now
  188. end
  189. a.join
  190. b.join
  191. assert t1 > t0 + zzz
  192. assert t2 > t0
  193. assert t3 > t2
  194. [t0.to_f..t1.to_f, t2.to_f..t3.to_f]
  195. end
  196. end
  197. end
  198. end