unicorn-simon 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitattributes +5 -0
  5. data/.gitignore +25 -0
  6. data/.mailmap +26 -0
  7. data/.manifest +156 -0
  8. data/.olddoc.yml +18 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +35 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +95 -0
  13. data/Documentation/.gitignore +5 -0
  14. data/Documentation/GNUmakefile +30 -0
  15. data/Documentation/unicorn.1.txt +187 -0
  16. data/Documentation/unicorn_rails.1.txt +175 -0
  17. data/FAQ +70 -0
  18. data/GIT-VERSION-FILE +1 -0
  19. data/GIT-VERSION-GEN +39 -0
  20. data/GNUmakefile +253 -0
  21. data/HACKING +120 -0
  22. data/ISSUES +90 -0
  23. data/KNOWN_ISSUES +79 -0
  24. data/LATEST +30 -0
  25. data/LICENSE +67 -0
  26. data/Links +56 -0
  27. data/NEWS +2465 -0
  28. data/PHILOSOPHY +139 -0
  29. data/README +138 -0
  30. data/Rakefile +16 -0
  31. data/SIGNALS +123 -0
  32. data/Sandbox +104 -0
  33. data/TODO +3 -0
  34. data/TUNING +119 -0
  35. data/archive/.gitignore +3 -0
  36. data/archive/slrnpull.conf +4 -0
  37. data/bin/unicorn +126 -0
  38. data/bin/unicorn_rails +209 -0
  39. data/examples/big_app_gc.rb +2 -0
  40. data/examples/echo.ru +27 -0
  41. data/examples/init.sh +102 -0
  42. data/examples/logger_mp_safe.rb +25 -0
  43. data/examples/logrotate.conf +44 -0
  44. data/examples/nginx.conf +155 -0
  45. data/examples/unicorn.conf.minimal.rb +13 -0
  46. data/examples/unicorn.conf.rb +110 -0
  47. data/examples/unicorn.socket +11 -0
  48. data/examples/unicorn@.service +33 -0
  49. data/ext/unicorn_http/CFLAGS +13 -0
  50. data/ext/unicorn_http/c_util.h +124 -0
  51. data/ext/unicorn_http/common_field_optimization.h +111 -0
  52. data/ext/unicorn_http/ext_help.h +62 -0
  53. data/ext/unicorn_http/extconf.rb +11 -0
  54. data/ext/unicorn_http/global_variables.h +97 -0
  55. data/ext/unicorn_http/httpdate.c +78 -0
  56. data/ext/unicorn_http/unicorn_http.c +4274 -0
  57. data/ext/unicorn_http/unicorn_http.rl +980 -0
  58. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  59. data/lib/unicorn/app/old_rails/static.rb +59 -0
  60. data/lib/unicorn/app/old_rails.rb +35 -0
  61. data/lib/unicorn/cgi_wrapper.rb +147 -0
  62. data/lib/unicorn/configurator.rb +664 -0
  63. data/lib/unicorn/const.rb +21 -0
  64. data/lib/unicorn/http_request.rb +122 -0
  65. data/lib/unicorn/http_response.rb +60 -0
  66. data/lib/unicorn/http_server.rb +824 -0
  67. data/lib/unicorn/launcher.rb +62 -0
  68. data/lib/unicorn/oob_gc.rb +82 -0
  69. data/lib/unicorn/preread_input.rb +33 -0
  70. data/lib/unicorn/socket_helper.rb +195 -0
  71. data/lib/unicorn/stream_input.rb +146 -0
  72. data/lib/unicorn/tee_input.rb +133 -0
  73. data/lib/unicorn/tmpio.rb +27 -0
  74. data/lib/unicorn/util.rb +90 -0
  75. data/lib/unicorn/version.rb +1 -0
  76. data/lib/unicorn/worker.rb +140 -0
  77. data/lib/unicorn.rb +123 -0
  78. data/man/man1/unicorn.1 +221 -0
  79. data/man/man1/unicorn_rails.1 +212 -0
  80. data/setup.rb +1586 -0
  81. data/t/.gitignore +4 -0
  82. data/t/GNUmakefile +74 -0
  83. data/t/README +42 -0
  84. data/t/bin/content-md5-put +36 -0
  85. data/t/bin/sha1sum.rb +17 -0
  86. data/t/bin/unused_listen +40 -0
  87. data/t/broken-app.ru +12 -0
  88. data/t/detach.ru +11 -0
  89. data/t/env.ru +3 -0
  90. data/t/fails-rack-lint.ru +5 -0
  91. data/t/heartbeat-timeout.ru +12 -0
  92. data/t/hijack.ru +43 -0
  93. data/t/listener_names.ru +4 -0
  94. data/t/my-tap-lib.sh +201 -0
  95. data/t/oob_gc.ru +20 -0
  96. data/t/oob_gc_path.ru +20 -0
  97. data/t/pid.ru +3 -0
  98. data/t/preread_input.ru +17 -0
  99. data/t/rack-input-tests.ru +21 -0
  100. data/t/t0000-http-basic.sh +50 -0
  101. data/t/t0001-reload-bad-config.sh +53 -0
  102. data/t/t0002-config-conflict.sh +49 -0
  103. data/t/t0002-parser-error.sh +94 -0
  104. data/t/t0003-working_directory.sh +51 -0
  105. data/t/t0004-heartbeat-timeout.sh +69 -0
  106. data/t/t0004-working_directory_broken.sh +24 -0
  107. data/t/t0005-working_directory_app.rb.sh +40 -0
  108. data/t/t0006-reopen-logs.sh +83 -0
  109. data/t/t0006.ru +13 -0
  110. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  111. data/t/t0008-back_out_of_upgrade.sh +110 -0
  112. data/t/t0009-broken-app.sh +56 -0
  113. data/t/t0009-winch_ttin.sh +59 -0
  114. data/t/t0010-reap-logging.sh +55 -0
  115. data/t/t0011-active-unix-socket.sh +79 -0
  116. data/t/t0012-reload-empty-config.sh +85 -0
  117. data/t/t0013-rewindable-input-false.sh +24 -0
  118. data/t/t0013.ru +12 -0
  119. data/t/t0014-rewindable-input-true.sh +24 -0
  120. data/t/t0014.ru +12 -0
  121. data/t/t0015-configurator-internals.sh +25 -0
  122. data/t/t0018-write-on-close.sh +23 -0
  123. data/t/t0019-max_header_len.sh +49 -0
  124. data/t/t0020-at_exit-handler.sh +49 -0
  125. data/t/t0021-process_detach.sh +29 -0
  126. data/t/t0022-listener_names-preload_app.sh +32 -0
  127. data/t/t0100-rack-input-tests.sh +124 -0
  128. data/t/t0116-client_body_buffer_size.sh +80 -0
  129. data/t/t0116.ru +16 -0
  130. data/t/t0200-rack-hijack.sh +30 -0
  131. data/t/t0300-no-default-middleware.sh +20 -0
  132. data/t/t9000-preread-input.sh +48 -0
  133. data/t/t9001-oob_gc.sh +47 -0
  134. data/t/t9002-oob_gc-path.sh +75 -0
  135. data/t/test-lib.sh +128 -0
  136. data/t/write-on-close.ru +11 -0
  137. data/test/aggregate.rb +15 -0
  138. data/test/benchmark/README +50 -0
  139. data/test/benchmark/dd.ru +18 -0
  140. data/test/benchmark/stack.ru +8 -0
  141. data/test/exec/README +5 -0
  142. data/test/exec/test_exec.rb +1099 -0
  143. data/test/test_helper.rb +298 -0
  144. data/test/unit/test_configurator.rb +175 -0
  145. data/test/unit/test_droplet.rb +28 -0
  146. data/test/unit/test_http_parser.rb +886 -0
  147. data/test/unit/test_http_parser_ng.rb +633 -0
  148. data/test/unit/test_request.rb +182 -0
  149. data/test/unit/test_response.rb +111 -0
  150. data/test/unit/test_server.rb +268 -0
  151. data/test/unit/test_signals.rb +188 -0
  152. data/test/unit/test_socket_helper.rb +197 -0
  153. data/test/unit/test_stream_input.rb +203 -0
  154. data/test/unit/test_tee_input.rb +304 -0
  155. data/test/unit/test_upload.rb +306 -0
  156. data/test/unit/test_util.rb +105 -0
  157. data/unicorn.gemspec +50 -0
  158. metadata +310 -0
@@ -0,0 +1,304 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'test/unit'
4
+ require 'digest/sha1'
5
+ require 'unicorn'
6
+
7
+ class TeeInput < Unicorn::TeeInput
8
+ attr_accessor :tmp, :len
9
+ end
10
+
11
+ class TestTeeInput < Test::Unit::TestCase
12
+
13
+ def setup
14
+ @rs = $/
15
+ @rd, @wr = Kgio::UNIXSocket.pair
16
+ @rd.sync = @wr.sync = true
17
+ @start_pid = $$
18
+ end
19
+
20
+ def teardown
21
+ return if $$ != @start_pid
22
+ $/ = @rs
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
31
+
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
38
+
39
+ def test_gets_long
40
+ r = init_request("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".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 "#$/foo#$/"
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) << "#$/", line)
53
+ line = ti.gets
54
+ assert_equal "foo#$/", line
55
+ assert_nil ti.gets
56
+ pid, status = Process.waitpid2(pid)
57
+ assert status.success?
58
+ end
59
+
60
+ def test_gets_short
61
+ r = init_request("hello", 5 + "#$/foo".size)
62
+ ti = TeeInput.new(@rd, r)
63
+ status = line = nil
64
+ pid = fork {
65
+ @rd.close
66
+ @wr.write "#$/foo"
67
+ @wr.close
68
+ }
69
+ @wr.close
70
+ line = ti.gets
71
+ assert_equal("hello#$/", 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
78
+
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
92
+
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
107
+
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
118
+
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
134
+
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
161
+
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
170
+
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
197
+
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
209
+
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
228
+
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
239
+
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
256
+
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
267
+
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
279
+
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
287
+
288
+ private
289
+
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
303
+
304
+ end
@@ -0,0 +1,306 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2009 Eric Wong
4
+ require './test/test_helper'
5
+ require 'digest/md5'
6
+
7
+ include Unicorn
8
+
9
+ class UploadTest < Test::Unit::TestCase
10
+
11
+ def setup
12
+ @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
13
+ @port = unused_port
14
+ @hdr = {'Content-Type' => 'text/plain', 'Content-Length' => '0'}
15
+ @bs = 4096
16
+ @count = 256
17
+ @server = nil
18
+
19
+ # we want random binary data to test 1.9 encoding-aware IO craziness
20
+ @random = File.open('/dev/urandom','rb')
21
+ @sha1 = Digest::SHA1.new
22
+ @sha1_app = lambda do |env|
23
+ input = env['rack.input']
24
+ resp = {}
25
+
26
+ @sha1.reset
27
+ while buf = input.read(@bs)
28
+ @sha1.update(buf)
29
+ end
30
+ resp[:sha1] = @sha1.hexdigest
31
+
32
+ # rewind and read again
33
+ input.rewind
34
+ @sha1.reset
35
+ while buf = input.read(@bs)
36
+ @sha1.update(buf)
37
+ end
38
+
39
+ if resp[:sha1] == @sha1.hexdigest
40
+ resp[:sysread_read_byte_match] = true
41
+ end
42
+
43
+ if expect_size = env['HTTP_X_EXPECT_SIZE']
44
+ if expect_size.to_i == input.size
45
+ resp[:expect_size_match] = true
46
+ end
47
+ end
48
+ resp[:size] = input.size
49
+ resp[:content_md5] = env['HTTP_CONTENT_MD5']
50
+
51
+ [ 200, @hdr.merge({'X-Resp' => resp.inspect}), [] ]
52
+ end
53
+ end
54
+
55
+ def teardown
56
+ redirect_test_io { @server.stop(false) } if @server
57
+ @random.close
58
+ reset_sig_handlers
59
+ end
60
+
61
+ def test_put
62
+ start_server(@sha1_app)
63
+ sock = TCPSocket.new(@addr, @port)
64
+ sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
65
+ @count.times do |i|
66
+ buf = @random.sysread(@bs)
67
+ @sha1.update(buf)
68
+ sock.syswrite(buf)
69
+ end
70
+ read = sock.read.split(/\r\n/)
71
+ assert_equal "HTTP/1.1 200 OK", read[0]
72
+ resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
73
+ assert_equal length, resp[:size]
74
+ assert_equal @sha1.hexdigest, resp[:sha1]
75
+ end
76
+
77
+ def test_put_content_md5
78
+ md5 = Digest::MD5.new
79
+ start_server(@sha1_app)
80
+ sock = TCPSocket.new(@addr, @port)
81
+ sock.syswrite("PUT / HTTP/1.0\r\nTransfer-Encoding: chunked\r\n" \
82
+ "Trailer: Content-MD5\r\n\r\n")
83
+ @count.times do |i|
84
+ buf = @random.sysread(@bs)
85
+ @sha1.update(buf)
86
+ md5.update(buf)
87
+ sock.syswrite("#{'%x' % buf.size}\r\n")
88
+ sock.syswrite(buf << "\r\n")
89
+ end
90
+ sock.syswrite("0\r\n")
91
+
92
+ content_md5 = [ md5.digest! ].pack('m').strip.freeze
93
+ sock.syswrite("Content-MD5: #{content_md5}\r\n\r\n")
94
+ read = sock.read.split(/\r\n/)
95
+ assert_equal "HTTP/1.1 200 OK", read[0]
96
+ resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
97
+ assert_equal length, resp[:size]
98
+ assert_equal @sha1.hexdigest, resp[:sha1]
99
+ assert_equal content_md5, resp[:content_md5]
100
+ end
101
+
102
+ def test_put_trickle_small
103
+ @count, @bs = 2, 128
104
+ start_server(@sha1_app)
105
+ assert_equal 256, length
106
+ sock = TCPSocket.new(@addr, @port)
107
+ hdr = "PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n"
108
+ @count.times do
109
+ buf = @random.sysread(@bs)
110
+ @sha1.update(buf)
111
+ hdr << buf
112
+ sock.syswrite(hdr)
113
+ hdr = ''
114
+ sleep 0.6
115
+ end
116
+ read = sock.read.split(/\r\n/)
117
+ assert_equal "HTTP/1.1 200 OK", read[0]
118
+ resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
119
+ assert_equal length, resp[:size]
120
+ assert_equal @sha1.hexdigest, resp[:sha1]
121
+ end
122
+
123
+ def test_put_keepalive_truncates_small_overwrite
124
+ start_server(@sha1_app)
125
+ sock = TCPSocket.new(@addr, @port)
126
+ to_upload = length + 1
127
+ sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{to_upload}\r\n\r\n")
128
+ @count.times do
129
+ buf = @random.sysread(@bs)
130
+ @sha1.update(buf)
131
+ sock.syswrite(buf)
132
+ end
133
+ sock.syswrite('12345') # write 4 bytes more than we expected
134
+ @sha1.update('1')
135
+
136
+ buf = sock.readpartial(4096)
137
+ while buf !~ /\r\n\r\n/
138
+ buf << sock.readpartial(4096)
139
+ end
140
+ read = buf.split(/\r\n/)
141
+ assert_equal "HTTP/1.1 200 OK", read[0]
142
+ resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
143
+ assert_equal to_upload, resp[:size]
144
+ assert_equal @sha1.hexdigest, resp[:sha1]
145
+ end
146
+
147
+ def test_put_excessive_overwrite_closed
148
+ tmp = Tempfile.new('overwrite_check')
149
+ tmp.sync = true
150
+ start_server(lambda { |env|
151
+ nr = 0
152
+ while buf = env['rack.input'].read(65536)
153
+ nr += buf.size
154
+ end
155
+ tmp.write(nr.to_s)
156
+ [ 200, @hdr, [] ]
157
+ })
158
+ sock = TCPSocket.new(@addr, @port)
159
+ buf = ' ' * @bs
160
+ sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
161
+
162
+ @count.times { sock.syswrite(buf) }
163
+ assert_raise(Errno::ECONNRESET, Errno::EPIPE) do
164
+ ::Unicorn::Const::CHUNK_SIZE.times { sock.syswrite(buf) }
165
+ end
166
+ sock.gets
167
+ tmp.rewind
168
+ assert_equal length, tmp.read.to_i
169
+ end
170
+
171
+ # Despite reading numerous articles and inspecting the 1.9.1-p0 C
172
+ # source, Eric Wong will never trust that we're always handling
173
+ # encoding-aware IO objects correctly. Thus this test uses shell
174
+ # utilities that should always operate on files/sockets on a
175
+ # byte-level.
176
+ def test_uncomfortable_with_onenine_encodings
177
+ # POSIX doesn't require all of these to be present on a system
178
+ which('curl') or return
179
+ which('sha1sum') or return
180
+ which('dd') or return
181
+
182
+ start_server(@sha1_app)
183
+
184
+ tmp = Tempfile.new('dd_dest')
185
+ assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
186
+ "bs=#{@bs}", "count=#{@count}"),
187
+ "dd #@random to #{tmp}")
188
+ sha1_re = %r!\b([a-f0-9]{40})\b!
189
+ sha1_out = `sha1sum #{tmp.path}`
190
+ assert $?.success?, 'sha1sum ran OK'
191
+
192
+ assert_match(sha1_re, sha1_out)
193
+ sha1 = sha1_re.match(sha1_out)[1]
194
+ resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
195
+ assert $?.success?, 'curl ran OK'
196
+ assert_match(%r!\b#{sha1}\b!, resp)
197
+ assert_match(/sysread_read_byte_match/, resp)
198
+
199
+ # small StringIO path
200
+ assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
201
+ "bs=1024", "count=1"),
202
+ "dd #@random to #{tmp}")
203
+ sha1_re = %r!\b([a-f0-9]{40})\b!
204
+ sha1_out = `sha1sum #{tmp.path}`
205
+ assert $?.success?, 'sha1sum ran OK'
206
+
207
+ assert_match(sha1_re, sha1_out)
208
+ sha1 = sha1_re.match(sha1_out)[1]
209
+ resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
210
+ assert $?.success?, 'curl ran OK'
211
+ assert_match(%r!\b#{sha1}\b!, resp)
212
+ assert_match(/sysread_read_byte_match/, resp)
213
+ end
214
+
215
+ def test_chunked_upload_via_curl
216
+ # POSIX doesn't require all of these to be present on a system
217
+ which('curl') or return
218
+ which('sha1sum') or return
219
+ which('dd') or return
220
+
221
+ start_server(@sha1_app)
222
+
223
+ tmp = Tempfile.new('dd_dest')
224
+ assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
225
+ "bs=#{@bs}", "count=#{@count}"),
226
+ "dd #@random to #{tmp}")
227
+ sha1_re = %r!\b([a-f0-9]{40})\b!
228
+ sha1_out = `sha1sum #{tmp.path}`
229
+ assert $?.success?, 'sha1sum ran OK'
230
+
231
+ assert_match(sha1_re, sha1_out)
232
+ sha1 = sha1_re.match(sha1_out)[1]
233
+ cmd = "curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \
234
+ -isSf --no-buffer -T- " \
235
+ "http://#@addr:#@port/"
236
+ resp = Tempfile.new('resp')
237
+ resp.sync = true
238
+
239
+ rd, wr = IO.pipe
240
+ wr.sync = rd.sync = true
241
+ pid = fork {
242
+ STDIN.reopen(rd)
243
+ rd.close
244
+ wr.close
245
+ STDOUT.reopen(resp)
246
+ exec cmd
247
+ }
248
+ rd.close
249
+
250
+ tmp.rewind
251
+ @count.times { |i|
252
+ wr.write(tmp.read(@bs))
253
+ sleep(rand / 10) if 0 == i % 8
254
+ }
255
+ wr.close
256
+ pid, status = Process.waitpid2(pid)
257
+
258
+ resp.rewind
259
+ resp = resp.read
260
+ assert status.success?, 'curl ran OK'
261
+ assert_match(%r!\b#{sha1}\b!, resp)
262
+ assert_match(/sysread_read_byte_match/, resp)
263
+ assert_match(/expect_size_match/, resp)
264
+ end
265
+
266
+ def test_curl_chunked_small
267
+ # POSIX doesn't require all of these to be present on a system
268
+ which('curl') or return
269
+ which('sha1sum') or return
270
+ which('dd') or return
271
+
272
+ start_server(@sha1_app)
273
+
274
+ tmp = Tempfile.new('dd_dest')
275
+ # small StringIO path
276
+ assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
277
+ "bs=1024", "count=1"),
278
+ "dd #@random to #{tmp}")
279
+ sha1_re = %r!\b([a-f0-9]{40})\b!
280
+ sha1_out = `sha1sum #{tmp.path}`
281
+ assert $?.success?, 'sha1sum ran OK'
282
+
283
+ assert_match(sha1_re, sha1_out)
284
+ sha1 = sha1_re.match(sha1_out)[1]
285
+ resp = `curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \
286
+ -isSf --no-buffer -T- http://#@addr:#@port/ < #{tmp.path}`
287
+ assert $?.success?, 'curl ran OK'
288
+ assert_match(%r!\b#{sha1}\b!, resp)
289
+ assert_match(/sysread_read_byte_match/, resp)
290
+ assert_match(/expect_size_match/, resp)
291
+ end
292
+
293
+ private
294
+
295
+ def length
296
+ @bs * @count
297
+ end
298
+
299
+ def start_server(app)
300
+ redirect_test_io do
301
+ @server = HttpServer.new(app, :listeners => [ "#{@addr}:#{@port}" ] )
302
+ @server.start
303
+ end
304
+ end
305
+
306
+ end
@@ -0,0 +1,105 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require './test/test_helper'
4
+ require 'tempfile'
5
+
6
+ class TestUtil < Test::Unit::TestCase
7
+
8
+ EXPECT_FLAGS = File::WRONLY | File::APPEND
9
+ def test_reopen_logs_noop
10
+ tmp = Tempfile.new('')
11
+ fp = File.open(tmp.path, 'ab')
12
+ fp.sync = true
13
+ ext = fp.external_encoding rescue nil
14
+ int = fp.internal_encoding rescue nil
15
+ before = fp.stat.inspect
16
+ Unicorn::Util.reopen_logs
17
+ assert_equal before, File.stat(fp.path).inspect
18
+ assert_equal ext, (fp.external_encoding rescue nil)
19
+ assert_equal int, (fp.internal_encoding rescue nil)
20
+ assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL))
21
+ tmp.close!
22
+ fp.close
23
+ end
24
+
25
+ def test_reopen_logs_renamed
26
+ tmp = Tempfile.new('')
27
+ tmp_path = tmp.path.freeze
28
+ fp = File.open(tmp_path, 'ab')
29
+ fp.sync = true
30
+
31
+ ext = fp.external_encoding rescue nil
32
+ int = fp.internal_encoding rescue nil
33
+ before = fp.stat.inspect
34
+ to = Tempfile.new('')
35
+ File.rename(tmp_path, to.path)
36
+ assert ! File.exist?(tmp_path)
37
+ Unicorn::Util.reopen_logs
38
+ assert_equal tmp_path, tmp.path
39
+ assert File.exist?(tmp_path)
40
+ assert before != File.stat(tmp_path).inspect
41
+ assert_equal fp.stat.inspect, File.stat(tmp_path).inspect
42
+ assert_equal ext, (fp.external_encoding rescue nil)
43
+ assert_equal int, (fp.internal_encoding rescue nil)
44
+ assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL))
45
+ assert fp.sync
46
+ tmp.close!
47
+ to.close!
48
+ fp.close
49
+ end
50
+
51
+ def test_reopen_logs_renamed_with_encoding
52
+ tmp = Tempfile.new('')
53
+ tmp_path = tmp.path.dup.freeze
54
+ Encoding.list.each { |encoding|
55
+ File.open(tmp_path, "a:#{encoding.to_s}") { |fp|
56
+ fp.sync = true
57
+ assert_equal encoding, fp.external_encoding
58
+ assert_nil fp.internal_encoding
59
+ File.unlink(tmp_path)
60
+ assert ! File.exist?(tmp_path)
61
+ Unicorn::Util.reopen_logs
62
+ assert_equal tmp_path, fp.path
63
+ assert File.exist?(tmp_path)
64
+ assert_equal fp.stat.inspect, File.stat(tmp_path).inspect
65
+ assert_equal encoding, fp.external_encoding
66
+ assert_nil fp.internal_encoding
67
+ assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL))
68
+ assert fp.sync
69
+ }
70
+ }
71
+ tmp.close!
72
+ end if STDIN.respond_to?(:external_encoding)
73
+
74
+ def test_reopen_logs_renamed_with_internal_encoding
75
+ tmp = Tempfile.new('')
76
+ tmp_path = tmp.path.dup.freeze
77
+ Encoding.list.each { |ext|
78
+ Encoding.list.each { |int|
79
+ next if ext == int
80
+ File.open(tmp_path, "a:#{ext.to_s}:#{int.to_s}") { |fp|
81
+ fp.sync = true
82
+ assert_equal ext, fp.external_encoding
83
+
84
+ if ext != Encoding::BINARY
85
+ assert_equal int, fp.internal_encoding
86
+ end
87
+
88
+ File.unlink(tmp_path)
89
+ assert ! File.exist?(tmp_path)
90
+ Unicorn::Util.reopen_logs
91
+ assert_equal tmp_path, fp.path
92
+ assert File.exist?(tmp_path)
93
+ assert_equal fp.stat.inspect, File.stat(tmp_path).inspect
94
+ assert_equal ext, fp.external_encoding
95
+ if ext != Encoding::BINARY
96
+ assert_equal int, fp.internal_encoding
97
+ end
98
+ assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL))
99
+ assert fp.sync
100
+ }
101
+ }
102
+ }
103
+ tmp.close!
104
+ end if STDIN.respond_to?(:external_encoding)
105
+ end