rack.git  about / heads / tags
a modular Ruby webserver interface
blob c6f7c05ec70a8568964b90fc676c631510280162 5847 bytes (raw)
$ git show webrick-devdep:test/spec_lock.rb	# shows this blob on the CLI

  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
204
 
require 'minitest/autorun'
require 'rack/lint'
require 'rack/lock'
require 'rack/mock'

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

git clone https://yhbt.net/rack.git