1 # Copyright (c) 2009 Eric Wong
2 require 'test/test_helper'
6 class UploadTest < Test::Unit::TestCase
9 @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
11 @hdr = {'Content-Type' => 'text/plain', 'Content-Length' => '0'}
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 = { :size => input.size }
24 while buf = input.read(@bs)
27 resp[:sha1] = @sha1.hexdigest
29 # rewind and read again
32 while buf = input.read(@bs)
36 if resp[:sha1] == @sha1.hexdigest
37 resp[:sysread_read_byte_match] = true
40 [ 200, @hdr.merge({'X-Resp' => resp.inspect}), [] ]
45 redirect_test_io { @server.stop(true) } if @server
50 start_server(@sha1_app)
51 sock = TCPSocket.new(@addr, @port)
52 sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
54 buf = @random.sysread(@bs)
58 read = sock.read.split(/\r\n/)
59 assert_equal "HTTP/1.1 200 OK", read[0]
60 resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
61 assert_equal length, resp[:size]
62 assert_equal @sha1.hexdigest, resp[:sha1]
65 def test_put_trickle_small
67 start_server(@sha1_app)
68 assert_equal 256, length
69 sock = TCPSocket.new(@addr, @port)
70 hdr = "PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n"
72 buf = @random.sysread(@bs)
79 read = sock.read.split(/\r\n/)
80 assert_equal "HTTP/1.1 200 OK", read[0]
81 resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
82 assert_equal length, resp[:size]
83 assert_equal @sha1.hexdigest, resp[:sha1]
86 def test_put_keepalive_truncates_small_overwrite
87 start_server(@sha1_app)
88 sock = TCPSocket.new(@addr, @port)
89 to_upload = length + 1
90 sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{to_upload}\r\n\r\n")
92 buf = @random.sysread(@bs)
96 sock.syswrite('12345') # write 4 bytes more than we expected
99 read = sock.read.split(/\r\n/)
100 assert_equal "HTTP/1.1 200 OK", read[0]
101 resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
102 assert_equal to_upload, resp[:size]
103 assert_equal @sha1.hexdigest, resp[:sha1]
106 def test_put_excessive_overwrite_closed
107 start_server(lambda { |env| [ 200, @hdr, [] ] })
108 sock = TCPSocket.new(@addr, @port)
110 sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
111 @count.times { sock.syswrite(buf) }
112 assert_raise(Errno::ECONNRESET, Errno::EPIPE) do
113 ::Unicorn::Const::CHUNK_SIZE.times { sock.syswrite(buf) }
117 # Despite reading numerous articles and inspecting the 1.9.1-p0 C
118 # source, Eric Wong will never trust that we're always handling
119 # encoding-aware IO objects correctly. Thus this test uses shell
120 # utilities that should always operate on files/sockets on a
122 def test_uncomfortable_with_onenine_encodings
123 # POSIX doesn't require all of these to be present on a system
124 which('curl') or return
125 which('sha1sum') or return
126 which('dd') or return
128 start_server(@sha1_app)
130 tmp = Tempfile.new('dd_dest')
131 assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
132 "bs=#{@bs}", "count=#{@count}"),
133 "dd #@random to #{tmp}")
134 sha1_re = %r!\b([a-f0-9]{40})\b!
135 sha1_out = `sha1sum #{tmp.path}`
136 assert $?.success?, 'sha1sum ran OK'
138 assert_match(sha1_re, sha1_out)
139 sha1 = sha1_re.match(sha1_out)[1]
140 resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
141 assert $?.success?, 'curl ran OK'
142 assert_match(%r!\b#{sha1}\b!, resp)
143 assert_match(/sysread_read_byte_match/, resp)
145 # small StringIO path
146 assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
147 "bs=1024", "count=1"),
148 "dd #@random to #{tmp}")
149 sha1_re = %r!\b([a-f0-9]{40})\b!
150 sha1_out = `sha1sum #{tmp.path}`
151 assert $?.success?, 'sha1sum ran OK'
153 assert_match(sha1_re, sha1_out)
154 sha1 = sha1_re.match(sha1_out)[1]
155 resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
156 assert $?.success?, 'curl ran OK'
157 assert_match(%r!\b#{sha1}\b!, resp)
158 assert_match(/sysread_read_byte_match/, resp)
167 def start_server(app)
169 @server = HttpServer.new(app, :listeners => [ "#{@addr}:#{@port}" ] )