redis-actionpack-json 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.gitignore +15 -0
  2. data/.travis.yml +7 -0
  3. data/CHANGELOG +450 -0
  4. data/README.md +23 -0
  5. data/redis-actionpack-json/.gitignore +4 -0
  6. data/redis-actionpack-json/Gemfile +6 -0
  7. data/redis-actionpack-json/MIT-LICENSE +20 -0
  8. data/redis-actionpack-json/Rakefile +8 -0
  9. data/redis-actionpack-json/lib/action_dispatch/middleware/session/redis_store_json.rb +24 -0
  10. data/redis-actionpack-json/lib/redis-actionpack-json.rb +4 -0
  11. data/redis-actionpack-json/lib/redis/actionpack/version.rb +5 -0
  12. data/redis-actionpack-json/redis-actionpack-json.gemspec +32 -0
  13. data/redis-actionpack-json/test/dummy/.gitignore +1 -0
  14. data/redis-actionpack-json/test/dummy/Rakefile +7 -0
  15. data/redis-actionpack-json/test/dummy/app/controllers/test_controller.rb +37 -0
  16. data/redis-actionpack-json/test/dummy/config.ru +4 -0
  17. data/redis-actionpack-json/test/dummy/config/application.rb +29 -0
  18. data/redis-actionpack-json/test/dummy/config/boot.rb +10 -0
  19. data/redis-actionpack-json/test/dummy/config/environment.rb +5 -0
  20. data/redis-actionpack-json/test/dummy/config/initializers/secret_token.rb +7 -0
  21. data/redis-actionpack-json/test/dummy/config/initializers/session_store.rb +11 -0
  22. data/redis-actionpack-json/test/dummy/config/routes.rb +3 -0
  23. data/redis-actionpack-json/test/dummy/script/rails +6 -0
  24. data/redis-actionpack-json/test/fixtures/session_autoload_test/session_autoload_test/foo.rb +10 -0
  25. data/redis-actionpack-json/test/integration/redis_store_integration_test.rb +130 -0
  26. data/redis-actionpack-json/test/integration/redis_store_json_integration_test.rb +130 -0
  27. data/redis-actionpack-json/test/redis/actionpack/version_test.rb +7 -0
  28. data/redis-actionpack-json/test/test_helper.rb +23 -0
  29. data/redis-rack-json/.gitignore +5 -0
  30. data/redis-rack-json/Gemfile +5 -0
  31. data/redis-rack-json/MIT-LICENSE +20 -0
  32. data/redis-rack-json/Rakefile +8 -0
  33. data/redis-rack-json/lib/rack/session/redis.rb +69 -0
  34. data/redis-rack-json/lib/redis-rack-json.rb +3 -0
  35. data/redis-rack-json/lib/redis/rack/version.rb +6 -0
  36. data/redis-rack-json/redis-rack-json.gemspec +29 -0
  37. data/redis-rack-json/test/rack/session/redis_test.rb +289 -0
  38. data/redis-rack-json/test/redis/rack/version_test.rb +7 -0
  39. data/redis-rack-json/test/test_helper.rb +7 -0
  40. data/redis-store-json/Gemfile +4 -0
  41. data/redis-store-json/MIT-LICENSE +20 -0
  42. data/redis-store-json/Rakefile +7 -0
  43. data/redis-store-json/lib/redis-store-json.rb +11 -0
  44. data/redis-store-json/lib/redis/distributed_store.rb +46 -0
  45. data/redis-store-json/lib/redis/factory.rb +41 -0
  46. data/redis-store-json/lib/redis/store.rb +47 -0
  47. data/redis-store-json/lib/redis/store/interface.rb +21 -0
  48. data/redis-store-json/lib/redis/store/namespace.rb +66 -0
  49. data/redis-store-json/lib/redis/store/strategy.rb +60 -0
  50. data/redis-store-json/lib/redis/store/strategy/json.rb +49 -0
  51. data/redis-store-json/lib/redis/store/strategy/json_session.rb +67 -0
  52. data/redis-store-json/lib/redis/store/strategy/marshal.rb +16 -0
  53. data/redis-store-json/lib/redis/store/strategy/yaml.rb +16 -0
  54. data/redis-store-json/lib/redis/store/ttl.rb +37 -0
  55. data/redis-store-json/lib/redis/store/version.rb +5 -0
  56. data/redis-store-json/lib/tasks/redis.tasks.rb +167 -0
  57. data/redis-store-json/redis-store-json.gemspec +29 -0
  58. data/redis-store-json/test/config/node-one.conf +46 -0
  59. data/redis-store-json/test/config/node-two.conf +46 -0
  60. data/redis-store-json/test/config/redis.conf +46 -0
  61. data/redis-store-json/test/redis/distributed_store_test.rb +53 -0
  62. data/redis-store-json/test/redis/factory_test.rb +120 -0
  63. data/redis-store-json/test/redis/store/interface_test.rb +27 -0
  64. data/redis-store-json/test/redis/store/namespace_test.rb +103 -0
  65. data/redis-store-json/test/redis/store/strategy/json_session_test.rb +160 -0
  66. data/redis-store-json/test/redis/store/strategy/json_test.rb +108 -0
  67. data/redis-store-json/test/redis/store/strategy/marshal_test.rb +121 -0
  68. data/redis-store-json/test/redis/store/strategy/yaml_test.rb +105 -0
  69. data/redis-store-json/test/redis/store/ttl_test.rb +107 -0
  70. data/redis-store-json/test/redis/store/version_test.rb +7 -0
  71. data/redis-store-json/test/redis/store_test.rb +45 -0
  72. data/redis-store-json/test/test_helper.rb +22 -0
  73. metadata +279 -0
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ describe Redis::ActionPack::VERSION do
4
+ it "must be equal to 3.2.3" do
5
+ Redis::ActionPack::VERSION.must_equal '3.2.3'
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ require 'minitest/autorun'
2
+ require 'active_support/core_ext/numeric/time'
3
+
4
+ ENV["RAILS_ENV"] = "test"
5
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
6
+ require "rails/test_help"
7
+
8
+ Rails.backtrace_cleaner.remove_silencers!
9
+
10
+ def with_autoload_path(path)
11
+ path = File.join(File.dirname(__FILE__), "fixtures", path)
12
+ if ActiveSupport::Dependencies.autoload_paths.include?(path)
13
+ yield
14
+ else
15
+ begin
16
+ ActiveSupport::Dependencies.autoload_paths << path
17
+ yield
18
+ ensure
19
+ ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
20
+ ActiveSupport::Dependencies.clear
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tmp/*
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+ gemspec
3
+
4
+ gem 'redis-store', '~> 1.1.0', :path => File.expand_path('../../redis-store', __FILE__)
5
+ gem 'SystemTimer', :platform => :mri_18
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 - 2011 Luca Guidi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+ require 'rake'
4
+ require 'bundler/gem_tasks'
5
+
6
+ load 'tasks/redis.tasks.rb'
7
+ task :default => 'redis:test:suite'
8
+
@@ -0,0 +1,69 @@
1
+ require 'rack/session/abstract/id'
2
+ require 'redis-store-json'
3
+ require 'thread'
4
+
5
+ module Rack
6
+ module Session
7
+ class Redis < Abstract::ID
8
+ attr_reader :mutex, :pool
9
+
10
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
11
+ :redis_server => 'redis://127.0.0.1:6379/0/rack:session'
12
+
13
+ def initialize(app, options = {})
14
+ super
15
+
16
+ @mutex = Mutex.new
17
+ @pool = ::Redis::Factory.create @default_options
18
+ end
19
+
20
+ def generate_sid
21
+ loop do
22
+ sid = super
23
+ break sid unless @pool.get(sid)
24
+ end
25
+ end
26
+
27
+ def get_session(env, sid)
28
+ with_lock(env, [nil, {}]) do
29
+ unless sid and session = @pool.get(sid)
30
+ sid, session = generate_sid, {}
31
+ unless /^OK/ =~ @pool.set(sid, session)
32
+ raise "Session collision on '#{sid.inspect}'"
33
+ end
34
+ end
35
+ [sid, session]
36
+ end
37
+ end
38
+
39
+ def set_session(env, session_id, new_session, options)
40
+ with_lock(env, false) do
41
+ @pool.set session_id, new_session, options
42
+ session_id
43
+ end
44
+ end
45
+
46
+ def destroy_session(env, session_id, options)
47
+ with_lock(env) do
48
+ @pool.del(session_id)
49
+ generate_sid unless options[:drop]
50
+ end
51
+ end
52
+
53
+ def with_lock(env, default=nil)
54
+ @mutex.lock if env['rack.multithread']
55
+ yield
56
+ rescue Errno::ECONNREFUSED
57
+ if $VERBOSE
58
+ warn "#{self} is unable to find Redis server."
59
+ warn $!.inspect
60
+ end
61
+ default
62
+ ensure
63
+ @mutex.unlock if @mutex.locked?
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,3 @@
1
+ require 'redis-store-json'
2
+ require 'redis/rack/version'
3
+ require 'rack/session/redis'
@@ -0,0 +1,6 @@
1
+ class Redis
2
+ module Rack
3
+ VERSION = '1.5.2'
4
+ end
5
+ end
6
+
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'redis/rack/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'redis-rack-json'
7
+ s.version = Redis::Rack::VERSION
8
+ s.authors = ["Nathan Tsoi", "Luca Guidi", "Matt Horan"]
9
+ s.email = ["nathan@vertile.com"]
10
+ s.homepage = "http://github.com/nathantsoi/redis-store-json"
11
+ s.summary = "Rails 4 Redis session store for ActionPack with JSON serialization"
12
+ s.description = "Rails 4 Redis session store for ActionPack with JSON serialization"
13
+
14
+ s.rubyforge_project = 'redis-rack-json'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency 'redis-store-json', '~> 3.0.0'
22
+ s.add_runtime_dependency 'rack', '~> 1.5.2'
23
+
24
+ s.add_development_dependency 'rake', '~> 10'
25
+ s.add_development_dependency 'bundler', '~> 1.2'
26
+ s.add_development_dependency 'mocha', '~> 0.13.0'
27
+ s.add_development_dependency 'minitest', '~> 4.3.1'
28
+ end
29
+
@@ -0,0 +1,289 @@
1
+ require 'test_helper'
2
+ require 'rack/mock'
3
+ require 'thread'
4
+
5
+ describe Rack::Session::Redis do
6
+ session_key = Rack::Session::Redis::DEFAULT_OPTIONS[:key]
7
+ session_match = /#{session_key}=([0-9a-fA-F]+);/
8
+ incrementor = lambda do |env|
9
+ env["rack.session"]["counter"] ||= 0
10
+ env["rack.session"]["counter"] += 1
11
+ Rack::Response.new(env["rack.session"].inspect).to_a
12
+ end
13
+ drop_session = proc do |env|
14
+ env['rack.session.options'][:drop] = true
15
+ incrementor.call(env)
16
+ end
17
+ renew_session = proc do |env|
18
+ env['rack.session.options'][:renew] = true
19
+ incrementor.call(env)
20
+ end
21
+ defer_session = proc do |env|
22
+ env['rack.session.options'][:defer] = true
23
+ incrementor.call(env)
24
+ end
25
+
26
+ # # test Redis connection
27
+ # Rack::Session::Redis.new(incrementor)
28
+ #
29
+ # it "faults on no connection" do
30
+ # lambda{
31
+ # Rack::Session::Redis.new(incrementor, :redis_server => 'nosuchserver')
32
+ # }.must_raise(Exception)
33
+ # end
34
+
35
+ it "uses the default Redis server and namespace when not provided" do
36
+ pool = Rack::Session::Redis.new(incrementor)
37
+ pool.pool.to_s.must_match(/127\.0\.0\.1:6379 against DB 0 with namespace rack:session$/)
38
+ end
39
+
40
+ it "uses the specified namespace when provided" do
41
+ pool = Rack::Session::Redis.new(incrementor, :redis_server => {:namespace => 'test:rack:session'})
42
+ pool.pool.to_s.must_match(/namespace test:rack:session$/)
43
+ end
44
+
45
+ it "uses the specified Redis server when provided" do
46
+ pool = Rack::Session::Redis.new(incrementor, :redis_server => 'redis://127.0.0.1:6380/1')
47
+ pool.pool.to_s.must_match(/127\.0\.0\.1:6380 against DB 1$/)
48
+ end
49
+
50
+ it "creates a new cookie" do
51
+ pool = Rack::Session::Redis.new(incrementor)
52
+ res = Rack::MockRequest.new(pool).get("/")
53
+ res["Set-Cookie"].must_include("#{session_key}=")
54
+ res.body.must_equal('{"counter"=>1}')
55
+ end
56
+
57
+ it "determines session from a cookie" do
58
+ pool = Rack::Session::Redis.new(incrementor)
59
+ req = Rack::MockRequest.new(pool)
60
+ res = req.get("/")
61
+ cookie = res["Set-Cookie"]
62
+ req.get("/", "HTTP_COOKIE" => cookie).
63
+ body.must_equal('{"counter"=>2}')
64
+ req.get("/", "HTTP_COOKIE" => cookie).
65
+ body.must_equal('{"counter"=>3}')
66
+ end
67
+
68
+ it "determines session only from a cookie by default" do
69
+ pool = Rack::Session::Redis.new(incrementor)
70
+ req = Rack::MockRequest.new(pool)
71
+ res = req.get("/")
72
+ sid = res["Set-Cookie"][session_match, 1]
73
+ req.get("/?rack.session=#{sid}").
74
+ body.must_equal('{"counter"=>1}')
75
+ req.get("/?rack.session=#{sid}").
76
+ body.must_equal('{"counter"=>1}')
77
+ end
78
+
79
+ it "determines session from params" do
80
+ pool = Rack::Session::Redis.new(incrementor, :cookie_only => false)
81
+ req = Rack::MockRequest.new(pool)
82
+ res = req.get("/")
83
+ sid = res["Set-Cookie"][session_match, 1]
84
+ req.get("/?rack.session=#{sid}").
85
+ body.must_equal('{"counter"=>2}')
86
+ req.get("/?rack.session=#{sid}").
87
+ body.must_equal('{"counter"=>3}')
88
+ end
89
+
90
+ it "survives nonexistant cookies" do
91
+ bad_cookie = "rack.session=blarghfasel"
92
+ pool = Rack::Session::Redis.new(incrementor)
93
+ res = Rack::MockRequest.new(pool).
94
+ get("/", "HTTP_COOKIE" => bad_cookie)
95
+ res.body.must_equal('{"counter"=>1}')
96
+ cookie = res["Set-Cookie"][session_match]
97
+ cookie.wont_match(/#{bad_cookie}/)
98
+ end
99
+
100
+ it "maintains freshness" do
101
+ pool = Rack::Session::Redis.new(incrementor, :expire_after => 3)
102
+ res = Rack::MockRequest.new(pool).get('/')
103
+ res.body.must_include('"counter"=>1')
104
+ cookie = res["Set-Cookie"]
105
+ sid = cookie[session_match, 1]
106
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
107
+ res["Set-Cookie"][session_match, 1].must_equal(sid)
108
+ res.body.must_include('"counter"=>2')
109
+ puts 'Sleeping to expire session' if $DEBUG
110
+ sleep 4
111
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
112
+ res["Set-Cookie"][session_match, 1].wont_equal(sid)
113
+ res.body.must_include('"counter"=>1')
114
+ end
115
+
116
+ it "does not send the same session id if it did not change" do
117
+ pool = Rack::Session::Redis.new(incrementor)
118
+ req = Rack::MockRequest.new(pool)
119
+
120
+ res0 = req.get("/")
121
+ cookie = res0["Set-Cookie"]
122
+ res0.body.must_equal('{"counter"=>1}')
123
+
124
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
125
+ res1["Set-Cookie"].must_be_nil
126
+ res1.body.must_equal('{"counter"=>2}')
127
+
128
+ res2 = req.get("/", "HTTP_COOKIE" => cookie)
129
+ res2["Set-Cookie"].must_be_nil
130
+ res2.body.must_equal('{"counter"=>3}')
131
+ end
132
+
133
+ it "deletes cookies with :drop option" do
134
+ pool = Rack::Session::Redis.new(incrementor)
135
+ req = Rack::MockRequest.new(pool)
136
+ drop = Rack::Utils::Context.new(pool, drop_session)
137
+ dreq = Rack::MockRequest.new(drop)
138
+
139
+ res1 = req.get("/")
140
+ session = (cookie = res1["Set-Cookie"])[session_match]
141
+ res1.body.must_equal('{"counter"=>1}')
142
+
143
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
144
+ res2["Set-Cookie"].must_be_nil
145
+ res2.body.must_equal('{"counter"=>2}')
146
+
147
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
148
+ res3["Set-Cookie"][session_match].wont_equal(session)
149
+ res3.body.must_equal('{"counter"=>1}')
150
+ end
151
+
152
+ it "provides new session id with :renew option" do
153
+ pool = Rack::Session::Redis.new(incrementor)
154
+ req = Rack::MockRequest.new(pool)
155
+ renew = Rack::Utils::Context.new(pool, renew_session)
156
+ rreq = Rack::MockRequest.new(renew)
157
+
158
+ res1 = req.get("/")
159
+ session = (cookie = res1["Set-Cookie"])[session_match]
160
+ res1.body.must_equal('{"counter"=>1}')
161
+
162
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
163
+ new_cookie = res2["Set-Cookie"]
164
+ new_session = new_cookie[session_match]
165
+ new_session.wont_equal(session)
166
+ res2.body.must_equal('{"counter"=>2}')
167
+
168
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
169
+ res3.body.must_equal('{"counter"=>3}')
170
+
171
+ # Old cookie was deleted
172
+ res4 = req.get("/", "HTTP_COOKIE" => cookie)
173
+ res4.body.must_equal('{"counter"=>1}')
174
+ end
175
+
176
+ it "omits cookie with :defer option" do
177
+ pool = Rack::Session::Redis.new(incrementor)
178
+ defer = Rack::Utils::Context.new(pool, defer_session)
179
+ dreq = Rack::MockRequest.new(defer)
180
+
181
+ res0 = dreq.get("/")
182
+ res0["Set-Cookie"].must_be_nil
183
+ res0.body.must_equal('{"counter"=>1}')
184
+ end
185
+
186
+ it "updates deep hashes correctly" do
187
+ hash_check = proc do |env|
188
+ session = env['rack.session']
189
+ unless session.include? 'test'
190
+ session.update :a => :b, :c => { :d => :e },
191
+ :f => { :g => { :h => :i} }, 'test' => true
192
+ else
193
+ session[:f][:g][:h] = :j
194
+ end
195
+ [200, {}, [session.inspect]]
196
+ end
197
+ pool = Rack::Session::Redis.new(hash_check)
198
+ req = Rack::MockRequest.new(pool)
199
+
200
+ res0 = req.get("/")
201
+ session_id = (cookie = res0["Set-Cookie"])[session_match, 1]
202
+ ses0 = pool.pool.get(session_id)
203
+
204
+ req.get("/", "HTTP_COOKIE" => cookie)
205
+ ses1 = pool.pool.get(session_id)
206
+
207
+ ses1.wont_equal(ses0)
208
+ end
209
+
210
+ # anyone know how to do this better?
211
+ it "cleanly merges sessions when multithreaded" do
212
+ unless $DEBUG
213
+ 1.must_equal(1) # fake assertion to appease the mighty bacon
214
+ next
215
+ end
216
+ warn 'Running multithread test for Session::Redis'
217
+ pool = Rack::Session::Redis.new(incrementor)
218
+ req = Rack::MockRequest.new(pool)
219
+
220
+ res = req.get('/')
221
+ res.body.must_equal('{"counter"=>1}')
222
+ cookie = res["Set-Cookie"]
223
+ session_id = cookie[session_match, 1]
224
+
225
+ delta_incrementor = lambda do |env|
226
+ # emulate disconjoinment of threading
227
+ env['rack.session'] = env['rack.session'].dup
228
+ Thread.stop
229
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
230
+ incrementor.call(env)
231
+ end
232
+ tses = Rack::Utils::Context.new pool, delta_incrementor
233
+ treq = Rack::MockRequest.new(tses)
234
+ tnum = rand(7).to_i+5
235
+ r = Array.new(tnum) do
236
+ Thread.new(treq) do |run|
237
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
238
+ end
239
+ end.reverse.map{|t| t.run.join.value }
240
+ r.each do |request|
241
+ request['Set-Cookie'].must_equal(cookie)
242
+ request.body.must_include('"counter"=>2')
243
+ end
244
+
245
+ session = pool.pool.get(session_id)
246
+ session.size.must_equal(tnum+1) # counter
247
+ session['counter'].must_equal(2) # meeeh
248
+
249
+ tnum = rand(7).to_i+5
250
+ r = Array.new(tnum) do |i|
251
+ app = Rack::Utils::Context.new pool, time_delta
252
+ req = Rack::MockRequest.new app
253
+ Thread.new(req) do |run|
254
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
255
+ end
256
+ end.reverse.map{|t| t.run.join.value }
257
+ r.each do |request|
258
+ request['Set-Cookie'].must_equal(cookie)
259
+ request.body.must_include('"counter"=>3')
260
+ end
261
+
262
+ session = pool.pool.get(session_id)
263
+ session.size.must_equal(tnum+1)
264
+ session['counter'].must_equal(3)
265
+
266
+ drop_counter = proc do |env|
267
+ env['rack.session'].delete 'counter'
268
+ env['rack.session']['foo'] = 'bar'
269
+ [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
270
+ end
271
+ tses = Rack::Utils::Context.new pool, drop_counter
272
+ treq = Rack::MockRequest.new(tses)
273
+ tnum = rand(7).to_i+5
274
+ r = Array.new(tnum) do
275
+ Thread.new(treq) do |run|
276
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
277
+ end
278
+ end.reverse.map{|t| t.run.join.value }
279
+ r.each do |request|
280
+ request['Set-Cookie'].must_equal(cookie)
281
+ request.body.must_include('"foo"=>"bar"')
282
+ end
283
+
284
+ session = pool.pool.get(session_id)
285
+ session.size.must_equal(r.size+1)
286
+ session['counter'].must_be_nil
287
+ session['foo'].must_equal('bar')
288
+ end
289
+ end