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