xlymian-redis-store 0.3.8
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/.gitignore +7 -0
- data/Gemfile +13 -0
- data/MIT-LICENSE +20 -0
- data/README.md +105 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/lib/cache/merb/redis_store.rb +63 -0
- data/lib/cache/rails/redis_store.rb +109 -0
- data/lib/cache/sinatra/redis_store.rb +111 -0
- data/lib/rack/cache/redis_entitystore.rb +51 -0
- data/lib/rack/cache/redis_metastore.rb +42 -0
- data/lib/rack/session/merb.rb +32 -0
- data/lib/rack/session/redis.rb +81 -0
- data/lib/redis-store.rb +32 -0
- data/lib/redis/distributed_marshaled_redis.rb +10 -0
- data/lib/redis/marshaled_redis.rb +33 -0
- data/lib/redis/redis_factory.rb +28 -0
- data/redis-store.gemspec +80 -0
- data/spec/cache/merb/redis_store_spec.rb +145 -0
- data/spec/cache/rails/redis_store_spec.rb +173 -0
- data/spec/cache/sinatra/redis_store_spec.rb +194 -0
- data/spec/config/master.conf +171 -0
- data/spec/config/single.conf +171 -0
- data/spec/config/slave.conf +171 -0
- data/spec/rack/cache/entitystore/pony.jpg +0 -0
- data/spec/rack/cache/entitystore/redis_spec.rb +120 -0
- data/spec/rack/cache/metastore/redis_spec.rb +257 -0
- data/spec/rack/session/redis_spec.rb +238 -0
- data/spec/redis/distributed_marshaled_redis_spec.rb +35 -0
- data/spec/redis/marshaled_redis_spec.rb +54 -0
- data/spec/redis/redis_factory_spec.rb +34 -0
- data/spec/spec_helper.rb +16 -0
- data/tasks/redis.tasks.rb +70 -0
- metadata +103 -0
@@ -0,0 +1,257 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/../../../spec_helper")
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Cache
|
5
|
+
class MetaStore
|
6
|
+
# courtesy of http://github.com/rtomayko/rack-cache team
|
7
|
+
describe "Rack::Cache::MetaStore::Redis" do
|
8
|
+
before :each do
|
9
|
+
@store = Rack::Cache::MetaStore::Redis.resolve uri("redis://127.0.0.1")
|
10
|
+
@entity_store = Rack::Cache::EntityStore::Redis.resolve uri("redis://127.0.0.1:6380")
|
11
|
+
@request = mock_request('/', {})
|
12
|
+
@response = mock_response(200, {}, ['hello world'])
|
13
|
+
end
|
14
|
+
|
15
|
+
after :each do
|
16
|
+
@store.cache.flush_all
|
17
|
+
@entity_store.cache.flush_all
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have the class referenced by homonym constant" do
|
21
|
+
Rack::Cache::MetaStore::REDIS.should be(Rack::Cache::MetaStore::Redis)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should resolve the connection uri" do
|
25
|
+
cache = Rack::Cache::MetaStore::Redis.resolve(uri("redis://127.0.0.1")).cache
|
26
|
+
cache.should be_kind_of(::MarshaledRedis)
|
27
|
+
cache.host.should == "127.0.0.1"
|
28
|
+
cache.port.should == 6379
|
29
|
+
cache.db.should == 0
|
30
|
+
|
31
|
+
cache = Rack::Cache::MetaStore::Redis.resolve(uri("redis://127.0.0.1:6380")).cache
|
32
|
+
cache.port.should == 6380
|
33
|
+
|
34
|
+
cache = Rack::Cache::MetaStore::Redis.resolve(uri("redis://127.0.0.1/13")).cache
|
35
|
+
cache.db.should == 13
|
36
|
+
end
|
37
|
+
|
38
|
+
# Low-level implementation methods ===========================================
|
39
|
+
|
40
|
+
it 'writes a list of negotation tuples with #write' do
|
41
|
+
lambda { @store.write('/test', [[{}, {}]]) }.should_not raise_error
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'reads a list of negotation tuples with #read' do
|
45
|
+
@store.write('/test', [[{},{}],[{},{}]])
|
46
|
+
tuples = @store.read('/test')
|
47
|
+
tuples.should == [ [{},{}], [{},{}] ]
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'reads an empty list with #read when nothing cached at key' do
|
51
|
+
@store.read('/nothing').should be_empty
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'removes entries for key with #purge' do
|
55
|
+
@store.write('/test', [[{},{}]])
|
56
|
+
@store.read('/test').should_not be_empty
|
57
|
+
|
58
|
+
@store.purge('/test')
|
59
|
+
@store.read('/test').should be_empty
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'succeeds when purging non-existing entries' do
|
63
|
+
@store.read('/test').should be_empty
|
64
|
+
@store.purge('/test')
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns nil from #purge' do
|
68
|
+
@store.write('/test', [[{},{}]])
|
69
|
+
@store.purge('/test').should be_nil
|
70
|
+
@store.read('/test').should == []
|
71
|
+
end
|
72
|
+
|
73
|
+
%w[/test http://example.com:8080/ /test?x=y /test?x=y&p=q].each do |key|
|
74
|
+
it "can read and write key: '#{key}'" do
|
75
|
+
lambda { @store.write(key, [[{},{}]]) }.should_not raise_error
|
76
|
+
@store.read(key).should == [[{},{}]]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "can read and write fairly large keys" do
|
81
|
+
key = "b" * 4096
|
82
|
+
lambda { @store.write(key, [[{},{}]]) }.should_not raise_error
|
83
|
+
@store.read(key).should == [[{},{}]]
|
84
|
+
end
|
85
|
+
|
86
|
+
it "allows custom cache keys from block" do
|
87
|
+
request = mock_request('/test', {})
|
88
|
+
request.env['rack-cache.cache_key'] =
|
89
|
+
lambda { |request| request.path_info.reverse }
|
90
|
+
@store.cache_key(request).should == 'tset/'
|
91
|
+
end
|
92
|
+
|
93
|
+
it "allows custom cache keys from class" do
|
94
|
+
request = mock_request('/test', {})
|
95
|
+
request.env['rack-cache.cache_key'] = Class.new do
|
96
|
+
def self.call(request); request.path_info.reverse end
|
97
|
+
end
|
98
|
+
@store.cache_key(request).should == 'tset/'
|
99
|
+
end
|
100
|
+
|
101
|
+
# Abstract methods ===========================================================
|
102
|
+
|
103
|
+
# Stores an entry for the given request args, returns a url encoded cache key
|
104
|
+
# for the request.
|
105
|
+
define_method :store_simple_entry do |*request_args|
|
106
|
+
path, headers = request_args
|
107
|
+
@request = mock_request(path || '/test', headers || {})
|
108
|
+
@response = mock_response(200, {'Cache-Control' => 'max-age=420'}, ['test'])
|
109
|
+
body = @response.body
|
110
|
+
cache_key = @store.store(@request, @response, @entity_store)
|
111
|
+
@response.body.should_not equal(body)
|
112
|
+
cache_key
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'stores a cache entry' do
|
116
|
+
cache_key = store_simple_entry
|
117
|
+
@store.read(cache_key).should_not be_empty
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'sets the X-Content-Digest response header before storing' do
|
121
|
+
cache_key = store_simple_entry
|
122
|
+
req, res = @store.read(cache_key).first
|
123
|
+
res['X-Content-Digest'].should == 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'finds a stored entry with #lookup' do
|
127
|
+
store_simple_entry
|
128
|
+
response = @store.lookup(@request, @entity_store)
|
129
|
+
response.should_not be_nil
|
130
|
+
response.should be_kind_of(Rack::Cache::Response)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'does not find an entry with #lookup when none exists' do
|
134
|
+
req = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
|
135
|
+
@store.lookup(req, @entity_store).should be_nil
|
136
|
+
end
|
137
|
+
|
138
|
+
it "canonizes urls for cache keys" do
|
139
|
+
store_simple_entry(path='/test?x=y&p=q')
|
140
|
+
|
141
|
+
hits_req = mock_request(path, {})
|
142
|
+
miss_req = mock_request('/test?p=x', {})
|
143
|
+
|
144
|
+
@store.lookup(hits_req, @entity_store).should_not be_nil
|
145
|
+
@store.lookup(miss_req, @entity_store).should be_nil
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'does not find an entry with #lookup when the body does not exist' do
|
149
|
+
store_simple_entry
|
150
|
+
@response.headers['X-Content-Digest'].should_not be_nil
|
151
|
+
@entity_store.purge(@response.headers['X-Content-Digest'])
|
152
|
+
@store.lookup(@request, @entity_store).should be_nil
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'restores response headers properly with #lookup' do
|
156
|
+
store_simple_entry
|
157
|
+
response = @store.lookup(@request, @entity_store)
|
158
|
+
response.headers.should == @response.headers.merge('Content-Length' => '4')
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'restores response body from entity store with #lookup' do
|
162
|
+
store_simple_entry
|
163
|
+
response = @store.lookup(@request, @entity_store)
|
164
|
+
body = '' ; response.body.each {|p| body << p}
|
165
|
+
body.should == 'test'
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'invalidates meta and entity store entries with #invalidate' do
|
169
|
+
store_simple_entry
|
170
|
+
@store.invalidate(@request, @entity_store)
|
171
|
+
response = @store.lookup(@request, @entity_store)
|
172
|
+
response.should be_kind_of(Rack::Cache::Response)
|
173
|
+
response.should_not be_fresh
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'succeeds quietly when #invalidate called with no matching entries' do
|
177
|
+
req = mock_request('/test', {})
|
178
|
+
@store.invalidate(req, @entity_store)
|
179
|
+
@store.lookup(@request, @entity_store).should be_nil
|
180
|
+
end
|
181
|
+
|
182
|
+
# Vary =======================================================================
|
183
|
+
|
184
|
+
it 'does not return entries that Vary with #lookup' do
|
185
|
+
req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
|
186
|
+
req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
|
187
|
+
res = mock_response(200, {'Vary' => 'Foo Bar'}, ['test'])
|
188
|
+
@store.store(req1, res, @entity_store)
|
189
|
+
|
190
|
+
@store.lookup(req2, @entity_store).should be_nil
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'stores multiple responses for each Vary combination' do
|
194
|
+
req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
|
195
|
+
res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1'])
|
196
|
+
key = @store.store(req1, res1, @entity_store)
|
197
|
+
|
198
|
+
req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
|
199
|
+
res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2'])
|
200
|
+
@store.store(req2, res2, @entity_store)
|
201
|
+
|
202
|
+
req3 = mock_request('/test', {'HTTP_FOO' => 'Baz', 'HTTP_BAR' => 'Boom'})
|
203
|
+
res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3'])
|
204
|
+
@store.store(req3, res3, @entity_store)
|
205
|
+
|
206
|
+
slurp(@store.lookup(req3, @entity_store).body).should == 'test 3'
|
207
|
+
slurp(@store.lookup(req1, @entity_store).body).should == 'test 1'
|
208
|
+
slurp(@store.lookup(req2, @entity_store).body).should == 'test 2'
|
209
|
+
|
210
|
+
@store.read(key).length.should == 3
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'overwrites non-varying responses with #store' do
|
214
|
+
req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
|
215
|
+
res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1'])
|
216
|
+
key = @store.store(req1, res1, @entity_store)
|
217
|
+
slurp(@store.lookup(req1, @entity_store).body).should == 'test 1'
|
218
|
+
|
219
|
+
req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
|
220
|
+
res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2'])
|
221
|
+
@store.store(req2, res2, @entity_store)
|
222
|
+
slurp(@store.lookup(req2, @entity_store).body).should == 'test 2'
|
223
|
+
|
224
|
+
req3 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
|
225
|
+
res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3'])
|
226
|
+
@store.store(req3, res3, @entity_store)
|
227
|
+
slurp(@store.lookup(req1, @entity_store).body).should == 'test 3'
|
228
|
+
|
229
|
+
@store.read(key).length.should == 2
|
230
|
+
end
|
231
|
+
|
232
|
+
# Helper Methods =============================================================
|
233
|
+
|
234
|
+
define_method :mock_request do |uri,opts|
|
235
|
+
env = Rack::MockRequest.env_for(uri, opts || {})
|
236
|
+
Rack::Cache::Request.new(env)
|
237
|
+
end
|
238
|
+
|
239
|
+
define_method :mock_response do |status,headers,body|
|
240
|
+
headers ||= {}
|
241
|
+
body = Array(body).compact
|
242
|
+
Rack::Cache::Response.new(status, headers, body)
|
243
|
+
end
|
244
|
+
|
245
|
+
define_method :slurp do |body|
|
246
|
+
buf = ''
|
247
|
+
body.each {|part| buf << part }
|
248
|
+
buf
|
249
|
+
end
|
250
|
+
|
251
|
+
define_method :uri do |uri|
|
252
|
+
URI.parse uri
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/../../spec_helper")
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Session
|
5
|
+
describe "Rack::Session::Redis" do
|
6
|
+
before(:each) do
|
7
|
+
@session_key = Rack::Session::Redis::DEFAULT_OPTIONS[:key]
|
8
|
+
@session_match = /#{@session_key}=[0-9a-fA-F]+;/
|
9
|
+
@incrementor = lambda do |env|
|
10
|
+
env["rack.session"]["counter"] ||= 0
|
11
|
+
env["rack.session"]["counter"] += 1
|
12
|
+
Rack::Response.new(env["rack.session"].inspect).to_a
|
13
|
+
end
|
14
|
+
@drop_session = proc do |env|
|
15
|
+
env['rack.session.options'][:drop] = true
|
16
|
+
@incrementor.call(env)
|
17
|
+
end
|
18
|
+
@renew_session = proc do |env|
|
19
|
+
env['rack.session.options'][:renew] = true
|
20
|
+
@incrementor.call(env)
|
21
|
+
end
|
22
|
+
@defer_session = proc do |env|
|
23
|
+
env['rack.session.options'][:defer] = true
|
24
|
+
@incrementor.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should specify connection params" do
|
29
|
+
pool = Rack::Session::Redis.new(@incrementor, :redis_server => "localhost:6380/1").pool
|
30
|
+
pool.should be_kind_of(MarshaledRedis)
|
31
|
+
pool.host.should == "localhost"
|
32
|
+
pool.port.should == 6380
|
33
|
+
pool.db.should == 1
|
34
|
+
|
35
|
+
pool = Rack::Session::Redis.new(@incrementor, :redis_server => ["localhost:6379", "localhost:6380"]).pool
|
36
|
+
pool.should be_kind_of(DistributedMarshaledRedis)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "creates a new cookie" do
|
40
|
+
pool = Rack::Session::Redis.new(@incrementor)
|
41
|
+
res = Rack::MockRequest.new(pool).get("/")
|
42
|
+
res["Set-Cookie"].should match(/#{@session_key}=/)
|
43
|
+
res.body.should == '{"counter"=>1}'
|
44
|
+
end
|
45
|
+
|
46
|
+
it "determines session from a cookie" do
|
47
|
+
pool = Rack::Session::Redis.new(@incrementor)
|
48
|
+
req = Rack::MockRequest.new(pool)
|
49
|
+
res = req.get("/")
|
50
|
+
cookie = res["Set-Cookie"]
|
51
|
+
req.get("/", "HTTP_COOKIE" => cookie).
|
52
|
+
body.should == '{"counter"=>2}'
|
53
|
+
req.get("/", "HTTP_COOKIE" => cookie).
|
54
|
+
body.should == '{"counter"=>3}'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "survives nonexistant cookies" do
|
58
|
+
bad_cookie = "rack.session=blarghfasel"
|
59
|
+
pool = Rack::Session::Redis.new(@incrementor)
|
60
|
+
res = Rack::MockRequest.new(pool).
|
61
|
+
get("/", "HTTP_COOKIE" => bad_cookie)
|
62
|
+
res.body.should == '{"counter"=>1}'
|
63
|
+
cookie = res["Set-Cookie"][@session_match]
|
64
|
+
cookie.should_not match(/#{bad_cookie}/)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should maintain freshness" do
|
68
|
+
pool = Rack::Session::Redis.new(@incrementor, :expire_after => 3)
|
69
|
+
res = Rack::MockRequest.new(pool).get('/')
|
70
|
+
res.body.should include('"counter"=>1')
|
71
|
+
cookie = res["Set-Cookie"]
|
72
|
+
res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
|
73
|
+
res["Set-Cookie"].should == cookie
|
74
|
+
res.body.should include('"counter"=>2')
|
75
|
+
puts 'Sleeping to expire session' if $DEBUG
|
76
|
+
sleep 4
|
77
|
+
res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
|
78
|
+
res["Set-Cookie"].should_not == cookie
|
79
|
+
res.body.should include('"counter"=>1')
|
80
|
+
end
|
81
|
+
|
82
|
+
it "deletes cookies with :drop option" do
|
83
|
+
pool = Rack::Session::Redis.new(@incrementor)
|
84
|
+
req = Rack::MockRequest.new(pool)
|
85
|
+
drop = Rack::Utils::Context.new(pool, @drop_session)
|
86
|
+
dreq = Rack::MockRequest.new(drop)
|
87
|
+
|
88
|
+
res0 = req.get("/")
|
89
|
+
session = (cookie = res0["Set-Cookie"])[@session_match]
|
90
|
+
res0.body.should == '{"counter"=>1}'
|
91
|
+
|
92
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
93
|
+
res1["Set-Cookie"][@session_match].should == session
|
94
|
+
res1.body.should == '{"counter"=>2}'
|
95
|
+
|
96
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
97
|
+
res2["Set-Cookie"].should be_nil
|
98
|
+
res2.body.should == '{"counter"=>3}'
|
99
|
+
|
100
|
+
res3 = req.get("/", "HTTP_COOKIE" => cookie)
|
101
|
+
res3["Set-Cookie"][@session_match].should_not == session
|
102
|
+
res3.body.should == '{"counter"=>1}'
|
103
|
+
end
|
104
|
+
|
105
|
+
it "provides new session id with :renew option" do
|
106
|
+
pool = Rack::Session::Redis.new(@incrementor)
|
107
|
+
req = Rack::MockRequest.new(pool)
|
108
|
+
renew = Rack::Utils::Context.new(pool, @renew_session)
|
109
|
+
rreq = Rack::MockRequest.new(renew)
|
110
|
+
|
111
|
+
res0 = req.get("/")
|
112
|
+
session = (cookie = res0["Set-Cookie"])[@session_match]
|
113
|
+
res0.body.should == '{"counter"=>1}'
|
114
|
+
|
115
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
116
|
+
res1["Set-Cookie"][@session_match].should == session
|
117
|
+
res1.body.should == '{"counter"=>2}'
|
118
|
+
|
119
|
+
res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
|
120
|
+
new_cookie = res2["Set-Cookie"]
|
121
|
+
new_session = new_cookie[@session_match]
|
122
|
+
new_session.should_not == session
|
123
|
+
res2.body.should == '{"counter"=>3}'
|
124
|
+
|
125
|
+
res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
|
126
|
+
res3["Set-Cookie"][@session_match].should == new_session
|
127
|
+
res3.body.should == '{"counter"=>4}'
|
128
|
+
end
|
129
|
+
|
130
|
+
specify "omits cookie with :defer option" do
|
131
|
+
pool = Rack::Session::Redis.new(@incrementor)
|
132
|
+
req = Rack::MockRequest.new(pool)
|
133
|
+
defer = Rack::Utils::Context.new(pool, @defer_session)
|
134
|
+
dreq = Rack::MockRequest.new(defer)
|
135
|
+
|
136
|
+
res0 = req.get("/")
|
137
|
+
session = (cookie = res0["Set-Cookie"])[@session_match]
|
138
|
+
res0.body.should == '{"counter"=>1}'
|
139
|
+
|
140
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
141
|
+
res1["Set-Cookie"][@session_match].should == session
|
142
|
+
res1.body.should == '{"counter"=>2}'
|
143
|
+
|
144
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
145
|
+
res2["Set-Cookie"].should be_nil
|
146
|
+
res2.body.should == '{"counter"=>3}'
|
147
|
+
|
148
|
+
res3 = req.get("/", "HTTP_COOKIE" => cookie)
|
149
|
+
res3["Set-Cookie"][@session_match].should == session
|
150
|
+
res3.body.should == '{"counter"=>4}'
|
151
|
+
end
|
152
|
+
|
153
|
+
# anyone know how to do this better?
|
154
|
+
specify "multithread: should cleanly merge sessions" do
|
155
|
+
next unless $DEBUG
|
156
|
+
warn 'Running multithread test for Session::Redis'
|
157
|
+
pool = Rack::Session::Redis.new(@incrementor)
|
158
|
+
req = Rack::MockRequest.new(pool)
|
159
|
+
|
160
|
+
res = req.get('/')
|
161
|
+
res.body.should == '{"counter"=>1}'
|
162
|
+
cookie = res["Set-Cookie"]
|
163
|
+
sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
|
164
|
+
|
165
|
+
delta_incrementor = lambda do |env|
|
166
|
+
# emulate disconjoinment of threading
|
167
|
+
env['rack.session'] = env['rack.session'].dup
|
168
|
+
Thread.stop
|
169
|
+
env['rack.session'][(Time.now.usec*rand).to_i] = true
|
170
|
+
@incrementor.call(env)
|
171
|
+
end
|
172
|
+
tses = Rack::Utils::Context.new pool, delta_incrementor
|
173
|
+
treq = Rack::MockRequest.new(tses)
|
174
|
+
tnum = rand(7).to_i+5
|
175
|
+
r = Array.new(tnum) do
|
176
|
+
Thread.new(treq) do |run|
|
177
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
178
|
+
end
|
179
|
+
end.reverse.map{|t| t.run.join.value }
|
180
|
+
r.each do |res|
|
181
|
+
res['Set-Cookie'].should == cookie
|
182
|
+
res.body.should include('"counter"=>2')
|
183
|
+
end
|
184
|
+
|
185
|
+
session = pool.pool.get(sess_id)
|
186
|
+
session.size.should == tnum+1 # counter
|
187
|
+
session['counter'].should == 2 # meeeh
|
188
|
+
|
189
|
+
tnum = rand(7).to_i+5
|
190
|
+
r = Array.new(tnum) do |i|
|
191
|
+
delta_time = proc do |env|
|
192
|
+
env['rack.session'][i] = Time.now
|
193
|
+
Thread.stop
|
194
|
+
env['rack.session'] = env['rack.session'].dup
|
195
|
+
env['rack.session'][i] -= Time.now
|
196
|
+
@incrementor.call(env)
|
197
|
+
end
|
198
|
+
app = Rack::Utils::Context.new pool, time_delta
|
199
|
+
req = Rack::MockRequest.new app
|
200
|
+
Thread.new(req) do |run|
|
201
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
202
|
+
end
|
203
|
+
end.reverse.map{|t| t.run.join.value }
|
204
|
+
r.each do |res|
|
205
|
+
res['Set-Cookie'].should == cookie
|
206
|
+
res.body.should include('"counter"=>3')
|
207
|
+
end
|
208
|
+
|
209
|
+
session = pool.pool.get(sess_id)
|
210
|
+
session.size.should == tnum+1
|
211
|
+
session['counter'].should == 3
|
212
|
+
|
213
|
+
drop_counter = proc do |env|
|
214
|
+
env['rack.session'].delete 'counter'
|
215
|
+
env['rack.session']['foo'] = 'bar'
|
216
|
+
[200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
|
217
|
+
end
|
218
|
+
tses = Rack::Utils::Context.new pool, drop_counter
|
219
|
+
treq = Rack::MockRequest.new(tses)
|
220
|
+
tnum = rand(7).to_i+5
|
221
|
+
r = Array.new(tnum) do
|
222
|
+
Thread.new(treq) do |run|
|
223
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
224
|
+
end
|
225
|
+
end.reverse.map{|t| t.run.join.value }
|
226
|
+
r.each do |res|
|
227
|
+
res['Set-Cookie'].should == cookie
|
228
|
+
res.body.should include('"foo"=>"bar"')
|
229
|
+
end
|
230
|
+
|
231
|
+
session = pool.pool.get(sess_id)
|
232
|
+
session.size.should == r.size+1
|
233
|
+
session['counter'].should be_nil
|
234
|
+
session['foo'].should == 'bar'
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|