test_upload: close urandom fd at teardown
[unicorn.git] / test / unit / test_upload.rb
blob7629fbef08c8fba6e3344b3959f1f39d09f3047c
1 # Copyright (c) 2009 Eric Wong
2 require 'test/test_helper'
4 include Unicorn
6 class UploadTest < Test::Unit::TestCase
8   def setup
9     @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
10     @port = unused_port
11     @hdr = {'Content-Type' => 'text/plain', 'Content-Length' => '0'}
12     @bs = 4096
13     @count = 256
14     @server = nil
16     # we want random binary data to test 1.9 encoding-aware IO craziness
17     @random = File.open('/dev/urandom','rb')
18     @sha1 = Digest::SHA1.new
19     @sha1_app = lambda do |env|
20       input = env['rack.input']
21       resp = { :pos => input.pos, :size => input.stat.size }
22       begin
23         loop { @sha1.update(input.sysread(@bs)) }
24       rescue EOFError
25       end
26       resp[:sha1] = @sha1.hexdigest
27       [ 200, @hdr.merge({'X-Resp' => resp.inspect}), [] ]
28     end
29   end
31   def teardown
32     redirect_test_io { @server.stop(true) } if @server
33     @random.close
34   end
36   def test_put
37     start_server(@sha1_app)
38     sock = TCPSocket.new(@addr, @port)
39     sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
40     @count.times do
41       buf = @random.sysread(@bs)
42       @sha1.update(buf)
43       sock.syswrite(buf)
44     end
45     read = sock.read.split(/\r\n/)
46     assert_equal "HTTP/1.1 200 OK", read[0]
47     resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
48     assert_equal length, resp[:size]
49     assert_equal 0, resp[:pos]
50     assert_equal @sha1.hexdigest, resp[:sha1]
51   end
54   def test_put_keepalive_truncates_small_overwrite
55     start_server(@sha1_app)
56     sock = TCPSocket.new(@addr, @port)
57     to_upload = length + 1
58     sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{to_upload}\r\n\r\n")
59     @count.times do
60       buf = @random.sysread(@bs)
61       @sha1.update(buf)
62       sock.syswrite(buf)
63     end
64     sock.syswrite('12345') # write 4 bytes more than we expected
65     @sha1.update('1')
67     read = sock.read.split(/\r\n/)
68     assert_equal "HTTP/1.1 200 OK", read[0]
69     resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
70     assert_equal to_upload, resp[:size]
71     assert_equal 0, resp[:pos]
72     assert_equal @sha1.hexdigest, resp[:sha1]
73   end
75   def test_put_excessive_overwrite_closed
76     start_server(lambda { |env| [ 200, @hdr, [] ] })
77     sock = TCPSocket.new(@addr, @port)
78     buf = ' ' * @bs
79     sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
80     @count.times { sock.syswrite(buf) }
81     assert_raise Errno::ECONNRESET do
82       ::Unicorn::Const::CHUNK_SIZE.times { sock.syswrite(buf) }
83     end
84   end
86   def test_put_handler_closed_file
87     nr = '0'
88     start_server(lambda { |env|
89       env['rack.input'].close
90       resp = { :nr => nr.succ! }
91       [ 200, @hdr.merge({ 'X-Resp' => resp.inspect}), [] ]
92     })
93     sock = TCPSocket.new(@addr, @port)
94     buf = ' ' * @bs
95     sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
96     @count.times { sock.syswrite(buf) }
97     read = sock.read.split(/\r\n/)
98     assert_equal "HTTP/1.1 200 OK", read[0]
99     resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
100     assert_equal '1', resp[:nr]
102     # server still alive?
103     sock = TCPSocket.new(@addr, @port)
104     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
105     read = sock.read.split(/\r\n/)
106     assert_equal "HTTP/1.1 200 OK", read[0]
107     resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
108     assert_equal '2', resp[:nr]
109   end
111   def test_renamed_file_not_closed
112     start_server(lambda { |env|
113       new_tmp = Tempfile.new('unicorn_test')
114       input = env['rack.input']
115       File.rename(input.path, new_tmp.path)
116       resp = {
117         :inode => input.stat.ino,
118         :size => input.stat.size,
119         :new_tmp => new_tmp.path,
120         :old_tmp => input.path,
121       }
122       [ 200, @hdr.merge({ 'X-Resp' => resp.inspect}), [] ]
123     })
124     sock = TCPSocket.new(@addr, @port)
125     buf = ' ' * @bs
126     sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
127     @count.times { sock.syswrite(buf) }
128     read = sock.read.split(/\r\n/)
129     assert_equal "HTTP/1.1 200 OK", read[0]
130     resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
131     new_tmp = File.open(resp[:new_tmp])
132     assert_equal resp[:inode], new_tmp.stat.ino
133     assert_equal length, resp[:size]
134     assert ! File.exist?(resp[:old_tmp])
135     assert_equal resp[:size], new_tmp.stat.size
136   end
138   private
140   def length
141     @bs * @count
142   end
144   def start_server(app)
145     redirect_test_io do
146       @server = HttpServer.new(app, :listeners => [ "#{@addr}:#{@port}" ] )
147       @server.start
148     end
149   end