frivol 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.travis.yml +15 -0
- data/Gemfile +13 -0
- data/LICENSE +1 -1
- data/README.rdoc +58 -38
- data/Rakefile +16 -26
- data/VERSION +1 -1
- data/frivol.gemspec +74 -62
- data/lib/frivol.rb +14 -363
- data/lib/frivol/class_methods.rb +195 -0
- data/lib/frivol/config.rb +39 -0
- data/lib/frivol/functor.rb +37 -0
- data/lib/frivol/helpers.rb +133 -0
- data/lib/frivol/time_extensions.rb +45 -0
- data/test/fake_redis.rb +36 -11
- data/test/helper.rb +16 -11
- data/test/test_buckets.rb +53 -0
- data/test/test_condition.rb +43 -0
- data/test/test_condition_with_counters.rb +90 -0
- data/test/test_counters.rb +72 -0
- data/test/test_else_with_counters.rb +39 -0
- data/test/test_extensions.rb +15 -0
- data/test/test_frivol.rb +96 -388
- data/test/test_frivolize.rb +81 -0
- data/test/test_seeds.rb +53 -0
- data/test/test_threads.rb +15 -0
- metadata +88 -88
- data/.gitignore +0 -21
data/test/test_frivol.rb
CHANGED
@@ -1,445 +1,153 @@
|
|
1
1
|
require "#{File.expand_path(File.dirname(__FILE__))}/helper.rb"
|
2
2
|
|
3
3
|
class TestFrivol < Test::Unit::TestCase
|
4
|
-
def
|
5
|
-
fake_redis # Comment out this line to test against a real live Redis
|
6
|
-
Frivol::Config.redis_config = { :thread_safe => true } # This will connect to a default Redis setup, otherwise set to { :host => "localhost", :port => 6379 }, for example
|
7
|
-
Frivol::Config.redis.flushdb
|
8
|
-
end
|
9
|
-
|
10
|
-
def teardown
|
11
|
-
# puts Frivol::Config.redis.inspect
|
12
|
-
Frivol::Config.redis.flushdb
|
13
|
-
end
|
14
|
-
|
15
|
-
should "have a default storage key made up of the class name and id" do
|
4
|
+
def test_have_a_default_storage_key_made_up_of_the_class_name_and_id
|
16
5
|
t = TestClass.new
|
17
6
|
assert_equal "TestClass-1", t.storage_key
|
18
7
|
end
|
19
|
-
|
20
|
-
|
8
|
+
|
9
|
+
def test_store_and_retrieve_data
|
21
10
|
t = TestClass.new
|
22
|
-
t.
|
23
|
-
assert_equal "value", t.
|
11
|
+
t.store :value => 'value'
|
12
|
+
assert_equal "value", t.retrieve(:value => 'default')
|
24
13
|
end
|
25
|
-
|
26
|
-
|
14
|
+
|
15
|
+
def test_return_a_default_for_a_value_thats_not_in_storage
|
27
16
|
t = TestClass.new
|
28
|
-
assert_equal "default", t.
|
17
|
+
assert_equal "default", t.retrieve(:value => 'default')
|
29
18
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
19
|
+
|
20
|
+
def test_save_and_retrieve_multiple_values
|
21
|
+
t = TestClass.new
|
22
|
+
t.store :val1 => 1, :val2 => 2
|
23
|
+
|
24
|
+
r = t.retrieve :val1 => nil, :val2 => nil
|
25
|
+
if ruby_one_eight?
|
26
|
+
assert r == [ 1, 2 ] || r == [ 2, 1 ] # yuck!
|
27
|
+
else
|
28
|
+
# Ruby 1.8 may fail this because hash keys do not guarantee order
|
29
|
+
assert_equal [ 1, 2 ], r
|
40
30
|
end
|
41
|
-
|
42
|
-
t = MultiTestClass.new
|
43
|
-
t.save
|
44
|
-
assert_equal [ 1, 2 ], t.load
|
45
31
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
def load
|
50
|
-
retrieve :value => nil, :value2 => :value2_default
|
51
|
-
end
|
52
|
-
|
32
|
+
|
33
|
+
def test_get_defaults_from_instance_methods_if_defined
|
34
|
+
t = Class.new(TestClass) do
|
53
35
|
def value2_default
|
54
36
|
"value2"
|
55
37
|
end
|
56
|
-
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
assert_equal [ "value", "value2" ], t.load.sort
|
38
|
+
end.new
|
39
|
+
|
40
|
+
result = t.retrieve :value => nil, :value2 => :value2_default
|
41
|
+
assert_equal [ "", "value2" ], result.map(&:to_s).sort
|
61
42
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
retrieve :value => nil
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
t = DefaultNilTestClass.new
|
71
|
-
assert_equal nil, t.load
|
43
|
+
|
44
|
+
def test_not_be_naughty_and_try_to_respond_to_nil_default
|
45
|
+
t = TestClass.new
|
46
|
+
assert_equal nil, t.retrieve(:value => nil)
|
72
47
|
end
|
73
48
|
|
74
|
-
|
75
|
-
|
49
|
+
def test_only_try_respond_to_symbol_defaults
|
50
|
+
t = Class.new(TestClass) do
|
76
51
|
def load
|
77
52
|
retrieve :value => "load", :def_val => :default_value # yes, that's right, we would cause a stack overflow here
|
78
53
|
end
|
79
|
-
|
54
|
+
|
80
55
|
def default_value
|
81
56
|
"yay!"
|
82
57
|
end
|
83
|
-
end
|
84
|
-
|
85
|
-
t = DefaultNonSymbolTestClass.new
|
58
|
+
end.new
|
59
|
+
|
86
60
|
assert_equal [ "load", "yay!" ], t.load.sort
|
87
61
|
end
|
88
62
|
|
89
|
-
|
90
|
-
|
63
|
+
def test_be_able_to_override_the_key_method
|
64
|
+
t = Class.new(TestClass) do
|
91
65
|
def storage_key(bucket = nil)
|
92
66
|
"my_storage"
|
93
67
|
end
|
94
|
-
end
|
95
|
-
|
96
|
-
t
|
97
|
-
t.save
|
98
|
-
assert_equal "value", t.load
|
68
|
+
end.new
|
69
|
+
|
70
|
+
t.store :value => 'value'
|
99
71
|
assert Frivol::Config.redis["my_storage"]
|
100
72
|
end
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
def load
|
109
|
-
retrieve :t => nil
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
t = TimeTestClass.new
|
114
|
-
t.save
|
115
|
-
assert_equal Time.local(2010, 8, 20), t.load
|
116
|
-
end
|
117
|
-
|
118
|
-
should "expire storage the everytime time it's stored" do
|
119
|
-
class ExpiryTestClass < TestClass
|
120
|
-
storage_expires_in 60
|
121
|
-
end
|
122
|
-
|
123
|
-
Frivol::Config.redis.expects(:expire).twice
|
124
|
-
Frivol::Config.redis.expects(:ttl).once
|
125
|
-
t = ExpiryTestClass.new
|
126
|
-
t.load
|
127
|
-
t.save
|
128
|
-
t = ExpiryTestClass.new # get a new one
|
129
|
-
t.save
|
73
|
+
|
74
|
+
def test_retain_Times_as_Times
|
75
|
+
t = TestClass.new
|
76
|
+
t.store :t => Time.local(2010, 8, 20)
|
77
|
+
assert_equal Time.local(2010, 8, 20), t.retrieve(:t => nil)
|
130
78
|
end
|
131
|
-
|
132
|
-
|
79
|
+
|
80
|
+
def test_expires_should_not_throw_nasty_errors
|
133
81
|
t = TestClass.new
|
134
|
-
t.
|
135
|
-
t.expire_storage 1
|
136
|
-
|
82
|
+
t.store :value => 'value'
|
83
|
+
t.expire_storage -1
|
84
|
+
|
137
85
|
t = TestClass.new # Get a fresh instance so that the @frivol_data is empty
|
138
|
-
assert_equal "default", t.
|
86
|
+
assert_equal "default", t.retrieve(:value => 'default')
|
139
87
|
end
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
t.
|
147
|
-
|
148
|
-
t = ExpiryTestClass.new # Get a fresh instance so that the @frivol_data is empty
|
149
|
-
assert_equal "default", t.load
|
88
|
+
|
89
|
+
def test_use_default_expiry_set_on_the_class
|
90
|
+
klass = Class.new(TestClass) { storage_expires_in -1 }
|
91
|
+
t = klass.new
|
92
|
+
t.store :value => 'value'
|
93
|
+
|
94
|
+
t = klass.new # Get a fresh instance so that the @frivol_data is empty
|
95
|
+
assert_equal "default", t.retrieve(:value => 'default')
|
150
96
|
end
|
151
97
|
|
152
|
-
#
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
t
|
158
|
-
t.save
|
98
|
+
# In older versions of Redis, if you save a value to a volatile key, the expiry is cleared
|
99
|
+
# TODO: test against older Redis and remove re-expiring for versions that don't need it
|
100
|
+
def test_resaving_a_value_should_not_clear_the_expiry
|
101
|
+
t = Class.new(TestClass) { storage_expires_in 2 }.new
|
102
|
+
|
103
|
+
t.store :value => 'value'
|
159
104
|
assert Frivol::Config.redis.ttl(t.storage_key) > 0
|
160
|
-
|
105
|
+
|
106
|
+
t.store :value => 'value' # a second time
|
161
107
|
assert Frivol::Config.redis.ttl(t.storage_key) > 0
|
162
108
|
end
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
t = BlankTestClass.new
|
109
|
+
|
110
|
+
def test_be_able_to_include_in_other_classes_with_storage_expiry
|
111
|
+
klass = Class.new
|
112
|
+
Frivol::Config.include_in(klass, 30)
|
113
|
+
t = klass.new
|
169
114
|
assert t.respond_to? :store
|
170
|
-
assert_equal 30,
|
171
|
-
end
|
172
|
-
|
173
|
-
should "return the already loaded hash if it's already loaded" do
|
174
|
-
Frivol::Config.redis.expects(:[]).once
|
175
|
-
t = TestClass.new
|
176
|
-
t.load
|
177
|
-
t.load
|
115
|
+
assert_equal 30, klass.storage_expiry
|
178
116
|
end
|
179
|
-
|
180
|
-
|
117
|
+
|
118
|
+
def test_return_the_already_loaded_hash_if_its_already_loaded
|
181
119
|
t = TestClass.new
|
182
|
-
t.
|
183
|
-
t.delete_storage
|
184
|
-
assert_equal "default", t.load
|
185
|
-
end
|
186
|
-
|
187
|
-
should "be able to create and use buckets" do
|
188
|
-
class SimpleBucketTestClass < TestClass
|
189
|
-
storage_bucket :blue
|
190
|
-
end
|
191
|
-
t = SimpleBucketTestClass.new
|
192
|
-
assert t.respond_to?(:store_blue)
|
193
|
-
assert t.respond_to?(:retrieve_blue)
|
194
|
-
end
|
195
|
-
|
196
|
-
should "store different values in different buckets" do
|
197
|
-
class StorageBucketTestClass < TestClass
|
198
|
-
storage_bucket :blue
|
199
|
-
|
200
|
-
def save_blue
|
201
|
-
store_blue :value => "blue value"
|
202
|
-
end
|
203
|
-
|
204
|
-
def load_blue
|
205
|
-
retrieve_blue :value => "blue default"
|
206
|
-
end
|
207
|
-
end
|
208
|
-
t = StorageBucketTestClass.new
|
209
|
-
t.save
|
210
|
-
t.save_blue
|
211
|
-
assert_equal "value", t.load
|
212
|
-
assert_equal "blue value", t.load_blue
|
213
|
-
end
|
120
|
+
t.retrieve :value => 'default'
|
214
121
|
|
215
|
-
|
216
|
-
|
217
|
-
storage_bucket :blue, :expires_in => 1
|
218
|
-
storage_expires_in 2
|
219
|
-
end
|
220
|
-
t = ExpireBucketsTestClass.new
|
221
|
-
assert_equal 1, ExpireBucketsTestClass.storage_expiry(:blue)
|
222
|
-
assert_equal 2, ExpireBucketsTestClass.storage_expiry
|
223
|
-
end
|
122
|
+
redis = Frivol::Config.redis
|
123
|
+
def redis.[](key); raise 'Onoes, loaded again'; end
|
224
124
|
|
225
|
-
|
226
|
-
class ExpireBucketsTestClass < TestClass
|
227
|
-
storage_bucket :blue, :expires_in => 1
|
228
|
-
storage_expires_in 3
|
229
|
-
|
230
|
-
def save_blue
|
231
|
-
store_blue :value => "blue value"
|
232
|
-
end
|
233
|
-
|
234
|
-
def load_blue
|
235
|
-
retrieve_blue :value => "blue default"
|
236
|
-
end
|
237
|
-
end
|
238
|
-
t = ExpireBucketsTestClass.new
|
239
|
-
t.save
|
240
|
-
t.save_blue
|
241
|
-
sleep 2
|
242
|
-
t = ExpireBucketsTestClass.new # get a new instance so @frivol_data is empty
|
243
|
-
assert_equal "value", t.load
|
244
|
-
assert_equal "blue default", t.load_blue
|
245
|
-
end
|
246
|
-
|
247
|
-
should "be able to create counter buckets" do
|
248
|
-
class SimpleCounterTestClass < TestClass
|
249
|
-
storage_bucket :blue, :counter => true
|
250
|
-
end
|
251
|
-
t = SimpleCounterTestClass.new
|
252
|
-
assert t.respond_to?(:store_blue)
|
253
|
-
assert t.respond_to?(:retrieve_blue)
|
254
|
-
assert t.respond_to?(:increment_blue)
|
125
|
+
t.retrieve :value => 'default'
|
255
126
|
end
|
256
|
-
|
257
|
-
should "store, increment and retrieve integers in a counter" do
|
258
|
-
class IncrCounterTestClass < TestClass
|
259
|
-
storage_bucket :blue, :counter => true
|
260
|
-
|
261
|
-
def save_blue
|
262
|
-
store_blue 10
|
263
|
-
end
|
264
|
-
|
265
|
-
def load_blue
|
266
|
-
retrieve_blue 0
|
267
|
-
end
|
268
|
-
end
|
269
|
-
t = IncrCounterTestClass.new
|
270
|
-
assert_equal 0, t.load_blue
|
271
|
-
t.save_blue
|
272
|
-
assert_equal 10, t.load_blue
|
273
|
-
assert_equal 11, t.increment_blue
|
274
|
-
assert_equal 11, t.load_blue
|
275
|
-
end
|
276
|
-
|
277
|
-
should "set expiry on counters" do
|
278
|
-
class SetExpireCounterTestClass < TestClass
|
279
|
-
storage_bucket :sheep_counter, :counter => true, :expires_in => 1
|
280
|
-
end
|
281
|
-
t = SetExpireCounterTestClass.new
|
282
|
-
assert_equal 1, SetExpireCounterTestClass.storage_expiry(:sheep_counter)
|
283
|
-
end
|
284
|
-
|
285
|
-
should "expire a counter bucket" do
|
286
|
-
class ExpireCounterTestClass < TestClass
|
287
|
-
storage_bucket :kitten_grave, :counter => true, :expires_in => 1
|
288
127
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
retrieve_kitten_grave 0
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
k = ExpireCounterTestClass.new
|
299
|
-
k.bury_kittens
|
300
|
-
assert_equal 10, k.peek_in_grave
|
301
|
-
|
302
|
-
sleep 2
|
303
|
-
assert_equal 0, k.peek_in_grave
|
128
|
+
def test_be_able_to_delete_storage
|
129
|
+
t = TestClass.new
|
130
|
+
t.store :value => ''
|
131
|
+
t.delete_storage
|
132
|
+
assert_equal "default", t.retrieve(:value => 'default')
|
304
133
|
end
|
305
134
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
def save_blue
|
312
|
-
store_blue 10
|
313
|
-
end
|
314
|
-
|
315
|
-
def load_blue
|
316
|
-
retrieve_blue 0
|
317
|
-
end
|
318
|
-
end
|
319
|
-
t = ThreadCounterTestClass.new
|
320
|
-
t.save_blue
|
321
|
-
assert_equal 10, t.load_blue
|
322
|
-
|
323
|
-
threads = []
|
324
|
-
100.times do
|
325
|
-
threads << Thread.new do
|
326
|
-
10.times do
|
327
|
-
temp = ThreadCounterTestClass.new
|
328
|
-
temp.increment_blue
|
329
|
-
sleep(rand(10) / 100.0)
|
330
|
-
end
|
331
|
-
end
|
332
|
-
end
|
333
|
-
threads.each { |a| a.join }
|
334
|
-
|
335
|
-
assert_equal 1010, t.load_blue
|
336
|
-
end
|
337
|
-
|
338
|
-
should "be able to delete storage for a bucket" do
|
339
|
-
class DeleteBucketTestClass < TestClass
|
340
|
-
storage_bucket :silver
|
341
|
-
|
342
|
-
def save_silver
|
343
|
-
store_silver :value => "value"
|
344
|
-
end
|
345
|
-
|
346
|
-
def load_silver
|
347
|
-
retrieve_silver :value => "default"
|
348
|
-
end
|
349
|
-
end
|
350
|
-
t = DeleteBucketTestClass.new
|
351
|
-
t.save_silver
|
352
|
-
assert_equal "value", t.load_silver
|
353
|
-
t.delete_silver
|
354
|
-
assert_equal "default", t.load_silver
|
355
|
-
end
|
356
|
-
|
357
|
-
should "be able to clear cached storage for a bucket" do
|
358
|
-
class ClearBucketTestClass < TestClass
|
359
|
-
storage_bucket :gold
|
360
|
-
|
361
|
-
def save_gold
|
362
|
-
store_gold :value => "value"
|
363
|
-
end
|
364
|
-
|
365
|
-
def load_gold
|
366
|
-
retrieve_gold :value => "default"
|
367
|
-
end
|
368
|
-
end
|
369
|
-
t = ClearBucketTestClass.new
|
370
|
-
t.save_gold
|
371
|
-
assert_equal "value", t.load_gold
|
372
|
-
t.clear_gold
|
373
|
-
# ensure we're getting the result from Redis and not the cache
|
374
|
-
Frivol::Config.redis.expects(:[]).with(t.storage_key(:gold)).once.returns({ :value => "value" }.to_json)
|
375
|
-
assert_equal "value", t.load_gold
|
376
|
-
end
|
377
|
-
|
378
|
-
should "frivolize methods" do
|
379
|
-
class FrivolizeTestClass < TestClass
|
380
|
-
def dinosaur_count
|
381
|
-
10
|
382
|
-
end
|
383
|
-
frivolize :dinosaur_count
|
384
|
-
end
|
135
|
+
def test_be_able_to_clear_cached_storage_for_a_bucket
|
136
|
+
t = Class.new(TestClass) { storage_bucket :gold }.new
|
137
|
+
t.store_gold :value => "value"
|
138
|
+
value = t.retrieve_gold :value => "default"
|
139
|
+
assert_equal "value", value
|
385
140
|
|
386
|
-
|
387
|
-
Frivol::Config.redis.expects(:[]).twice.returns(nil, { :dinosaur_count => 10 }.to_json)
|
141
|
+
t.clear_gold
|
388
142
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
assert_equal 10, t.dinosaur_count
|
394
|
-
|
395
|
-
t = FrivolizeTestClass.new # a fresh instance
|
396
|
-
assert_equal 10, t.dinosaur_count
|
397
|
-
end
|
398
|
-
|
399
|
-
should "frivolize methods with expiry in a bucket" do
|
400
|
-
class FrivolizeExpiringBucketTestClass < TestClass
|
401
|
-
def dinosaur_count
|
402
|
-
10
|
403
|
-
end
|
404
|
-
frivolize :dinosaur_count, { :bucket => :dinosaurs, :expires_in => 1 }
|
143
|
+
# ensure we're getting the result from Redis and not the cache
|
144
|
+
redis = Frivol::Config.redis
|
145
|
+
def redis.[](key)
|
146
|
+
MultiJson.dump(:value => 'this is what we want')
|
405
147
|
end
|
406
|
-
Frivol::Config.redis.expects(:[]=).twice
|
407
|
-
Frivol::Config.redis.expects(:[]).times(3).returns(nil, { :dinosaur_count => 10 }.to_json, nil)
|
408
148
|
|
409
|
-
|
410
|
-
assert_equal
|
411
|
-
|
412
|
-
t = FrivolizeExpiringBucketTestClass.new # a fresh instance
|
413
|
-
assert_equal 10, t.dinosaur_count
|
414
|
-
|
415
|
-
sleep 2
|
416
|
-
|
417
|
-
t = FrivolizeExpiringBucketTestClass.new # another fresh instance after value expired
|
418
|
-
assert_equal 10, t.dinosaur_count
|
149
|
+
value = t.retrieve_gold :value => "default"
|
150
|
+
assert_equal "this is what we want", value
|
419
151
|
end
|
420
|
-
|
421
|
-
should "frivolize methods with expiry as a counter" do
|
422
|
-
class FrivolizeExpiringBucketTestClass < TestClass
|
423
|
-
def dinosaur_count
|
424
|
-
10
|
425
|
-
end
|
426
|
-
frivolize :dinosaur_count, { :expires_in => 1, :counter => true }
|
427
|
-
end
|
428
|
-
Frivol::Config.redis.expects(:[]=).twice
|
429
|
-
Frivol::Config.redis.expects(:[]).times(3).returns(nil, 10, nil)
|
430
|
-
|
431
|
-
t = FrivolizeExpiringBucketTestClass.new
|
432
|
-
assert t.methods.include? "store_dinosaur_count" # check that the bucket name is the method name
|
433
152
|
|
434
|
-
assert_equal 10, t.dinosaur_count
|
435
|
-
|
436
|
-
t = FrivolizeExpiringBucketTestClass.new # a fresh instance
|
437
|
-
assert_equal 10, t.dinosaur_count
|
438
|
-
|
439
|
-
sleep 2
|
440
|
-
|
441
|
-
t = FrivolizeExpiringBucketTestClass.new # another fresh instance after value expired
|
442
|
-
assert_equal 10, t.dinosaur_count
|
443
|
-
end
|
444
|
-
|
445
153
|
end
|