low_card_tables 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +59 -0
  4. data/Gemfile +17 -0
  5. data/LICENSE +21 -0
  6. data/README.md +75 -0
  7. data/Rakefile +6 -0
  8. data/lib/low_card_tables.rb +72 -0
  9. data/lib/low_card_tables/active_record/base.rb +55 -0
  10. data/lib/low_card_tables/active_record/migrations.rb +223 -0
  11. data/lib/low_card_tables/active_record/relation.rb +35 -0
  12. data/lib/low_card_tables/active_record/scoping.rb +87 -0
  13. data/lib/low_card_tables/errors.rb +74 -0
  14. data/lib/low_card_tables/has_low_card_table/base.rb +114 -0
  15. data/lib/low_card_tables/has_low_card_table/low_card_association.rb +273 -0
  16. data/lib/low_card_tables/has_low_card_table/low_card_associations_manager.rb +143 -0
  17. data/lib/low_card_tables/has_low_card_table/low_card_dynamic_method_manager.rb +224 -0
  18. data/lib/low_card_tables/has_low_card_table/low_card_objects_manager.rb +80 -0
  19. data/lib/low_card_tables/low_card_table/base.rb +184 -0
  20. data/lib/low_card_tables/low_card_table/cache.rb +214 -0
  21. data/lib/low_card_tables/low_card_table/cache_expiration/exponential_cache_expiration_policy.rb +151 -0
  22. data/lib/low_card_tables/low_card_table/cache_expiration/fixed_cache_expiration_policy.rb +23 -0
  23. data/lib/low_card_tables/low_card_table/cache_expiration/has_cache_expiration.rb +100 -0
  24. data/lib/low_card_tables/low_card_table/cache_expiration/no_caching_expiration_policy.rb +13 -0
  25. data/lib/low_card_tables/low_card_table/cache_expiration/unlimited_cache_expiration_policy.rb +13 -0
  26. data/lib/low_card_tables/low_card_table/row_collapser.rb +175 -0
  27. data/lib/low_card_tables/low_card_table/row_manager.rb +681 -0
  28. data/lib/low_card_tables/low_card_table/table_unique_index.rb +134 -0
  29. data/lib/low_card_tables/version.rb +4 -0
  30. data/lib/low_card_tables/version_support.rb +52 -0
  31. data/low_card_tables.gemspec +69 -0
  32. data/spec/low_card_tables/helpers/database_helper.rb +148 -0
  33. data/spec/low_card_tables/helpers/query_spy_helper.rb +47 -0
  34. data/spec/low_card_tables/helpers/system_helpers.rb +63 -0
  35. data/spec/low_card_tables/system/basic_system_spec.rb +254 -0
  36. data/spec/low_card_tables/system/bulk_system_spec.rb +334 -0
  37. data/spec/low_card_tables/system/caching_system_spec.rb +531 -0
  38. data/spec/low_card_tables/system/migrations_system_spec.rb +747 -0
  39. data/spec/low_card_tables/system/options_system_spec.rb +581 -0
  40. data/spec/low_card_tables/system/queries_system_spec.rb +142 -0
  41. data/spec/low_card_tables/system/validations_system_spec.rb +88 -0
  42. data/spec/low_card_tables/unit/active_record/base_spec.rb +53 -0
  43. data/spec/low_card_tables/unit/active_record/migrations_spec.rb +207 -0
  44. data/spec/low_card_tables/unit/active_record/relation_spec.rb +47 -0
  45. data/spec/low_card_tables/unit/active_record/scoping_spec.rb +101 -0
  46. data/spec/low_card_tables/unit/has_low_card_table/base_spec.rb +79 -0
  47. data/spec/low_card_tables/unit/has_low_card_table/low_card_association_spec.rb +287 -0
  48. data/spec/low_card_tables/unit/has_low_card_table/low_card_associations_manager_spec.rb +190 -0
  49. data/spec/low_card_tables/unit/has_low_card_table/low_card_dynamic_method_manager_spec.rb +234 -0
  50. data/spec/low_card_tables/unit/has_low_card_table/low_card_objects_manager_spec.rb +70 -0
  51. data/spec/low_card_tables/unit/low_card_table/base_spec.rb +207 -0
  52. data/spec/low_card_tables/unit/low_card_table/cache_expiration/exponential_cache_expiration_policy_spec.rb +128 -0
  53. data/spec/low_card_tables/unit/low_card_table/cache_expiration/fixed_cache_expiration_policy_spec.rb +25 -0
  54. data/spec/low_card_tables/unit/low_card_table/cache_expiration/has_cache_expiration_policy_spec.rb +100 -0
  55. data/spec/low_card_tables/unit/low_card_table/cache_expiration/no_caching_expiration_policy_spec.rb +14 -0
  56. data/spec/low_card_tables/unit/low_card_table/cache_expiration/unlimited_cache_expiration_policy_spec.rb +14 -0
  57. data/spec/low_card_tables/unit/low_card_table/cache_spec.rb +282 -0
  58. data/spec/low_card_tables/unit/low_card_table/row_collapser_spec.rb +109 -0
  59. data/spec/low_card_tables/unit/low_card_table/row_manager_spec.rb +918 -0
  60. data/spec/low_card_tables/unit/low_card_table/table_unique_index_spec.rb +117 -0
  61. metadata +206 -0
@@ -0,0 +1,531 @@
1
+ require 'low_card_tables'
2
+ require 'low_card_tables/helpers/database_helper'
3
+ require 'low_card_tables/helpers/system_helpers'
4
+ require 'low_card_tables/helpers/query_spy_helper'
5
+
6
+ describe "LowCardTables caching" do
7
+ include LowCardTables::Helpers::SystemHelpers
8
+
9
+ before :each do
10
+ @dh = LowCardTables::Helpers::DatabaseHelper.new
11
+ @dh.setup_activerecord!
12
+
13
+ create_standard_system_spec_tables!
14
+ create_standard_system_spec_models!
15
+ end
16
+
17
+ after :each do
18
+ drop_standard_system_spec_tables!
19
+ end
20
+
21
+ def create_basic_user(name = 'User1')
22
+ out = ::User.new
23
+ out.name = 'User1'
24
+ out.deleted = false
25
+ out.deceased = false
26
+ out.gender = 'female'
27
+ out.donation_level = 3
28
+ out.save!
29
+
30
+ out
31
+ end
32
+
33
+ it "should have an explicit cache-flush call that works" do
34
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
35
+ create_basic_user("User1")
36
+
37
+ mid_count = spy.call_count
38
+
39
+ create_basic_user("User2")
40
+
41
+ spy.call_count.should == mid_count
42
+
43
+ ::UserStatus.low_card_flush_cache!
44
+ create_basic_user("User3")
45
+
46
+ spy.call_count.should > mid_count
47
+ end
48
+ end
49
+
50
+ it "should cache low-card rows in memory" do
51
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
52
+ spy.call_count.should == 0
53
+
54
+ user1 = create_basic_user
55
+
56
+ mid_calls = spy.call_count
57
+ mid_calls.should > 0
58
+
59
+ user2 = create_basic_user('User2')
60
+
61
+ spy.call_count.should == mid_calls
62
+ end
63
+ end
64
+
65
+ it "should purge its cache efficiently when adding a new row" do
66
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
67
+ spy.call_count.should == 0
68
+
69
+ user1 = create_basic_user
70
+
71
+ mid_calls = spy.call_count
72
+ mid_calls.should > 0
73
+
74
+ user2 = ::User.new
75
+ user2.name = 'User2'
76
+ user2.deleted = false
77
+ user2.deceased = false
78
+ user2.gender = 'male'
79
+ user2.donation_level = 7
80
+ user2.save!
81
+
82
+ # We allow two calls here because we need it for our double-checked locking pattern.
83
+ (spy.call_count - mid_calls).should <= 2
84
+ end
85
+ end
86
+
87
+ it "should handle the situation where a row in the database has a low-card ID that's not in cache" do
88
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
89
+ spy.call_count.should == 0
90
+
91
+ user1 = create_basic_user
92
+
93
+ mid_calls = spy.call_count
94
+ mid_calls.should > 0
95
+
96
+ new_status = ::UserStatusBackdoor.new
97
+ new_status.deleted = false
98
+ new_status.deceased = false
99
+ new_status.gender = 'male'
100
+ new_status.donation_level = 7
101
+ new_status.save!
102
+
103
+ old_status_id = user1.user_status_id
104
+ ::User.where([ "id = :id", { :id => user1.id } ]).update_all([ "user_status_id = :new_status_id", { :new_status_id => new_status.id } ])
105
+ user1.user_status_id.should == old_status_id # make sure we didn't touch the existing object
106
+
107
+ # Make sure we didn't somehow invalidate the cache before this
108
+ spy.call_count.should == mid_calls
109
+
110
+ user2 = ::User.find(user1.id)
111
+ user2.deleted.should == false
112
+ user2.deceased.should == false
113
+ user2.gender.should == 'male'
114
+ user2.donation_level.should == 7
115
+
116
+ (spy.call_count - mid_calls).should > 0
117
+ (spy.call_count - mid_calls).should <= 2
118
+ end
119
+ end
120
+
121
+ it "should be OK with manually-assigning an ID that's not in cache (that you somehow got out-of-band)" do
122
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
123
+ spy.call_count.should == 0
124
+
125
+ user1 = create_basic_user
126
+
127
+ mid_calls = spy.call_count
128
+ mid_calls.should > 0
129
+
130
+ new_status = ::UserStatusBackdoor.new
131
+ new_status.deleted = false
132
+ new_status.deceased = false
133
+ new_status.gender = 'male'
134
+ new_status.donation_level = 7
135
+ new_status.save!
136
+
137
+ user1.user_status_id = new_status.id
138
+ user1.deleted.should == false
139
+ user1.deceased.should == false
140
+ user1.gender.should == 'male'
141
+ user1.donation_level.should == 7
142
+
143
+ (spy.call_count - mid_calls).should > 0
144
+ (spy.call_count - mid_calls).should <= 2
145
+ end
146
+ end
147
+
148
+ context "with a cache listener" do
149
+ before :each do
150
+ class CacheListener
151
+ def initialize
152
+ @calls = [ ]
153
+ end
154
+
155
+ def call(name, started, finished, unique_id, data)
156
+ @calls << { :name => name, :started => started, :finished => finished, :unique_id => unique_id, :data => data }
157
+ end
158
+
159
+ def listen!(*event_names)
160
+ event_names.each do |event_name|
161
+ ActiveSupport::Notifications.subscribe(event_name, self)
162
+ end
163
+ end
164
+
165
+ def unlisten!
166
+ ActiveSupport::Notifications.unsubscribe(self)
167
+ end
168
+
169
+ attr_reader :calls
170
+ end
171
+
172
+ @cache_listener = CacheListener.new
173
+ @cache_listener.listen!('low_card_tables.cache_load', 'low_card_tables.cache_flush', 'low_card_tables.rows_created')
174
+ end
175
+
176
+ after :each do
177
+ @cache_listener.unlisten!
178
+ end
179
+
180
+ it "should notify listeners when flushing and loading its cache" do
181
+ @cache_listener.calls.length.should == 0
182
+
183
+ user1 = create_basic_user
184
+
185
+ call_count = @cache_listener.calls.length
186
+ call_count.should > 0
187
+ @cache_listener.calls.detect { |c| c[:name] == 'low_card_tables.cache_load' }.should be
188
+
189
+ start_time = Time.now
190
+ user1.deleted = true
191
+ user1.save!
192
+ end_time = Time.now
193
+
194
+ new_calls = @cache_listener.calls[call_count..-1]
195
+ new_calls.length.should > 0
196
+
197
+ flush_event = new_calls.detect { |c| c[:name] == 'low_card_tables.cache_flush' }
198
+ load_event = new_calls.detect { |c| c[:name] == 'low_card_tables.cache_load' }
199
+ flush_event.should be
200
+ load_event.should be
201
+
202
+ flush_event[:started].should >= start_time
203
+ flush_event[:finished].should >= flush_event[:started]
204
+ flush_event[:finished].should <= end_time
205
+ flush_event[:data][:reason].should == :creating_rows
206
+ flush_event[:data][:low_card_model].should == ::UserStatus
207
+
208
+ load_event[:started].should >= start_time
209
+ load_event[:started].should >= flush_event[:finished]
210
+ load_event[:finished].should >= load_event[:started]
211
+ load_event[:finished].should <= end_time
212
+ load_event[:data][:low_card_model].should == ::UserStatus
213
+ end
214
+
215
+ it "should notify listeners when the cache is manually flushed" do
216
+ user1 = create_basic_user
217
+ call_count = @cache_listener.calls.length
218
+
219
+ start_time = Time.now
220
+ ::UserStatus.low_card_flush_cache!
221
+ new_calls = @cache_listener.calls[call_count..-1]
222
+ end_time = Time.now
223
+
224
+ new_calls.length.should == 1
225
+ new_call = new_calls[0]
226
+ new_call[:name].should == 'low_card_tables.cache_flush'
227
+ new_call[:started].should >= start_time
228
+ new_call[:finished].should >= new_call[:started]
229
+ new_call[:finished].should <= end_time
230
+ new_call[:data][:reason].should == :manually_requested
231
+ new_call[:data][:low_card_model].should == ::UserStatus
232
+ end
233
+
234
+ it "should notify listeners when the cache is flushed because an ID was not found" do
235
+ user1 = create_basic_user
236
+ call_count = @cache_listener.calls.length
237
+
238
+ start_time = Time.now
239
+ lambda do
240
+ ::UserStatus.low_card_rows_for_ids([ user1.user_status_id, user1.user_status_id + 1000 ])
241
+ end.should raise_error(LowCardTables::Errors::LowCardIdNotFoundError)
242
+
243
+ new_calls = @cache_listener.calls[call_count..-1]
244
+ end_time = Time.now
245
+
246
+ new_calls.length.should > 1
247
+ new_call = new_calls.detect { |c| c[:name] == 'low_card_tables.cache_flush' && c[:data][:reason] == :id_not_found }
248
+ new_call[:name].should == 'low_card_tables.cache_flush'
249
+ new_call[:started].should >= start_time
250
+ new_call[:finished].should >= new_call[:started]
251
+ new_call[:finished].should <= end_time
252
+ new_call[:data][:reason].should == :id_not_found
253
+ new_call[:data][:low_card_model].should == ::UserStatus
254
+ end
255
+
256
+ it "should notify listeners when adding a new row" do
257
+ @cache_listener.calls.length.should == 0
258
+
259
+ user1 = create_basic_user
260
+
261
+ call_count = @cache_listener.calls.length
262
+ call_count.should > 0
263
+
264
+ start_time = Time.now
265
+ user1.gender = 'male'
266
+ user1.donation_level = 9
267
+ user1.save!
268
+ end_time = Time.now
269
+
270
+ new_calls = @cache_listener.calls[call_count..-1]
271
+ new_calls.length.should > 0
272
+
273
+ create_calls = new_calls.select { |c| c[:name] == 'low_card_tables.rows_created' }
274
+ create_calls.length.should == 1
275
+ create_call = create_calls[0]
276
+
277
+ create_call[:started].should >= start_time
278
+ create_call[:finished].should <= end_time
279
+ create_call[:finished].should >= create_call[:started]
280
+ create_call[:data][:low_card_model].should == ::UserStatus
281
+
282
+ keys = create_call[:data][:keys].map(&:to_s)
283
+ keys.sort.should == %w{deceased deleted donation_level gender}
284
+
285
+ values_array = create_call[:data][:values]
286
+ values_array.length.should == 1
287
+ values = values_array[0]
288
+ values.length.should == keys.length
289
+
290
+ values[keys.index('deceased')].should == false
291
+ values[keys.index('deleted')].should == false
292
+ values[keys.index('gender')].should == 'male'
293
+ values[keys.index('donation_level')].should == 9
294
+ end
295
+ end
296
+
297
+ context "cache policies" do
298
+ before :each do
299
+ class LowCardTables::LowCardTable::RowManager
300
+ def current_time
301
+ self.class.override_current_time || Time.now
302
+ end
303
+
304
+ class << self
305
+ def override_current_time=(x)
306
+ @override_current_time = x
307
+ end
308
+
309
+ def override_current_time
310
+ @override_current_time
311
+ end
312
+ end
313
+ end
314
+
315
+ module LowCardTables::LowCardTable::CacheExpiration::HasCacheExpiration::ClassMethods
316
+ def low_card_current_time
317
+ LowCardTables::LowCardTable::RowManager.override_current_time || Time.now
318
+ end
319
+ end
320
+
321
+ class LowCardTables::LowCardTable::Cache
322
+ def current_time
323
+ LowCardTables::LowCardTable::RowManager.override_current_time || Time.now
324
+ end
325
+ end
326
+
327
+ class LowCardTables::LowCardTable::CacheExpiration::ExponentialCacheExpirationPolicy
328
+ def current_time
329
+ LowCardTables::LowCardTable::RowManager.override_current_time || Time.now
330
+ end
331
+ end
332
+ end
333
+
334
+ def check_cache_expiration(*cache_expiration_settings, &block)
335
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
336
+ @start_time = Time.now
337
+ set_current_time(0)
338
+
339
+ ::UserStatus.low_card_cache_expiration *cache_expiration_settings
340
+
341
+ user1 = create_basic_user
342
+
343
+ @last_call_count = spy.call_count
344
+ @spy = spy
345
+
346
+ block.call
347
+ end
348
+ end
349
+
350
+ def set_current_time(x)
351
+ LowCardTables::LowCardTable::RowManager.override_current_time = @start_time + x
352
+ end
353
+
354
+ def time_and_check(time, cached_or_not)
355
+ set_current_time(time)
356
+ new_user = create_basic_user
357
+
358
+ if cached_or_not == :cached
359
+ @spy.call_count.should == @last_call_count
360
+ elsif cached_or_not == :uncached
361
+ @spy.call_count.should > @last_call_count
362
+ @last_call_count = @spy.call_count
363
+ else
364
+ raise "Unknown cached_or_not: #{cached_or_not.inspect}"
365
+ end
366
+ end
367
+
368
+ it "should inherit policies correctly" do
369
+ ::LowCardTables.low_card_cache_expiration 20
370
+
371
+ ::LowCardTables.low_card_cache_expiration.should == 20
372
+
373
+ klass = Class.new(::ActiveRecord::Base)
374
+ klass.class_eval { is_low_card_table }
375
+ klass.low_card_cache_expiration.should == 20
376
+
377
+ ::LowCardTables.low_card_cache_expiration :unlimited
378
+ klass.low_card_cache_expiration.should == :unlimited
379
+
380
+ klass.low_card_cache_expiration 40
381
+ klass.low_card_cache_expiration.should == 40
382
+
383
+ ::LowCardTables.low_card_cache_expiration 190
384
+ klass.low_card_cache_expiration.should == 40
385
+ end
386
+
387
+ it "should apply a fixed setting correctly" do
388
+ check_cache_expiration(2.minutes) do |spy, initial_call_count|
389
+ time_and_check(119.seconds, :cached)
390
+ time_and_check(121.seconds, :uncached)
391
+ time_and_check(240.seconds, :cached)
392
+ time_and_check(242.seconds, :uncached)
393
+ end
394
+ end
395
+
396
+ it "should apply a setting of 0 correctly" do
397
+ check_cache_expiration(0) do |spy, initial_call_count|
398
+ time_and_check(1.seconds, :uncached)
399
+ time_and_check(1.seconds, :uncached)
400
+ time_and_check(2.seconds, :uncached)
401
+ time_and_check(3.seconds, :uncached)
402
+ time_and_check(3.seconds, :uncached)
403
+ end
404
+ end
405
+
406
+ it "should apply a setting of :unlimited correctly" do
407
+ check_cache_expiration(:unlimited) do |spy, initial_call_count|
408
+ time_and_check(1.seconds, :cached)
409
+ time_and_check(2.seconds, :cached)
410
+ time_and_check(5.years, :cached)
411
+ end
412
+ end
413
+
414
+ it "should apply a setting of :exponential with default settings correctly" do
415
+ check_cache_expiration(:exponential) do |spy, initial_call_count|
416
+ # 0-180: zero floor
417
+ time_and_check(0.seconds, :uncached)
418
+ time_and_check(1.seconds, :uncached)
419
+ time_and_check(30.seconds, :uncached)
420
+ time_and_check(90.seconds, :uncached)
421
+ time_and_check(179.seconds, :uncached)
422
+
423
+ # 180-190: ten second minimum
424
+ time_and_check(181.seconds, :uncached)
425
+ time_and_check(182.seconds, :cached)
426
+ time_and_check(189.seconds, :cached)
427
+
428
+ # 190-210: first doubling (20 seconds)
429
+ time_and_check(191.seconds, :uncached)
430
+ time_and_check(192.seconds, :cached)
431
+ time_and_check(209.seconds, :cached)
432
+
433
+ # 210-250: second doubling (40 seconds)
434
+ # don't check here -- want to make sure we skip this properly
435
+
436
+ # 250-330: third doubling (80 seconds)
437
+ time_and_check(251.seconds, :uncached)
438
+ time_and_check(295.seconds, :cached)
439
+ time_and_check(329.seconds, :cached)
440
+
441
+ # 330-490: fourth doubling (160 seconds)
442
+ # 490-810: fifth doubling (320 seconds)
443
+ # 810-1450: sixth doubling (640 seconds)
444
+ # 1450-2730: seventh doubling (1280 seconds)
445
+ # 2730-5290: eighth doubling (2560 seconds)
446
+ time_and_check(2731.seconds, :uncached)
447
+ time_and_check(2732.seconds, :cached)
448
+ time_and_check(5289.seconds, :cached)
449
+
450
+ # 5290-8890: max (3600 seconds)
451
+ time_and_check(5291.seconds, :uncached)
452
+ time_and_check(5292.seconds, :cached)
453
+ time_and_check(8889.seconds, :cached)
454
+
455
+ # 8890-12490: max (3600 seconds)
456
+ time_and_check(8891.seconds, :uncached)
457
+ time_and_check(8892.seconds, :cached)
458
+ time_and_check(12489.seconds, :cached)
459
+
460
+ # 12490-16090: max (3600 seconds)
461
+ time_and_check(12491.seconds, :uncached)
462
+ time_and_check(16089.seconds, :cached)
463
+ end
464
+ end
465
+
466
+ it "should apply a setting of :exponential with custom settings correctly" do
467
+ check_cache_expiration(:exponential, :zero_floor_time => 10.seconds, :min_time => 5.seconds, :exponent => 3.0, :max_time => 200.seconds) do |spy, initial_call_count|
468
+ # 0-10: zero floor
469
+ time_and_check(0.seconds, :uncached)
470
+ time_and_check(1.seconds, :uncached)
471
+ time_and_check(9.seconds, :uncached)
472
+
473
+ # 10-15: five second minimum
474
+ time_and_check(10.seconds, :uncached)
475
+ time_and_check(14.seconds, :cached)
476
+
477
+ # 15-30: first tripling (15 seconds)
478
+ time_and_check(16.seconds, :uncached)
479
+ time_and_check(17.seconds, :cached)
480
+ time_and_check(29.seconds, :cached)
481
+
482
+ # 30-75: second tripling (45 seconds)
483
+ time_and_check(31.seconds, :uncached)
484
+ time_and_check(32.seconds, :cached)
485
+ time_and_check(74.seconds, :cached)
486
+
487
+ # 75-210: third tripling (135 seconds)
488
+
489
+ # 210-410: max (200 seconds)
490
+ time_and_check(211.seconds, :uncached)
491
+ time_and_check(212.seconds, :cached)
492
+ time_and_check(409.seconds, :cached)
493
+
494
+ # 410-610: max (200 seconds)
495
+ time_and_check(411.seconds, :uncached)
496
+ time_and_check(412.seconds, :cached)
497
+ time_and_check(609.seconds, :cached)
498
+ end
499
+ end
500
+
501
+ it "should apply a setting of :exponential with custom settings with no zero floor" do
502
+ check_cache_expiration(:exponential, :zero_floor_time => 0, :min_time => 5.seconds, :exponent => 3.0, :max_time => 200.seconds) do |spy, initial_call_count|
503
+ # 0-5: five second minimum
504
+ time_and_check(1.seconds, :cached)
505
+ time_and_check(4.seconds, :cached)
506
+
507
+ # 5-20: first tripling (15 seconds)
508
+ time_and_check(6.seconds, :uncached)
509
+ time_and_check(7.seconds, :cached)
510
+ time_and_check(19.seconds, :cached)
511
+
512
+ # 20-65: second tripling (45 seconds)
513
+ time_and_check(21.seconds, :uncached)
514
+ time_and_check(22.seconds, :cached)
515
+ time_and_check(64.seconds, :cached)
516
+
517
+ # 65-200: third tripling (135 seconds)
518
+
519
+ # 200-400: max (200 seconds)
520
+ time_and_check(201.seconds, :uncached)
521
+ time_and_check(202.seconds, :cached)
522
+ time_and_check(399.seconds, :cached)
523
+
524
+ # 400-600: max (200 seconds)
525
+ time_and_check(401.seconds, :uncached)
526
+ time_and_check(402.seconds, :cached)
527
+ time_and_check(599.seconds, :cached)
528
+ end
529
+ end
530
+ end
531
+ end