tee_input: restore read position after #size
[unicorn.git] / test / unit / test_tee_input.rb
blobe69c8f1d44c94ebdb6a73972c1cd3f011cc040b7
1 # -*- encoding: binary -*-
3 require 'test/unit'
4 require 'digest/sha1'
5 require 'unicorn'
7 class TeeInput < Unicorn::TeeInput
8   attr_accessor :tmp, :len
9 end
11 class TestTeeInput < Test::Unit::TestCase
13   def setup
14     @rs = $/
15     @env = {}
16     @rd, @wr = Kgio::UNIXSocket.pair
17     @rd.sync = @wr.sync = true
18     @start_pid = $$
19   end
21   def teardown
22     return if $$ != @start_pid
23     $/ = @rs
24     @rd.close rescue nil
25     @wr.close rescue nil
26     begin
27       Process.wait
28     rescue Errno::ECHILD
29       break
30     end while true
31   end
33   def test_gets_long
34     r = init_request("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size)
35     ti = TeeInput.new(@rd, r)
36     status = line = nil
37     pid = fork {
38       @rd.close
39       3.times { @wr.write("ffff" * 4096) }
40       @wr.write "#$/foo#$/"
41       @wr.close
42     }
43     @wr.close
44     assert_nothing_raised { line = ti.gets }
45     assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
46     assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
47     assert_nothing_raised { line = ti.gets }
48     assert_equal "foo#$/", line
49     assert_nil ti.gets
50     assert_nothing_raised { pid, status = Process.waitpid2(pid) }
51     assert status.success?
52   end
54   def test_gets_short
55     r = init_request("hello", 5 + "#$/foo".size)
56     ti = TeeInput.new(@rd, r)
57     status = line = nil
58     pid = fork {
59       @rd.close
60       @wr.write "#$/foo"
61       @wr.close
62     }
63     @wr.close
64     assert_nothing_raised { line = ti.gets }
65     assert_equal("hello#$/", line)
66     assert_nothing_raised { line = ti.gets }
67     assert_equal "foo", line
68     assert_nil ti.gets
69     assert_nothing_raised { pid, status = Process.waitpid2(pid) }
70     assert status.success?
71   end
73   def test_small_body
74     r = init_request('hello')
75     ti = TeeInput.new(@rd, r)
76     assert_equal 0, @parser.content_length
77     assert @parser.body_eof?
78     assert_equal StringIO, ti.tmp.class
79     assert_equal 0, ti.tmp.pos
80     assert_equal 5, ti.size
81     assert_equal 'hello', ti.read
82     assert_equal '', ti.read
83     assert_nil ti.read(4096)
84     assert_equal 5, ti.size
85   end
87   def test_read_with_buffer
88     r = init_request('hello')
89     ti = TeeInput.new(@rd, r)
90     buf = ''
91     rv = ti.read(4, buf)
92     assert_equal 'hell', rv
93     assert_equal 'hell', buf
94     assert_equal rv.object_id, buf.object_id
95     assert_equal 'o', ti.read
96     assert_equal nil, ti.read(5, buf)
97     assert_equal 0, ti.rewind
98     assert_equal 'hello', ti.read(5, buf)
99     assert_equal 'hello', buf
100   end
102   def test_big_body
103     r = init_request('.' * Unicorn::Const::MAX_BODY << 'a')
104     ti = TeeInput.new(@rd, r)
105     assert_equal 0, @parser.content_length
106     assert @parser.body_eof?
107     assert_kind_of File, ti.tmp
108     assert_equal 0, ti.tmp.pos
109     assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
110   end
112   def test_read_in_full_if_content_length
113     a, b = 300, 3
114     r = init_request('.' * b, 300)
115     assert_equal 300, @parser.content_length
116     ti = TeeInput.new(@rd, r)
117     pid = fork {
118       @wr.write('.' * 197)
119       sleep 1 # still a *potential* race here that would make the test moot...
120       @wr.write('.' * 100)
121     }
122     assert_equal a, ti.read(a).size
123     _, status = Process.waitpid2(pid)
124     assert status.success?
125     @wr.close
126   end
128   def test_big_body_multi
129     r = init_request('.', Unicorn::Const::MAX_BODY + 1)
130     ti = TeeInput.new(@rd, r)
131     assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
132     assert ! @parser.body_eof?
133     assert_kind_of File, ti.tmp
134     assert_equal 0, ti.tmp.pos
135     assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
136     nr = Unicorn::Const::MAX_BODY / 4
137     pid = fork {
138       @rd.close
139       nr.times { @wr.write('....') }
140       @wr.close
141     }
142     @wr.close
143     assert_equal '.', ti.read(1)
144     assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
145     nr.times { |x|
146       assert_equal '....', ti.read(4), "nr=#{x}"
147       assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
148     }
149     assert_nil ti.read(1)
150     status = nil
151     assert_nothing_raised { pid, status = Process.waitpid2(pid) }
152     assert status.success?
153   end
155   def test_chunked
156     @parser = Unicorn::HttpParser.new
157     @buf = "POST / HTTP/1.1\r\n" \
158            "Host: localhost\r\n" \
159            "Transfer-Encoding: chunked\r\n" \
160            "\r\n"
161     assert_equal @env, @parser.headers(@env, @buf)
162     assert_equal "", @buf
164     pid = fork {
165       @rd.close
166       5.times { @wr.write("5\r\nabcde\r\n") }
167       @wr.write("0\r\n\r\n")
168     }
169     @wr.close
170     ti = TeeInput.new(@rd, @parser)
171     assert_nil @parser.content_length
172     assert_nil ti.len
173     assert ! @parser.body_eof?
174     assert_equal 25, ti.size
175     assert @parser.body_eof?
176     assert_equal 25, ti.len
177     assert_equal 0, ti.tmp.pos
178     assert_nothing_raised { ti.rewind }
179     assert_equal 0, ti.tmp.pos
180     assert_equal 'abcdeabcdeabcdeabcde', ti.read(20)
181     assert_equal 20, ti.tmp.pos
182     assert_nothing_raised { ti.rewind }
183     assert_equal 0, ti.tmp.pos
184     assert_kind_of File, ti.tmp
185     status = nil
186     assert_nothing_raised { pid, status = Process.waitpid2(pid) }
187     assert status.success?
188   end
190   def test_chunked_ping_pong
191     @parser = Unicorn::HttpParser.new
192     @buf = "POST / HTTP/1.1\r\n" \
193            "Host: localhost\r\n" \
194            "Transfer-Encoding: chunked\r\n" \
195            "\r\n"
196     assert_equal @env, @parser.headers(@env, @buf)
197     assert_equal "", @buf
198     chunks = %w(aa bbb cccc dddd eeee)
199     rd, wr = IO.pipe
201     pid = fork {
202       chunks.each do |chunk|
203         rd.read(1) == "." and
204           @wr.write("#{'%x' % [ chunk.size]}\r\n#{chunk}\r\n")
205       end
206       @wr.write("0\r\n\r\n")
207     }
208     ti = TeeInput.new(@rd, @parser)
209     assert_nil @parser.content_length
210     assert_nil ti.len
211     assert ! @parser.body_eof?
212     chunks.each do |chunk|
213       wr.write('.')
214       assert_equal chunk, ti.read(16384)
215     end
216     _, status = Process.waitpid2(pid)
217     assert status.success?
218   end
220   def test_chunked_with_trailer
221     @parser = Unicorn::HttpParser.new
222     @buf = "POST / HTTP/1.1\r\n" \
223            "Host: localhost\r\n" \
224            "Trailer: Hello\r\n" \
225            "Transfer-Encoding: chunked\r\n" \
226            "\r\n"
227     assert_equal @env, @parser.headers(@env, @buf)
228     assert_equal "", @buf
230     pid = fork {
231       @rd.close
232       5.times { @wr.write("5\r\nabcde\r\n") }
233       @wr.write("0\r\n")
234       @wr.write("Hello: World\r\n\r\n")
235     }
236     @wr.close
237     ti = TeeInput.new(@rd, @parser)
238     assert_nil @parser.content_length
239     assert_nil ti.len
240     assert ! @parser.body_eof?
241     assert_equal 25, ti.size
242     assert_equal "World", @env['HTTP_HELLO']
243     status = nil
244     assert_nothing_raised { pid, status = Process.waitpid2(pid) }
245     assert status.success?
246   end
248   def test_chunked_and_size_slow
249     @parser = Unicorn::HttpParser.new
250     @buf = "POST / HTTP/1.1\r\n" \
251            "Host: localhost\r\n" \
252            "Trailer: Hello\r\n" \
253            "Transfer-Encoding: chunked\r\n" \
254            "\r\n"
255     assert_equal @env, @parser.headers(@env, @buf)
256     assert_equal "", @buf
258     @wr.write("9\r\nabcde")
259     ti = TeeInput.new(@rd, @parser)
260     assert_nil @parser.content_length
261     assert_equal "abcde", ti.read(9)
262     assert ! @parser.body_eof?
263     @wr.write("fghi\r\n0\r\nHello: World\r\n\r\n")
264     assert_equal 9, ti.size
265     assert_equal "fghi", ti.read(9)
266     assert_equal nil, ti.read(9)
267     assert_equal "World", @env['HTTP_HELLO']
268   end
270 private
272   def init_request(body, size = nil)
273     @parser = Unicorn::HttpParser.new
274     body = body.to_s.freeze
275     @buf = "POST / HTTP/1.1\r\n" \
276            "Host: localhost\r\n" \
277            "Content-Length: #{size || body.size}\r\n" \
278            "\r\n#{body}"
279     assert_equal @env, @parser.headers(@env, @buf)
280     assert_equal body, @buf
281     @parser
282   end