persistent-cache-ram 0.4.1
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/.gitignore +2 -0
- data/Gemfile +7 -0
- data/LICENSE +22 -0
- data/README.md +33 -0
- data/Rakefile +2 -0
- data/lib/persistent-cache.rb +122 -0
- data/lib/persistent-cache/storage/storage_ram.rb +51 -0
- data/lib/persistent-cache/version.rb +5 -0
- data/persistent-cache-ram.gemspec +22 -0
- data/spec/persistent-cache_spec.rb +214 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/storage/storage_ram_spec.rb +132 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c7d3cdc58f9ac1e510011927748260e063825f52
|
4
|
+
data.tar.gz: 63c5741f23250ac692f5f0b7e92d2e13f64bc5bb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6c993c25d4af5093b66223a3ea1b74cce639af34d41c5973445350ff58eccdb62b356aa0d3e5e754558d9adf7687faa69cf917fdaadb025ea7cb1c593d553acb
|
7
|
+
data.tar.gz: a5b627e4efff38f5f8f08dc5ed4502bbd664da66640cb8ccef9a5dff07f108e4a2936896748ff13ffb90e7fd8d74e0c0bc9a24a4b8397c1456c4ff3917e1101a
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Wynand van Dyk, Ernst van Graan, Hetzner (Pty) Ltd
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Persistent::Cache
|
2
|
+
|
3
|
+
Persistent cache behaves like a hash, with a pluggable back-end. This gem only implements STORAGE_RAM, and in doing so has no dependencies such as sqlite, etc. Please see the persistent-cache gem for support for more back-ends and a comprehensive README.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'persistent-cache-ram'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install persistent-cache
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Please see the persistent-cache gem for usage instructions. This is a STORAGE_RAM only implementation.
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
Please send feedback and comments to the authors at:
|
26
|
+
|
27
|
+
Ernst van Graan <ernst.van.graan@hetzner.co.za>
|
28
|
+
|
29
|
+
1. Fork it
|
30
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
31
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
32
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
33
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require "persistent-cache/version"
|
2
|
+
require "persistent-cache/storage/storage_ram"
|
3
|
+
|
4
|
+
module Persistent
|
5
|
+
class Cache
|
6
|
+
STORAGE_RAM = 'ram' unless defined? STORAGE_RAM
|
7
|
+
|
8
|
+
# Fresh is 1 day less than the bacula default job retention time. If this is configured differently, FRESH should be updated as well.
|
9
|
+
FRESH = 15465600; FRESH.freeze
|
10
|
+
|
11
|
+
attr_accessor :storage_details
|
12
|
+
attr_accessor :storage
|
13
|
+
attr_accessor :fresh
|
14
|
+
attr_accessor :encoding
|
15
|
+
|
16
|
+
def initialize(storage_details, fresh = FRESH, storage = STORAGE_RAM)
|
17
|
+
raise ArgumentError.new("No storage details provided") if storage_details.nil? or storage_details == ""
|
18
|
+
|
19
|
+
@storage = StorageRAM.new if storage == STORAGE_RAM
|
20
|
+
@fresh = fresh
|
21
|
+
@storage_details = storage_details
|
22
|
+
|
23
|
+
raise ArgumentError.new("Unsupported storage type #{storage}}") if @storage.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def set(key, value, timestamp)
|
27
|
+
if value.nil?
|
28
|
+
delete_entry(key)
|
29
|
+
else
|
30
|
+
save_key_value_pair(key, value, timestamp)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def []=(key, value)
|
35
|
+
if value.nil?
|
36
|
+
delete_entry(key)
|
37
|
+
else
|
38
|
+
save_key_value_pair(key, value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def [](key)
|
43
|
+
lookup_key(key)
|
44
|
+
end
|
45
|
+
|
46
|
+
def each(&block)
|
47
|
+
keys.each do |key|
|
48
|
+
yield key, lookup_key(key)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def size
|
53
|
+
@storage.size
|
54
|
+
end
|
55
|
+
|
56
|
+
def keys
|
57
|
+
@storage.keys
|
58
|
+
end
|
59
|
+
|
60
|
+
def clear
|
61
|
+
@storage.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
def timestamp?(key)
|
65
|
+
k = encode_if_requested(key)
|
66
|
+
result = @storage.lookup_key(k)
|
67
|
+
return nil if result.nil? or result[1].nil?
|
68
|
+
Time.parse(result[1])
|
69
|
+
end
|
70
|
+
|
71
|
+
def key?(key)
|
72
|
+
if @storage.keys and @storage.keys[0]
|
73
|
+
@storage.keys[0].each do |k|
|
74
|
+
return k if k == key
|
75
|
+
end
|
76
|
+
end
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def encode_if_requested(key)
|
83
|
+
return key.encode(@encoding) if (not @encoding.nil?) and (key.is_a?(String))
|
84
|
+
key
|
85
|
+
end
|
86
|
+
|
87
|
+
def save_key_value_pair(key, value, timestamp = nil)
|
88
|
+
k = encode_if_requested(key)
|
89
|
+
@storage.delete_entry(k)
|
90
|
+
@storage.save_key_value_pair(k, value, timestamp)
|
91
|
+
end
|
92
|
+
|
93
|
+
def lookup_key(key)
|
94
|
+
k = encode_if_requested(key)
|
95
|
+
result = @storage.lookup_key(k)
|
96
|
+
return nil if nil_result?(result)
|
97
|
+
return nil if stale_entry?(k, result)
|
98
|
+
|
99
|
+
return result[0]
|
100
|
+
end
|
101
|
+
|
102
|
+
def stale_entry?(key, result)
|
103
|
+
return false if @fresh.nil?
|
104
|
+
|
105
|
+
timestamp = Time.parse(result[1])
|
106
|
+
if ((Time.now - timestamp) > @fresh)
|
107
|
+
delete_entry(key)
|
108
|
+
return true
|
109
|
+
end
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
|
113
|
+
def delete_entry(key)
|
114
|
+
k = encode_if_requested(key)
|
115
|
+
@storage.delete_entry(key)
|
116
|
+
end
|
117
|
+
|
118
|
+
def nil_result?(result)
|
119
|
+
result.nil? or result[0].nil?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "eh/eh"
|
2
|
+
|
3
|
+
module Persistent
|
4
|
+
class StorageRAM
|
5
|
+
attr_accessor :storage
|
6
|
+
|
7
|
+
def initialize()
|
8
|
+
@storage = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def save_key_value_pair(serialized_key, serialized_value, timestamp = nil)
|
12
|
+
delete_entry(serialized_key)
|
13
|
+
time_entry = timestamp.nil? ? Time.now.to_s : timestamp.to_s
|
14
|
+
EH::retry!(:args => [serialized_key, serialized_value, time_entry]) do
|
15
|
+
@storage[serialized_key] = {:value => serialized_value, :timestamp => time_entry}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def lookup_key(serialized_key)
|
20
|
+
EH::retry!(:args => [serialized_key]) do
|
21
|
+
return [] if @storage[serialized_key].nil?
|
22
|
+
[@storage[serialized_key][:value], @storage[serialized_key][:timestamp]]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete_entry(serialized_key)
|
27
|
+
EH::retry!(:args => [serialized_key]) do
|
28
|
+
@storage.delete(serialized_key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def size
|
33
|
+
EH::retry!(:args => []) do
|
34
|
+
@storage.size
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def keys
|
39
|
+
EH::retry!(:args => []) do
|
40
|
+
return [] if @storage.keys == []
|
41
|
+
[@storage.keys]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def clear
|
46
|
+
EH::retry!(:args => []) do
|
47
|
+
@storage.clear
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/persistent-cache/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Ernst van Graan"]
|
6
|
+
gem.email = ["ernst.van.graan@hetzner.co.za"]
|
7
|
+
gem.description = %q{Persistent Cache limited to STORAGE_RAM}
|
8
|
+
gem.summary = %q{Persistent Cache limited to STORAGE_RAM}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "persistent-cache-ram"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Persistent::Cache::VERSION
|
17
|
+
gem.add_development_dependency 'rspec', '2.12.0'
|
18
|
+
gem.add_development_dependency 'simplecov'
|
19
|
+
gem.add_development_dependency 'simplecov-rcov'
|
20
|
+
# gem.add_development_dependency 'debugger'
|
21
|
+
gem.add_dependency 'eh'
|
22
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require "persistent-cache"
|
4
|
+
|
5
|
+
describe Persistent::Cache do
|
6
|
+
before :each do
|
7
|
+
@db_name = get_database_name
|
8
|
+
@mock_storage = double(Persistent::StorageRAM)
|
9
|
+
@test_key = "testkey"
|
10
|
+
@test_value = "testvalue"
|
11
|
+
FileUtils.rm_f(@db_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when constructing" do
|
15
|
+
it "should receive database connection details and create a StorageRAM instance if specified" do
|
16
|
+
@pcache = Persistent::Cache.new(@db_name, Persistent::Cache::STORAGE_RAM)
|
17
|
+
expect(@pcache.class).to eq(Persistent::Cache)
|
18
|
+
expect(@pcache.storage.is_a?(Persistent::StorageRAM)).to eq(true)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should raise an ArgumentError if storage details have not been provided" do
|
22
|
+
expect {
|
23
|
+
Persistent::Cache.new(nil)
|
24
|
+
}.to raise_error(ArgumentError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should remember the freshness interval if provided" do
|
28
|
+
@pcache = Persistent::Cache.new(@db_name, 123)
|
29
|
+
expect(@pcache.fresh).to eq(123)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should remember the storage details provided" do
|
33
|
+
@pcache = Persistent::Cache.new(@db_name, 123)
|
34
|
+
expect(@pcache.storage_details).to eq(@db_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should default the freshness interval to FRESH if not provided" do
|
38
|
+
@pcache = Persistent::Cache.new(@db_name)
|
39
|
+
expect(@pcache.fresh).to eq(Persistent::Cache::FRESH)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should raise an ArgumentError if an unknown storage type has been provided" do
|
43
|
+
expect {
|
44
|
+
Persistent::Cache.new(@db_name, 100, "unknown")
|
45
|
+
}.to raise_error(ArgumentError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "When assigning a value to a key" do
|
50
|
+
it "should ask the storage handler to first delete, then save the key/value pair" do
|
51
|
+
expect(Persistent::StorageRAM).to receive(:new).and_return(@mock_storage)
|
52
|
+
expect(@mock_storage).to receive(:delete_entry)
|
53
|
+
expect(@mock_storage).to receive(:save_key_value_pair).with(@test_key, @test_value, nil)
|
54
|
+
@pcache = Persistent::Cache.new(@db_name)
|
55
|
+
@pcache[@test_key] = @test_value
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should ask the storage handler to delete if the value is nil using []" do
|
59
|
+
expect(Persistent::StorageRAM).to receive(:new).and_return(@mock_storage)
|
60
|
+
expect(@mock_storage).to receive(:delete_entry).with(@test_key)
|
61
|
+
@pcache = Persistent::Cache.new(@db_name)
|
62
|
+
@pcache[@test_key] = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should ask the storage handler to delete if the value is nil using set()" do
|
66
|
+
expect(Persistent::StorageRAM).to receive(:new).and_return(@mock_storage)
|
67
|
+
expect(@mock_storage).to receive(:delete_entry).with(@test_key)
|
68
|
+
@pcache = Persistent::Cache.new(@db_name)
|
69
|
+
@pcache.set(@test_key, nil, Time.now)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should serialize the key and value for persistence" do
|
73
|
+
expect(Persistent::StorageRAM).to receive(:new).and_return(@mock_storage)
|
74
|
+
expect(@mock_storage).to receive(:delete_entry)
|
75
|
+
expect(@mock_storage).to receive(:save_key_value_pair).with(@test_key, @test_value, nil)
|
76
|
+
@pcache = Persistent::Cache.new(@db_name)
|
77
|
+
@pcache[@test_key] = @test_value
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should ask the storage handler to store the value, with a specific timestamp if specified" do
|
81
|
+
expect(Persistent::StorageRAM).to receive(:new).and_return(@mock_storage)
|
82
|
+
expect(@mock_storage).to receive(:delete_entry)
|
83
|
+
timestamp = Time.now - 100
|
84
|
+
expect(@mock_storage).to receive(:save_key_value_pair).with(@test_key, @test_value, timestamp)
|
85
|
+
@pcache = Persistent::Cache.new(@db_name)
|
86
|
+
@pcache.set(@test_key, @test_value, timestamp)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "When looking up a value given its key" do
|
91
|
+
it "should retrieve the value from storage using lookup_key and deserialize the value" do
|
92
|
+
expect(@mock_storage).to receive(:delete_entry)
|
93
|
+
expect(@mock_storage).to receive(:save_key_value_pair).with(@test_key, @test_value, nil)
|
94
|
+
expect(@mock_storage).to receive(:lookup_key).with(@test_key).and_return([@test_value, Time.now.to_s])
|
95
|
+
expect(Persistent::StorageRAM).to receive(:new).and_return(@mock_storage)
|
96
|
+
@pcache = Persistent::Cache.new(@db_name)
|
97
|
+
@pcache[@test_key] = @test_value
|
98
|
+
result = @pcache[@test_key]
|
99
|
+
expect(result).to eq(@test_value)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should return nil if a value exists but it not fresh" do
|
103
|
+
expect(@mock_storage).to receive(:delete_entry)
|
104
|
+
expect(@mock_storage).to receive(:lookup_key).with(@test_key).and_return([@test_value, (Time.now - Persistent::Cache::FRESH).to_s])
|
105
|
+
expect(Persistent::StorageRAM).to receive(:new).and_return(@mock_storage)
|
106
|
+
|
107
|
+
@pcache = Persistent::Cache.new(@db_name)
|
108
|
+
expect(@pcache[@test_key].nil?).to eq(true)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should remove from the cache an entry it encounters that is not fresh" do
|
112
|
+
expect(@mock_storage).to receive(:delete_entry)
|
113
|
+
expect(@mock_storage).to receive(:lookup_key).with(@test_key).and_return([@test_value, (Time.now - Persistent::Cache::FRESH).to_s])
|
114
|
+
expect(Persistent::StorageRAM).to receive(:new).and_return(@mock_storage)
|
115
|
+
|
116
|
+
@pcache = Persistent::Cache.new(@db_name)
|
117
|
+
@pcache[@test_key]
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should return nil if a key is not in the database" do
|
121
|
+
@pcache = Persistent::Cache.new(@db_name)
|
122
|
+
result = @pcache["thiskeydoesnotexist"]
|
123
|
+
expect(result.nil?).to eq(true)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should serialize the key for lookup" do
|
127
|
+
@pcache = Persistent::Cache.new(@db_name)
|
128
|
+
@pcache["testkey"] = "testvalue"
|
129
|
+
expect(@pcache).to receive(:lookup_key).with("testkey")
|
130
|
+
@pcache["testkey"]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "it should behave like a cache" do
|
135
|
+
it "should return the correct size" do
|
136
|
+
setup_cache
|
137
|
+
expect(@pcache.size).to eq(3)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should return the list of keys when asked" do
|
141
|
+
setup_cache
|
142
|
+
expect(@pcache.keys[0].sort).to eq(["two", "one", "three"].sort)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should allow iteration through each" do
|
146
|
+
setup_cache
|
147
|
+
test = []
|
148
|
+
@pcache.each do |key, value|
|
149
|
+
test << "#{key} => #{value}"
|
150
|
+
end
|
151
|
+
expect(test).to eq(["[\"one\", \"two\", \"three\"] => "])
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should delete all entries in the database when asked to clear" do
|
155
|
+
setup_cache
|
156
|
+
@pcache.clear
|
157
|
+
expect(@pcache.size).to eq(0)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should be able to handle multiple accesses to the same db" do
|
161
|
+
#pending "find a better way to test this, currently you need to spawn multiple rspecs running this test to hit the error if the busy_timeout is not specified"
|
162
|
+
pcache = Persistent::Cache.new("multidb")
|
163
|
+
pcache["multi_test"] = 0
|
164
|
+
|
165
|
+
threads = []
|
166
|
+
100.times do |i|
|
167
|
+
threads << Thread.new do
|
168
|
+
Thread.current['pcache'] = Persistent::Cache.new("multidb")
|
169
|
+
if (!Thread.current['pcache'].nil? && !Thread.current['pcache']["multi_test"].nil?)
|
170
|
+
Thread.current['pcache']["multi_test"] += 1
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
threads.each { |t| t.join }
|
175
|
+
|
176
|
+
p pcache["multi_test"]
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
def setup_cache
|
181
|
+
FileUtils.rm_f(@db_name)
|
182
|
+
@pcache = Persistent::Cache.new(@db_name)
|
183
|
+
@pcache["one"] = "value one"
|
184
|
+
@pcache["two"] = "value two"
|
185
|
+
@pcache["three"] = "value three"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "when needing to know if a key is in the cache" do
|
190
|
+
it "should return nil if the key is not present" do
|
191
|
+
setup_cache
|
192
|
+
expect(@pcache.key?(1)).to eql(nil)
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should return the key if the key is present" do
|
196
|
+
setup_cache
|
197
|
+
@pcache[1] = "1"
|
198
|
+
expect(@pcache.key?(1)).to eql(1)
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should return the key even if the entry is stale" do
|
202
|
+
setup_cache
|
203
|
+
@pcache[1] = "1"
|
204
|
+
sleep 2
|
205
|
+
expect(@pcache.key?(1)).to eql(1)
|
206
|
+
expect(@pcache[1]).to eql(nil)
|
207
|
+
end
|
208
|
+
|
209
|
+
def setup_cache(encoding = nil)
|
210
|
+
FileUtils.rm_f(@db_name)
|
211
|
+
@pcache = Persistent::Cache.new(@db_name, 1)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'rspec/mocks'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'simplecov'
|
5
|
+
require 'simplecov-rcov'
|
6
|
+
#require 'debugger'
|
7
|
+
|
8
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
9
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
10
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
11
|
+
# loaded once.
|
12
|
+
#
|
13
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
14
|
+
|
15
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'persistent-cache'))
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.run_all_when_everything_filtered = true
|
19
|
+
config.filter_run :focus
|
20
|
+
#config.expect_with(:rspec) { |c| c.syntax = :should }
|
21
|
+
|
22
|
+
# Run specs in random order to surface order dependencies. If you find an
|
23
|
+
# order dependency and want to debug it, you can fix the order by providing
|
24
|
+
# the seed, which is printed after each run.
|
25
|
+
# --seed 1234
|
26
|
+
config.order = 'random'
|
27
|
+
end
|
28
|
+
|
29
|
+
SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
|
30
|
+
SimpleCov.start do
|
31
|
+
add_filter "/spec/"
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_database_name
|
35
|
+
path = Tempfile.new("persistent-cache-spec-testdb").path
|
36
|
+
FileUtils.rm_f(path)
|
37
|
+
path
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require "persistent-cache/storage/storage_ram"
|
3
|
+
|
4
|
+
describe Persistent::StorageRAM do
|
5
|
+
before :each do
|
6
|
+
@test_key = "testkey"
|
7
|
+
@test_value = "testvalue"
|
8
|
+
@iut = Persistent::StorageRAM.new
|
9
|
+
end
|
10
|
+
|
11
|
+
context "when constructed" do
|
12
|
+
it "should have a storage hash in RAM" do
|
13
|
+
expect(@iut.storage.nil?).to eql(false)
|
14
|
+
expect(@iut.storage.instance_of?(Hash)).to eql(true)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when asked to store a key value pair" do
|
19
|
+
it "should store the key/value pair in RAM, with the current time as timestamp" do
|
20
|
+
start_time = Time.now - 1
|
21
|
+
@iut.save_key_value_pair(Marshal.dump(@test_key), Marshal.dump(@test_value))
|
22
|
+
result = @iut.lookup_key(Marshal.dump(@test_key))
|
23
|
+
expect(result[0]).to eql(Marshal.dump(@test_value))
|
24
|
+
test_time = Time.parse(result[1])
|
25
|
+
expect(test_time).to be > start_time
|
26
|
+
expect(test_time).to be < start_time + 600
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should store the key/value pair in RAM, with a timestamp specified" do
|
30
|
+
test_time = (Time.now - 2500)
|
31
|
+
@iut.save_key_value_pair(Marshal.dump(@test_key), Marshal.dump(@test_value), test_time)
|
32
|
+
result = @iut.lookup_key(Marshal.dump(@test_key))
|
33
|
+
expect(result.nil?).to eql(false)
|
34
|
+
expect(result[0]).to eql(Marshal.dump(@test_value))
|
35
|
+
time_retrieved = Time.parse(result[1])
|
36
|
+
expect(time_retrieved.to_s).to eql(test_time.to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should overwrite the existing key/value pair if they already exist" do
|
40
|
+
@iut.save_key_value_pair(Marshal.dump(@test_key), Marshal.dump(@test_value))
|
41
|
+
@iut.save_key_value_pair(Marshal.dump(@test_key), Marshal.dump("testvalue2"))
|
42
|
+
result = @iut.lookup_key(Marshal.dump(@test_key))
|
43
|
+
expect(result[0]).to eql(Marshal.dump("testvalue2"))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "When looking up a value given its key" do
|
48
|
+
it "should retrieve the value from RAM" do
|
49
|
+
@iut.save_key_value_pair(Marshal.dump(@test_key), Marshal.dump(@test_value))
|
50
|
+
result = @iut.lookup_key(Marshal.dump(@test_key))
|
51
|
+
expect(result[0]).to eql(Marshal.dump(@test_value))
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should retrieve the timestamp when the value was stored from RAM" do
|
55
|
+
now = Time.now.to_s
|
56
|
+
@iut.save_key_value_pair(Marshal.dump(@test_key), Marshal.dump(@test_value))
|
57
|
+
sleep 1
|
58
|
+
result = @iut.lookup_key(Marshal.dump(@test_key))
|
59
|
+
expect(result[1]).to eql(now)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return an empty array if a key is not in RAM" do
|
63
|
+
@iut.delete_entry(Marshal.dump(@test_key))
|
64
|
+
result = @iut.lookup_key(Marshal.dump(@test_key))
|
65
|
+
expect(result).to eql([])
|
66
|
+
expect(result[0]).to eql(nil)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when asked to delete an entry" do
|
71
|
+
it "should not raise an error if the entry is not present" do
|
72
|
+
@iut.delete_entry(Marshal.dump("shouldnotbepresent"))
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should delete the entry if it is present" do
|
76
|
+
@iut.save_key_value_pair(Marshal.dump(@test_key), Marshal.dump(@test_value))
|
77
|
+
result = @iut.lookup_key(Marshal.dump(@test_key))
|
78
|
+
expect(result[0]).to eql(Marshal.dump(@test_value))
|
79
|
+
@iut.delete_entry(Marshal.dump(@test_key))
|
80
|
+
result = @iut.lookup_key(Marshal.dump(@test_key))
|
81
|
+
expect(result).to eql([])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "when asked the size of the RAM database" do
|
86
|
+
it "should return 0 if the RAM database has no entries" do
|
87
|
+
expect(@iut.size).to eql(0)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should return the number of entries" do
|
91
|
+
populate_database(@iut)
|
92
|
+
expect(@iut.size).to eql(3)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "when asked for the keys in the RAM database" do
|
97
|
+
it "should return an empty array if there are no entries in the RAM database" do
|
98
|
+
expect(@iut.keys).to eql([])
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should return the keys in the RAM database" do
|
102
|
+
populate_database(@iut)
|
103
|
+
keys = @iut.keys.flatten
|
104
|
+
expect(keys.include?(Marshal.dump("one"))).to eql(true)
|
105
|
+
expect(keys.include?(Marshal.dump("two"))).to eql(true)
|
106
|
+
expect(keys.include?(Marshal.dump("three"))).to eql(true)
|
107
|
+
expect(@iut.size).to eql(3)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should return the keys in an array, with each key in its own sub-array" do
|
111
|
+
populate_database(@iut)
|
112
|
+
found = false
|
113
|
+
test = Marshal.dump("one")
|
114
|
+
found = true if (@iut.keys[0][0] == test or @iut.keys[0][1] == test or @iut.keys[0][2] == test)
|
115
|
+
expect(found).to eql(true)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "when asked to clear the RAM database" do
|
120
|
+
it "should delete all entries in RAM" do
|
121
|
+
populate_database(@iut)
|
122
|
+
@iut.clear
|
123
|
+
expect(@iut.size).to eql(0)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def populate_database(iut)
|
128
|
+
iut.save_key_value_pair(Marshal.dump("one"), Marshal.dump("one"))
|
129
|
+
iut.save_key_value_pair(Marshal.dump("two"), Marshal.dump("two"))
|
130
|
+
iut.save_key_value_pair(Marshal.dump("three"), Marshal.dump("three"))
|
131
|
+
end
|
132
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: persistent-cache-ram
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ernst van Graan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.12.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.12.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: simplecov
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov-rcov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: eh
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Persistent Cache limited to STORAGE_RAM
|
70
|
+
email:
|
71
|
+
- ernst.van.graan@hetzner.co.za
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/persistent-cache.rb
|
82
|
+
- lib/persistent-cache/storage/storage_ram.rb
|
83
|
+
- lib/persistent-cache/version.rb
|
84
|
+
- persistent-cache-ram.gemspec
|
85
|
+
- spec/persistent-cache_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
- spec/storage/storage_ram_spec.rb
|
88
|
+
homepage: ''
|
89
|
+
licenses: []
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.4.8
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Persistent Cache limited to STORAGE_RAM
|
111
|
+
test_files:
|
112
|
+
- spec/persistent-cache_spec.rb
|
113
|
+
- spec/spec_helper.rb
|
114
|
+
- spec/storage/storage_ram_spec.rb
|
115
|
+
has_rdoc:
|