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