record-cache 0.1.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.
- 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
|