1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
# frozen_string_literal: true
require_relative 'helper'
class Lock
attr_reader :synchronized
def initialize
@synchronized = false
end
def lock
@synchronized = true
end
def unlock
@synchronized = false
end
end
module LockHelpers
def lock_app(app, lock = Lock.new)
app = if lock
Rack::Lock.new app, lock
else
Rack::Lock.new app
end
Rack::Lint.new app
end
end
describe Rack::Lock do
include LockHelpers
describe 'Proxy' do
include LockHelpers
it 'delegate each' do
env = Rack::MockRequest.env_for("/")
response = Class.new {
attr_accessor :close_called
def initialize; @close_called = false; end
def each; %w{ hi mom }.each { |x| yield x }; end
}.new
app = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, response] })
response = app.call(env)[2]
list = []
response.each { |x| list << x }
list.must_equal %w{ hi mom }
end
it 'delegate to_path' do
lock = Lock.new
env = Rack::MockRequest.env_for("/")
res = ['Hello World']
def res.to_path ; "/tmp/hello.txt" ; end
app = Rack::Lock.new(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, res] }, lock)
body = app.call(env)[2]
body.must_respond_to :to_path
body.to_path.must_equal "/tmp/hello.txt"
end
it 'not delegate to_path if body does not implement it' do
env = Rack::MockRequest.env_for("/")
res = ['Hello World']
app = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, res] })
body = app.call(env)[2]
body.wont_respond_to :to_path
end
end
it 'call super on close' do
env = Rack::MockRequest.env_for("/")
response = Class.new {
attr_accessor :close_called
def initialize; @close_called = false; end
def close; @close_called = true; end
}.new
app = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, response] })
app.call(env)
response.close_called.must_equal false
response.close
response.close_called.must_equal true
end
it "not unlock until body is closed" do
lock = Lock.new
env = Rack::MockRequest.env_for("/")
response = Object.new
app = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, response] }, lock)
lock.synchronized.must_equal false
response = app.call(env)[2]
lock.synchronized.must_equal true
response.close
lock.synchronized.must_equal false
end
it "return value from app" do
env = Rack::MockRequest.env_for("/")
body = [200, { "Content-Type" => "text/plain" }, %w{ hi mom }]
app = lock_app(lambda { |inner_env| body })
res = app.call(env)
res[0].must_equal body[0]
res[1].must_equal body[1]
res[2].to_enum.to_a.must_equal ["hi", "mom"]
end
it "call synchronize on lock" do
lock = Lock.new
env = Rack::MockRequest.env_for("/")
app = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, %w{ a b c }] }, lock)
lock.synchronized.must_equal false
app.call(env)
lock.synchronized.must_equal true
end
it "unlock if the app raises" do
lock = Lock.new
env = Rack::MockRequest.env_for("/")
app = lock_app(lambda { raise Exception }, lock)
lambda { app.call(env) }.must_raise Exception
lock.synchronized.must_equal false
end
it "unlock if the app throws" do
lock = Lock.new
env = Rack::MockRequest.env_for("/")
app = lock_app(lambda {|_| throw :bacon }, lock)
lambda { app.call(env) }.must_throw :bacon
lock.synchronized.must_equal false
end
it "set multithread flag to false" do
app = lock_app(lambda { |env|
env['rack.multithread'].must_equal false
[200, { "Content-Type" => "text/plain" }, %w{ a b c }]
}, false)
env = Rack::MockRequest.env_for("/")
env['rack.multithread'].must_equal true
_, _, body = app.call(env)
body.close
env['rack.multithread'].must_equal true
end
it "reset original multithread flag when exiting lock" do
app = Class.new(Rack::Lock) {
def call(env)
env['rack.multithread'].must_equal true
super
end
}.new(lambda { |env| [200, { "Content-Type" => "text/plain" }, %w{ a b c }] })
Rack::Lint.new(app).call(Rack::MockRequest.env_for("/"))
end
it 'not unlock if an error is raised before the mutex is locked' do
lock = Class.new do
def initialize() @unlocked = false end
def unlocked?() @unlocked end
def lock() raise Exception end
def unlock() @unlocked = true end
end.new
env = Rack::MockRequest.env_for("/")
app = lock_app(proc { [200, { "Content-Type" => "text/plain" }, []] }, lock)
lambda { app.call(env) }.must_raise Exception
lock.unlocked?.must_equal false
end
it "not reset the environment while the body is proxied" do
proxy = Class.new do
attr_reader :env
def initialize(env) @env = env end
end
app = Rack::Lock.new lambda { |env| [200, { "Content-Type" => "text/plain" }, proxy.new(env)] }
response = app.call(Rack::MockRequest.env_for("/"))[2]
response.env['rack.multithread'].must_equal false
end
it "unlock if an exception occurs before returning" do
lock = Lock.new
env = Rack::MockRequest.env_for("/")
app = lock_app(proc { [].freeze }, lock)
lambda { app.call(env) }.must_raise Exception
lock.synchronized.must_equal false
end
it "not replace the environment" do
env = Rack::MockRequest.env_for("/")
app = lock_app(lambda { |inner_env| [200, { "Content-Type" => "text/plain" }, [inner_env.object_id.to_s]] })
_, _, body = app.call(env)
body.to_enum.to_a.must_equal [env.object_id.to_s]
end
end
|