~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/activerecord/test/cases/locking_test.rb

  • Committer: Richard Lee (Canonical)
  • Date: 2010-10-15 15:17:58 UTC
  • mfrom: (190.1.3 use-case-mapper)
  • Revision ID: richard.lee@canonical.com-20101015151758-wcvmfxrexsongf9d
Merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
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_raise(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_raise(ActiveRecord::StaleObjectError) { p2.save! }
54
 
    p2.first_name = 'sue2'
55
 
    assert_raise(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_raise(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_raise(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