record-cache 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/record-cache.rb +1 -0
- data/lib/record_cache/active_record.rb +318 -0
- data/lib/record_cache/base.rb +136 -0
- data/lib/record_cache/dispatcher.rb +90 -0
- data/lib/record_cache/multi_read.rb +51 -0
- data/lib/record_cache/query.rb +85 -0
- data/lib/record_cache/statistics.rb +82 -0
- data/lib/record_cache/strategy/base.rb +154 -0
- data/lib/record_cache/strategy/id_cache.rb +93 -0
- data/lib/record_cache/strategy/index_cache.rb +122 -0
- data/lib/record_cache/strategy/request_cache.rb +49 -0
- data/lib/record_cache/test/resettable_version_store.rb +49 -0
- data/lib/record_cache/version.rb +5 -0
- data/lib/record_cache/version_store.rb +54 -0
- data/lib/record_cache.rb +11 -0
- data/spec/db/database.yml +6 -0
- data/spec/db/schema.rb +42 -0
- data/spec/db/seeds.rb +40 -0
- data/spec/initializers/record_cache.rb +14 -0
- data/spec/lib/dispatcher_spec.rb +86 -0
- data/spec/lib/multi_read_spec.rb +51 -0
- data/spec/lib/query_spec.rb +148 -0
- data/spec/lib/statistics_spec.rb +140 -0
- data/spec/lib/strategy/base_spec.rb +241 -0
- data/spec/lib/strategy/id_cache_spec.rb +168 -0
- data/spec/lib/strategy/index_cache_spec.rb +223 -0
- data/spec/lib/strategy/request_cache_spec.rb +85 -0
- data/spec/lib/version_store_spec.rb +104 -0
- data/spec/models/apple.rb +8 -0
- data/spec/models/banana.rb +8 -0
- data/spec/models/pear.rb +6 -0
- data/spec/models/person.rb +11 -0
- data/spec/models/store.rb +13 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/after_commit.rb +71 -0
- data/spec/support/matchers/hit_cache_matcher.rb +53 -0
- data/spec/support/matchers/miss_cache_matcher.rb +53 -0
- data/spec/support/matchers/use_cache_matcher.rb +53 -0
- metadata +253 -0
data/spec/db/seeds.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
ActiveRecord::Schema.define :version => 1 do
|
2
|
+
|
3
|
+
# Make sure that at the beginning of the tests, NOTHING is known to Record Cache
|
4
|
+
RecordCache::Base.disable!
|
5
|
+
|
6
|
+
@adam = Person.create!(:name => "Adam", :birthday => Date.civil(1975,03,20), :height => 1.83)
|
7
|
+
@blue = Person.create!(:name => "Blue", :birthday => Date.civil(1953,11,11), :height => 1.75)
|
8
|
+
@cris = Person.create!(:name => "Cris", :birthday => Date.civil(1975,03,20), :height => 1.75)
|
9
|
+
|
10
|
+
@adam_apples = Store.create!(:name => "Adams Apple Store", :owner => @adam)
|
11
|
+
@blue_fruits = Store.create!(:name => "Blue Fruits", :owner => @blue)
|
12
|
+
@cris_bananas = Store.create!(:name => "Chris Bananas", :owner => @cris)
|
13
|
+
|
14
|
+
@fry = Person.create!(:name => "Fry", :birthday => Date.civil(1985,01,20), :height => 1.69)
|
15
|
+
@chase = Person.create!(:name => "Chase", :birthday => Date.civil(1970,07,03), :height => 1.91)
|
16
|
+
@penny = Person.create!(:name => "Penny", :birthday => Date.civil(1958,04,16), :height => 1.61)
|
17
|
+
|
18
|
+
Apple.create!(:name => "Adams Apple 1", :store => @adam_apples)
|
19
|
+
Apple.create!(:name => "Adams Apple 2", :store => @adam_apples)
|
20
|
+
Apple.create!(:name => "Adams Apple 3", :store => @adam_apples, :person => @fry)
|
21
|
+
Apple.create!(:name => "Adams Apple 4", :store => @adam_apples, :person => @fry)
|
22
|
+
Apple.create!(:name => "Adams Apple 5", :store => @adam_apples, :person => @chase)
|
23
|
+
Apple.create!(:name => "Blue Apple 1", :store => @blue_fruits, :person => @fry)
|
24
|
+
Apple.create!(:name => "Blue Apple 2", :store => @blue_fruits, :person => @fry)
|
25
|
+
Apple.create!(:name => "Blue Apple 3", :store => @blue_fruits, :person => @chase)
|
26
|
+
Apple.create!(:name => "Blue Apple 4", :store => @blue_fruits, :person => @chase)
|
27
|
+
|
28
|
+
Banana.create!(:name => "Blue Banana 1", :store => @blue_fruits, :person => @fry)
|
29
|
+
Banana.create!(:name => "Blue Banana 2", :store => @blue_fruits, :person => @chase)
|
30
|
+
Banana.create!(:name => "Blue Banana 3", :store => @blue_fruits, :person => @chase)
|
31
|
+
Banana.create!(:name => "Cris Banana 1", :store => @cris_bananas, :person => @fry)
|
32
|
+
Banana.create!(:name => "Cris Banana 2", :store => @cris_bananas, :person => @chase)
|
33
|
+
|
34
|
+
Pear.create!(:name => "Blue Pear 1", :store => @blue_fruits)
|
35
|
+
Pear.create!(:name => "Blue Pear 2", :store => @blue_fruits, :person => @fry)
|
36
|
+
Pear.create!(:name => "Blue Pear 3", :store => @blue_fruits, :person => @chase)
|
37
|
+
Pear.create!(:name => "Blue Pear 4", :store => @blue_fruits, :person => @chase)
|
38
|
+
|
39
|
+
RecordCache::Base.enable
|
40
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# --- Version Store
|
2
|
+
# All Workers that use the Record Cache should point to the same Version Store
|
3
|
+
# E.g. a MemCached cluster or a Redis Store (defaults to Rails.cache)
|
4
|
+
RecordCache::Base.version_store = ActiveSupport::Cache.lookup_store(:memory_store)
|
5
|
+
|
6
|
+
# --- Record Stores
|
7
|
+
# Register Cache Stores for the Records themselves
|
8
|
+
# Note: A different Cache Store could be used per Model, but in most configurations the following 2 stores will suffice:
|
9
|
+
|
10
|
+
# The :local store is used to keep records in Worker memory
|
11
|
+
RecordCache::Base.register_store(:local, ActiveSupport::Cache.lookup_store(:memory_store))
|
12
|
+
|
13
|
+
# The :shared store is used to share Records between multiple Workers
|
14
|
+
RecordCache::Base.register_store(:shared, ActiveSupport::Cache.lookup_store(:memory_store))
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RecordCache::Dispatcher do
|
4
|
+
before(:each) do
|
5
|
+
@apple_dispatcher = Apple.record_cache
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should raise an error when the same index is added twice" do
|
9
|
+
lambda { @apple_dispatcher.register(:store_id, RecordCache::Strategy::IdCache, nil, {}) }.should raise_error("Multiple record cache definitions found for 'store_id' on Apple")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return the Cache for the requested strategy" do
|
13
|
+
@apple_dispatcher[:id].class.should == RecordCache::Strategy::IdCache
|
14
|
+
@apple_dispatcher[:store_id].class.should == RecordCache::Strategy::IndexCache
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return nil for unknown requested strategies" do
|
18
|
+
@apple_dispatcher[:unknown].should == nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return cacheable? true if there is a cacheable strategy that accepts the query" do
|
22
|
+
query = RecordCache::Query.new
|
23
|
+
mock(@apple_dispatcher).first_cacheable_strategy(query) { Object.new }
|
24
|
+
@apple_dispatcher.cacheable?(query).should == true
|
25
|
+
end
|
26
|
+
|
27
|
+
context "fetch" do
|
28
|
+
it "should delegate fetch to the Request Cache if present" do
|
29
|
+
query = RecordCache::Query.new
|
30
|
+
mock(@apple_dispatcher[:request_cache]).fetch(query)
|
31
|
+
@apple_dispatcher.fetch(query)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should delegate fetch to the first cacheable strategy if Request Cache is not present" do
|
35
|
+
query = RecordCache::Query.new
|
36
|
+
banana_dispatcher = Banana.record_cache
|
37
|
+
banana_dispatcher[:request_cache].should == nil
|
38
|
+
mock(banana_dispatcher).first_cacheable_strategy(query) { mock(Object.new).fetch(query) }
|
39
|
+
banana_dispatcher.fetch(query)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "record_change" do
|
44
|
+
it "should dispatch record_change to all strategies" do
|
45
|
+
apple = Apple.first
|
46
|
+
[:id, :store_id, :person_id].each do |strategy|
|
47
|
+
mock(@apple_dispatcher[strategy]).record_change(apple, :create)
|
48
|
+
end
|
49
|
+
@apple_dispatcher.record_change(apple, :create)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should not dispatch record_change for updates without changes" do
|
53
|
+
apple = Apple.first
|
54
|
+
[:request_cache, :id, :store_id, :person_id].each do |strategy|
|
55
|
+
mock(@apple_dispatcher[strategy]).record_change(anything, anything).times(0)
|
56
|
+
end
|
57
|
+
@apple_dispatcher.record_change(apple, :update)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "invalidate" do
|
62
|
+
it "should default to the :id strategy" do
|
63
|
+
mock(@apple_dispatcher[:id]).invalidate(15)
|
64
|
+
@apple_dispatcher.invalidate(15)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should delegate to given strategy" do
|
68
|
+
mock(@apple_dispatcher[:id]).invalidate(15)
|
69
|
+
mock(@apple_dispatcher[:store_id]).invalidate(31)
|
70
|
+
@apple_dispatcher.invalidate(:id, 15)
|
71
|
+
@apple_dispatcher.invalidate(:store_id, 31)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should invalidate the request cache" do
|
75
|
+
store_dispatcher = Store.record_cache
|
76
|
+
mock(store_dispatcher[:request_cache]).invalidate(15)
|
77
|
+
store_dispatcher.invalidate(:id, 15)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should even invalidate the request cache if the given strategy is not known" do
|
81
|
+
store_dispatcher = Store.record_cache
|
82
|
+
mock(store_dispatcher[:request_cache]).invalidate(31)
|
83
|
+
store_dispatcher.invalidate(:unknown_id, 31)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RecordCache::MultiRead do
|
4
|
+
|
5
|
+
it "should not delegate to single reads when multi_read is supported" do
|
6
|
+
class MultiReadSupported
|
7
|
+
def read(key) "single" end
|
8
|
+
def read_multi(*keys) "multi" end
|
9
|
+
end
|
10
|
+
store = RecordCache::MultiRead.test(MultiReadSupported.new)
|
11
|
+
store.read_multi("key1", "key2").should == "multi"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should delegate to single reads when multi_read is explicitly disabled" do
|
15
|
+
class ExplicitlyDisabled
|
16
|
+
def read(key) "single" end
|
17
|
+
def read_multi(*keys) "multi" end
|
18
|
+
end
|
19
|
+
RecordCache::MultiRead.disable(ExplicitlyDisabled)
|
20
|
+
store = RecordCache::MultiRead.test(ExplicitlyDisabled.new)
|
21
|
+
store.read_multi("key1", "key2").should == {"key1" => "single", "key2" => "single"}
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should delegate to single reads when multi_read throws an error" do
|
25
|
+
class MultiReadNotImplemented
|
26
|
+
def read(key) "single" end
|
27
|
+
def read_multi(*keys) raise NotImplementedError.new("multiread not implemented") end
|
28
|
+
end
|
29
|
+
store = RecordCache::MultiRead.test(MultiReadNotImplemented.new)
|
30
|
+
store.read_multi("key1", "key2").should == {"key1" => "single", "key2" => "single"}
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should delegate to single reads when multi_read is undefined" do
|
34
|
+
class MultiReadNotDefined
|
35
|
+
def read(key) "single" end
|
36
|
+
end
|
37
|
+
store = RecordCache::MultiRead.test(MultiReadNotDefined.new)
|
38
|
+
store.read_multi("key1", "key2").should == {"key1" => "single", "key2" => "single"}
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should have tested the Version Store" do
|
42
|
+
RecordCache::MultiRead.instance_variable_get(:@tested).should include(RecordCache::Base.version_store.instance_variable_get(:@store))
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should have tested all Record Stores" do
|
46
|
+
tested_stores = RecordCache::MultiRead.instance_variable_get(:@tested)
|
47
|
+
RecordCache::Base.stores.values.each do |record_store|
|
48
|
+
tested_stores.should include(record_store)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RecordCache::Query do
|
4
|
+
before(:each) do
|
5
|
+
@query = RecordCache::Query.new
|
6
|
+
end
|
7
|
+
|
8
|
+
context "wheres" do
|
9
|
+
it "should be an empty hash by default" do
|
10
|
+
@query.wheres.should == {}
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should fill wheres on instantiation" do
|
14
|
+
@query = RecordCache::Query.new({:id => 1})
|
15
|
+
@query.wheres.should == {:id => 1}
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should keep track of where clauses" do
|
19
|
+
@query.where(:name, "My name")
|
20
|
+
@query.where(:id, [1, 2, 3])
|
21
|
+
@query.where(:height, 1.75)
|
22
|
+
@query.wheres.should == {:name => "My name", :id => [1, 2, 3], :height => 1.75}
|
23
|
+
end
|
24
|
+
|
25
|
+
context "where_ids" do
|
26
|
+
it "should return nil if the attribute is not defined" do
|
27
|
+
@query.where(:idx, 15)
|
28
|
+
@query.where_ids(:id).should == nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return nil if one the value is nil" do
|
32
|
+
@query.where(:id, nil)
|
33
|
+
@query.where_ids(:id).should == nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return nil if one of the values is < 1" do
|
37
|
+
@query.where(:id, [2, 0, 8])
|
38
|
+
@query.where_ids(:id).should == nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return nil if one of the values is nil" do
|
42
|
+
@query.where(:id, ["1", nil, "3"])
|
43
|
+
@query.where_ids(:id).should == nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should retrieve an array of integers when a single integer is provided" do
|
47
|
+
@query.where(:id, 15)
|
48
|
+
@query.where_ids(:id).should == [15]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should retrieve an array of integers when a multiple integers are provided" do
|
52
|
+
@query.where(:id, [2, 4, 8])
|
53
|
+
@query.where_ids(:id).should == [2, 4, 8]
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should retrieve an array of integers when a single string is provided" do
|
57
|
+
@query.where(:id, "15")
|
58
|
+
@query.where_ids(:id).should == [15]
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should retrieve an array of integers when a multiple strings are provided" do
|
62
|
+
@query.where(:id, ["2", "4", "8"])
|
63
|
+
@query.where_ids(:id).should == [2, 4, 8]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should cache the array of integers" do
|
67
|
+
@query.where(:id, ["2", "4", "8"])
|
68
|
+
ids1 = @query.where_ids(:id)
|
69
|
+
ids2 = @query.where_ids(:id)
|
70
|
+
ids1.object_id.should == ids2.object_id
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "where_id" do
|
75
|
+
it "should return nil when multiple integers are provided" do
|
76
|
+
@query.where(:id, [2, 4, 8])
|
77
|
+
@query.where_id(:id).should == nil
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should return the id when a single integer is provided" do
|
81
|
+
@query.where(:id, 4)
|
82
|
+
@query.where_id(:id).should == 4
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should return the id when a single string is provided" do
|
86
|
+
@query.where(:id, ["4"])
|
87
|
+
@query.where_id(:id).should == 4
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "sort" do
|
93
|
+
it "should be an empty array by default" do
|
94
|
+
@query.sort_orders.should == []
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should keep track of sort orders" do
|
98
|
+
@query.order_by("name", true)
|
99
|
+
@query.order_by("id", false)
|
100
|
+
@query.sort_orders.should == [ ["name", true], ["id", false] ]
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should convert attribute to string" do
|
104
|
+
@query.order_by(:name, true)
|
105
|
+
@query.sort_orders.should == [ ["name", true] ]
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should define sorted?" do
|
109
|
+
@query.sorted?.should == false
|
110
|
+
@query.order_by("name", true)
|
111
|
+
@query.sorted?.should == true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "limit" do
|
116
|
+
it "should be +nil+ by default" do
|
117
|
+
@query.limit.should == nil
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should keep track of limit" do
|
121
|
+
@query.limit = 4
|
122
|
+
@query.limit.should == 4
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should convert limit to integer" do
|
126
|
+
@query.limit = "4"
|
127
|
+
@query.limit.should == 4
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context "utility" do
|
132
|
+
before(:each) do
|
133
|
+
@query.where(:name, "My name")
|
134
|
+
@query.where(:id, [1, 2, 3])
|
135
|
+
@query.order_by("name", true)
|
136
|
+
@query.limit = "4"
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should generate a unique key for (request) caching purposes" do
|
140
|
+
@query.cache_key.should == 'name="My name"&id=[1, 2, 3].name=AL4'
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should generate a pretty formatted query" do
|
144
|
+
@query.to_s.should == 'SELECT name = "My name" AND id = [1, 2, 3] ORDER_BY name ASC LIMIT 4'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RecordCache::Statistics do
|
4
|
+
before(:each) do
|
5
|
+
# remove active setting from other tests
|
6
|
+
RecordCache::Statistics.send(:remove_instance_variable, :@active) if RecordCache::Statistics.instance_variable_get(:@active)
|
7
|
+
end
|
8
|
+
|
9
|
+
context "active" do
|
10
|
+
it "should default to false" do
|
11
|
+
RecordCache::Statistics.active?.should == false
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be activated by start" do
|
15
|
+
RecordCache::Statistics.start
|
16
|
+
RecordCache::Statistics.active?.should == true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be deactivated by stop" do
|
20
|
+
RecordCache::Statistics.start
|
21
|
+
RecordCache::Statistics.active?.should == true
|
22
|
+
RecordCache::Statistics.stop
|
23
|
+
RecordCache::Statistics.active?.should == false
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be toggleable" do
|
27
|
+
RecordCache::Statistics.toggle
|
28
|
+
RecordCache::Statistics.active?.should == true
|
29
|
+
RecordCache::Statistics.toggle
|
30
|
+
RecordCache::Statistics.active?.should == false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "find" do
|
35
|
+
it "should return {} for unknown base classes" do
|
36
|
+
class UnknownBase; end
|
37
|
+
RecordCache::Statistics.find(UnknownBase).should == {}
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should create a new counter for unknown strategies" do
|
41
|
+
class UnknownBase; end
|
42
|
+
counter = RecordCache::Statistics.find(UnknownBase, :strategy)
|
43
|
+
counter.calls.should == 0
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should retrieve all strategies if only the base is provided" do
|
47
|
+
class KnownBase; end
|
48
|
+
counter1 = RecordCache::Statistics.find(KnownBase, :strategy1)
|
49
|
+
counter2 = RecordCache::Statistics.find(KnownBase, :strategy2)
|
50
|
+
counters = RecordCache::Statistics.find(KnownBase)
|
51
|
+
counters.size.should == 2
|
52
|
+
counters[:strategy1].should == counter1
|
53
|
+
counters[:strategy2].should == counter2
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should retrieve the counter for an existing strategy" do
|
57
|
+
class KnownBase; end
|
58
|
+
counter1 = RecordCache::Statistics.find(KnownBase, :strategy1)
|
59
|
+
RecordCache::Statistics.find(KnownBase, :strategy1).should == counter1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "reset!" do
|
64
|
+
before(:each) do
|
65
|
+
class BaseA; end
|
66
|
+
@counter_a1 = RecordCache::Statistics.find(BaseA, :strategy1)
|
67
|
+
@counter_a2 = RecordCache::Statistics.find(BaseA, :strategy2)
|
68
|
+
class BaseB; end
|
69
|
+
@counter_b1 = RecordCache::Statistics.find(BaseB, :strategy1)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should reset all counters for a specific base" do
|
73
|
+
mock(@counter_a1).reset!
|
74
|
+
mock(@counter_a2).reset!
|
75
|
+
mock(@counter_b1).reset!.times(0)
|
76
|
+
RecordCache::Statistics.reset!(BaseA)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should reset all counters" do
|
80
|
+
mock(@counter_a1).reset!
|
81
|
+
mock(@counter_a2).reset!
|
82
|
+
mock(@counter_b1).reset!
|
83
|
+
RecordCache::Statistics.reset!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "counter" do
|
88
|
+
before(:each) do
|
89
|
+
@counter = RecordCache::Statistics::Counter.new
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should be empty by default" do
|
93
|
+
[@counter.calls, @counter.hits, @counter.misses].should == [0, 0, 0]
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should delegate active? to RecordCache::Statistics" do
|
97
|
+
mock(RecordCache::Statistics).active?
|
98
|
+
@counter.active?
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should add hits and misses" do
|
102
|
+
@counter.add(4, 3)
|
103
|
+
[@counter.calls, @counter.hits, @counter.misses].should == [1, 3, 1]
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should sum added hits and misses" do
|
107
|
+
@counter.add(4, 3)
|
108
|
+
@counter.add(1, 1)
|
109
|
+
@counter.add(3, 2)
|
110
|
+
[@counter.calls, @counter.hits, @counter.misses].should == [3, 6, 2]
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should reset! hits and misses" do
|
114
|
+
@counter.add(4, 3)
|
115
|
+
@counter.add(1, 1)
|
116
|
+
@counter.reset!
|
117
|
+
[@counter.calls, @counter.hits, @counter.misses].should == [0, 0, 0]
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should provide 0.0 percentage for empty counter" do
|
121
|
+
@counter.percentage.should == 0.0
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should provide percentage" do
|
125
|
+
@counter.add(4, 3)
|
126
|
+
@counter.percentage.should == 75.0
|
127
|
+
@counter.add(1, 1)
|
128
|
+
@counter.percentage.should == 80.0
|
129
|
+
@counter.add(5, 2)
|
130
|
+
@counter.percentage.should == 60.0
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should pretty print on inspect" do
|
134
|
+
@counter.add(4, 3)
|
135
|
+
@counter.add(1, 1)
|
136
|
+
@counter.add(5, 2)
|
137
|
+
@counter.inspect.should == "60.0% (6/10)"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|