Froze rails gems
[depot.git] / vendor / rails / activerecord / test / cases / locking_test.rb
1 require "cases/helper"
2 require 'models/person'
3 require 'models/reader'
4 require 'models/legacy_thing'
5 require 'models/reference'
6
7 class LockWithoutDefault < ActiveRecord::Base; end
8
9 class LockWithCustomColumnWithoutDefault < ActiveRecord::Base
10 set_table_name :lock_without_defaults_cust
11 set_locking_column :custom_lock_version
12 end
13
14 class ReadonlyFirstNamePerson < Person
15 attr_readonly :first_name
16 end
17
18 class OptimisticLockingTest < ActiveRecord::TestCase
19 fixtures :people, :legacy_things, :references
20
21 # need to disable transactional fixtures, because otherwise the sqlite3
22 # adapter (at least) chokes when we try and change the schema in the middle
23 # of a test (see test_increment_counter_*).
24 self.use_transactional_fixtures = false
25
26 def test_lock_existing
27 p1 = Person.find(1)
28 p2 = Person.find(1)
29 assert_equal 0, p1.lock_version
30 assert_equal 0, p2.lock_version
31
32 p1.first_name = 'stu'
33 p1.save!
34 assert_equal 1, p1.lock_version
35 assert_equal 0, p2.lock_version
36
37 p2.first_name = 'sue'
38 assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
39 end
40
41 def test_lock_repeating
42 p1 = Person.find(1)
43 p2 = Person.find(1)
44 assert_equal 0, p1.lock_version
45 assert_equal 0, p2.lock_version
46
47 p1.first_name = 'stu'
48 p1.save!
49 assert_equal 1, p1.lock_version
50 assert_equal 0, p2.lock_version
51
52 p2.first_name = 'sue'
53 assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
54 p2.first_name = 'sue2'
55 assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
56 end
57
58 def test_lock_new
59 p1 = Person.new(:first_name => 'anika')
60 assert_equal 0, p1.lock_version
61
62 p1.first_name = 'anika2'
63 p1.save!
64 p2 = Person.find(p1.id)
65 assert_equal 0, p1.lock_version
66 assert_equal 0, p2.lock_version
67
68 p1.first_name = 'anika3'
69 p1.save!
70 assert_equal 1, p1.lock_version
71 assert_equal 0, p2.lock_version
72
73 p2.first_name = 'sue'
74 assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
75 end
76
77 def test_lock_new_with_nil
78 p1 = Person.new(:first_name => 'anika')
79 p1.save!
80 p1.lock_version = nil # simulate bad fixture or column with no default
81 p1.save!
82 assert_equal 1, p1.lock_version
83 end
84
85
86 def test_lock_column_name_existing
87 t1 = LegacyThing.find(1)
88 t2 = LegacyThing.find(1)
89 assert_equal 0, t1.version
90 assert_equal 0, t2.version
91
92 t1.tps_report_number = 700
93 t1.save!
94 assert_equal 1, t1.version
95 assert_equal 0, t2.version
96
97 t2.tps_report_number = 800
98 assert_raises(ActiveRecord::StaleObjectError) { t2.save! }
99 end
100
101 def test_lock_column_is_mass_assignable
102 p1 = Person.create(:first_name => 'bianca')
103 assert_equal 0, p1.lock_version
104 assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
105
106 p1.first_name = 'bianca2'
107 p1.save!
108 assert_equal 1, p1.lock_version
109 assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
110 end
111
112 def test_lock_without_default_sets_version_to_zero
113 t1 = LockWithoutDefault.new
114 assert_equal 0, t1.lock_version
115 end
116
117 def test_lock_with_custom_column_without_default_sets_version_to_zero
118 t1 = LockWithCustomColumnWithoutDefault.new
119 assert_equal 0, t1.custom_lock_version
120 end
121
122 def test_readonly_attributes
123 assert_equal Set.new([ 'first_name' ]), ReadonlyFirstNamePerson.readonly_attributes
124
125 p = ReadonlyFirstNamePerson.create(:first_name => "unchangeable name")
126 p.reload
127 assert_equal "unchangeable name", p.first_name
128
129 p.update_attributes(:first_name => "changed name")
130 p.reload
131 assert_equal "unchangeable name", p.first_name
132 end
133
134 { :lock_version => Person, :custom_lock_version => LegacyThing }.each do |name, model|
135 define_method("test_increment_counter_updates_#{name}") do
136 counter_test model, 1 do |id|
137 model.increment_counter :test_count, id
138 end
139 end
140
141 define_method("test_decrement_counter_updates_#{name}") do
142 counter_test model, -1 do |id|
143 model.decrement_counter :test_count, id
144 end
145 end
146
147 define_method("test_update_counters_updates_#{name}") do
148 counter_test model, 1 do |id|
149 model.update_counters id, :test_count => 1
150 end
151 end
152 end
153
154 def test_quote_table_name
155 ref = references(:michael_magician)
156 ref.favourite = !ref.favourite
157 assert ref.save
158 end
159
160 # Useful for partial updates, don't only update the lock_version if there
161 # is nothing else being updated.
162 def test_update_without_attributes_does_not_only_update_lock_version
163 assert_nothing_raised do
164 p1 = Person.new(:first_name => 'anika')
165 p1.send(:update_with_lock, [])
166 end
167 end
168
169 private
170
171 def add_counter_column_to(model)
172 model.connection.add_column model.table_name, :test_count, :integer, :null => false, :default => 0
173 model.reset_column_information
174 # OpenBase does not set a value to existing rows when adding a not null default column
175 model.update_all(:test_count => 0) if current_adapter?(:OpenBaseAdapter)
176 end
177
178 def remove_counter_column_from(model)
179 model.connection.remove_column model.table_name, :test_count
180 model.reset_column_information
181 end
182
183 def counter_test(model, expected_count)
184 add_counter_column_to(model)
185 object = model.find(:first)
186 assert_equal 0, object.test_count
187 assert_equal 0, object.send(model.locking_column)
188 yield object.id
189 object.reload
190 assert_equal expected_count, object.test_count
191 assert_equal 1, object.send(model.locking_column)
192 ensure
193 remove_counter_column_from(model)
194 end
195 end
196
197
198 # TODO: test against the generated SQL since testing locking behavior itself
199 # is so cumbersome. Will deadlock Ruby threads if the underlying db.execute
200 # blocks, so separate script called by Kernel#system is needed.
201 # (See exec vs. async_exec in the PostgreSQL adapter.)
202
203 # TODO: The Sybase, and OpenBase adapters currently have no support for pessimistic locking
204
205 unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
206 class PessimisticLockingTest < ActiveRecord::TestCase
207 self.use_transactional_fixtures = false
208 fixtures :people, :readers
209
210 def setup
211 # Avoid introspection queries during tests.
212 Person.columns; Reader.columns
213 end
214
215 # Test typical find.
216 def test_sane_find_with_lock
217 assert_nothing_raised do
218 Person.transaction do
219 Person.find 1, :lock => true
220 end
221 end
222 end
223
224 # Test scoped lock.
225 def test_sane_find_with_scoped_lock
226 assert_nothing_raised do
227 Person.transaction do
228 Person.with_scope(:find => { :lock => true }) do
229 Person.find 1
230 end
231 end
232 end
233 end
234
235 # PostgreSQL protests SELECT ... FOR UPDATE on an outer join.
236 unless current_adapter?(:PostgreSQLAdapter)
237 # Test locked eager find.
238 def test_eager_find_with_lock
239 assert_nothing_raised do
240 Person.transaction do
241 Person.find 1, :include => :readers, :lock => true
242 end
243 end
244 end
245 end
246
247 # Locking a record reloads it.
248 def test_sane_lock_method
249 assert_nothing_raised do
250 Person.transaction do
251 person = Person.find 1
252 old, person.first_name = person.first_name, 'fooman'
253 person.lock!
254 assert_equal old, person.first_name
255 end
256 end
257 end
258
259 if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
260 use_concurrent_connections
261
262 def test_no_locks_no_wait
263 first, second = duel { Person.find 1 }
264 assert first.end > second.end
265 end
266
267 def test_second_lock_waits
268 assert [0.2, 1, 5].any? { |zzz|
269 first, second = duel(zzz) { Person.find 1, :lock => true }
270 second.end > first.end
271 }
272 end
273
274 protected
275 def duel(zzz = 5)
276 t0, t1, t2, t3 = nil, nil, nil, nil
277
278 a = Thread.new do
279 t0 = Time.now
280 Person.transaction do
281 yield
282 sleep zzz # block thread 2 for zzz seconds
283 end
284 t1 = Time.now
285 end
286
287 b = Thread.new do
288 sleep zzz / 2.0 # ensure thread 1 tx starts first
289 t2 = Time.now
290 Person.transaction { yield }
291 t3 = Time.now
292 end
293
294 a.join
295 b.join
296
297 assert t1 > t0 + zzz
298 assert t2 > t0
299 assert t3 > t2
300 [t0.to_f..t1.to_f, t2.to_f..t3.to_f]
301 end
302 end
303 end
304 end