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,188 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2009 Eric Wong
4
+ # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5
+ # the GPLv2+ (GPLv3+ preferred)
6
+ #
7
+ # Ensure we stay sane in the face of signals being sent to us
8
+
9
+ require './test/test_helper'
10
+
11
+ include Unicorn
12
+
13
+ class Dd
14
+ def initialize(bs, count)
15
+ @count = count
16
+ @buf = ' ' * bs
17
+ end
18
+
19
+ def each(&block)
20
+ @count.times { yield @buf }
21
+ end
22
+ end
23
+
24
+ class SignalsTest < Test::Unit::TestCase
25
+
26
+ def setup
27
+ @bs = 1 * 1024 * 1024
28
+ @count = 100
29
+ @port = unused_port
30
+ @sock = Tempfile.new('unicorn.sock')
31
+ @tmp = Tempfile.new('unicorn.write')
32
+ @tmp.sync = true
33
+ File.unlink(@sock.path)
34
+ File.unlink(@tmp.path)
35
+ @server_opts = {
36
+ :listeners => [ "127.0.0.1:#@port", @sock.path ],
37
+ :after_fork => lambda { |server,worker|
38
+ trap(:HUP) { @tmp.syswrite('.') }
39
+ },
40
+ }
41
+ @server = nil
42
+ end
43
+
44
+ def teardown
45
+ reset_sig_handlers
46
+ end
47
+
48
+ def test_worker_dies_on_dead_master
49
+ pid = fork {
50
+ app = lambda { |env| [ 200, {'X-Pid' => "#$$" }, [] ] }
51
+ opts = @server_opts.merge(:timeout => 3)
52
+ redirect_test_io { HttpServer.new(app, opts).start.join }
53
+ }
54
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
55
+ sock = TCPSocket.new('127.0.0.1', @port)
56
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
57
+ buf = sock.readpartial(4096)
58
+ assert_nil sock.close
59
+ buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
60
+ child = $1.to_i
61
+ wait_master_ready("test_stderr.#{pid}.log")
62
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
63
+ Process.kill(:KILL, pid)
64
+ Process.waitpid(pid)
65
+ File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
66
+ t0 = Time.now
67
+ assert child
68
+ assert t0
69
+ assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
70
+ assert((Time.now - t0) < 60)
71
+ end
72
+
73
+ def test_sleepy_kill
74
+ rd, wr = IO.pipe
75
+ pid = fork {
76
+ rd.close
77
+ app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
78
+ redirect_test_io { HttpServer.new(app, @server_opts).start.join }
79
+ }
80
+ wr.close
81
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
82
+ sock = TCPSocket.new('127.0.0.1', @port)
83
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
84
+ buf = rd.readpartial(1)
85
+ wait_master_ready("test_stderr.#{pid}.log")
86
+ Process.kill(:INT, pid)
87
+ Process.waitpid(pid)
88
+ assert_equal '.', buf
89
+ buf = nil
90
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
91
+ Errno::EBADF) do
92
+ buf = sock.sysread(4096)
93
+ end
94
+ assert_nil buf
95
+ end
96
+
97
+ def test_timeout_slow_response
98
+ pid = fork {
99
+ app = lambda { |env| sleep }
100
+ opts = @server_opts.merge(:timeout => 3)
101
+ redirect_test_io { HttpServer.new(app, opts).start.join }
102
+ }
103
+ t0 = Time.now
104
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
105
+ sock = TCPSocket.new('127.0.0.1', @port)
106
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
107
+
108
+ buf = nil
109
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
110
+ Errno::EBADF) do
111
+ buf = sock.sysread(4096)
112
+ end
113
+ diff = Time.now - t0
114
+ assert_nil buf
115
+ assert diff > 1.0, "diff was #{diff.inspect}"
116
+ assert diff < 60.0
117
+ ensure
118
+ Process.kill(:TERM, pid) rescue nil
119
+ end
120
+
121
+ def test_response_write
122
+ app = lambda { |env|
123
+ [ 200, { 'Content-Type' => 'text/plain', 'X-Pid' => Process.pid.to_s },
124
+ Dd.new(@bs, @count) ]
125
+ }
126
+ redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
127
+ wait_workers_ready("test_stderr.#{$$}.log", 1)
128
+ sock = TCPSocket.new('127.0.0.1', @port)
129
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
130
+ buf = ''
131
+ header_len = pid = nil
132
+ buf = sock.sysread(16384, buf)
133
+ pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
134
+ header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
135
+ assert pid > 0, "pid not positive: #{pid.inspect}"
136
+ read = buf.size
137
+ size_before = @tmp.stat.size
138
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
139
+ Errno::EBADF) do
140
+ loop do
141
+ 3.times { Process.kill(:HUP, pid) }
142
+ sock.sysread(16384, buf)
143
+ read += buf.size
144
+ 3.times { Process.kill(:HUP, pid) }
145
+ end
146
+ end
147
+
148
+ redirect_test_io { @server.stop(true) }
149
+ # can't check for == since pending signals get merged
150
+ assert size_before < @tmp.stat.size
151
+ got = read - header_len
152
+ expect = @bs * @count
153
+ assert_equal(expect, got, "expect=#{expect} got=#{got}")
154
+ assert_nil sock.close
155
+ end
156
+
157
+ def test_request_read
158
+ app = lambda { |env|
159
+ while env['rack.input'].read(4096)
160
+ end
161
+ [ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ]
162
+ }
163
+ redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
164
+
165
+ wait_workers_ready("test_stderr.#{$$}.log", 1)
166
+ sock = TCPSocket.new('127.0.0.1', @port)
167
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
168
+ pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
169
+ assert_nil sock.close
170
+
171
+ assert pid > 0, "pid not positive: #{pid.inspect}"
172
+ sock = TCPSocket.new('127.0.0.1', @port)
173
+ sock.syswrite("PUT / HTTP/1.0\r\n")
174
+ sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
175
+ 1000.times { Process.kill(:HUP, pid) }
176
+ size_before = @tmp.stat.size
177
+ killer = fork { loop { Process.kill(:HUP, pid); sleep(0.01) } }
178
+ buf = ' ' * @bs
179
+ @count.times { sock.syswrite(buf) }
180
+ Process.kill(:KILL, killer)
181
+ Process.waitpid2(killer)
182
+ redirect_test_io { @server.stop(true) }
183
+ # can't check for == since pending signals get merged
184
+ assert size_before < @tmp.stat.size
185
+ assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
186
+ assert_nil sock.close
187
+ end
188
+ end
@@ -0,0 +1,197 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require './test/test_helper'
4
+ require 'tempfile'
5
+
6
+ class TestSocketHelper < Test::Unit::TestCase
7
+ include Unicorn::SocketHelper
8
+ attr_reader :logger
9
+ GET_SLASH = "GET / HTTP/1.0\r\n\r\n".freeze
10
+
11
+ def setup
12
+ @log_tmp = Tempfile.new 'logger'
13
+ @logger = Logger.new(@log_tmp.path)
14
+ @test_addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
15
+ @test6_addr = ENV['UNICORN_TEST6_ADDR'] || '::1'
16
+ GC.disable
17
+ end
18
+
19
+ def teardown
20
+ GC.enable
21
+ end
22
+
23
+ def test_bind_listen_tcp
24
+ port = unused_port @test_addr
25
+ @tcp_listener_name = "#@test_addr:#{port}"
26
+ @tcp_listener = bind_listen(@tcp_listener_name)
27
+ assert TCPServer === @tcp_listener
28
+ assert_equal @tcp_listener_name, sock_name(@tcp_listener)
29
+ end
30
+
31
+ def test_bind_listen_options
32
+ port = unused_port @test_addr
33
+ tcp_listener_name = "#@test_addr:#{port}"
34
+ tmp = Tempfile.new 'unix.sock'
35
+ unix_listener_name = tmp.path
36
+ File.unlink(tmp.path)
37
+ [ { :backlog => 5 }, { :sndbuf => 4096 }, { :rcvbuf => 4096 },
38
+ { :backlog => 16, :rcvbuf => 4096, :sndbuf => 4096 }
39
+ ].each do |opts|
40
+ tcp_listener = bind_listen(tcp_listener_name, opts)
41
+ assert TCPServer === tcp_listener
42
+ tcp_listener.close
43
+ unix_listener = bind_listen(unix_listener_name, opts)
44
+ assert UNIXServer === unix_listener
45
+ unix_listener.close
46
+ end
47
+ end
48
+
49
+ def test_bind_listen_unix
50
+ old_umask = File.umask(0777)
51
+ tmp = Tempfile.new 'unix.sock'
52
+ @unix_listener_path = tmp.path
53
+ File.unlink(@unix_listener_path)
54
+ @unix_listener = bind_listen(@unix_listener_path)
55
+ assert UNIXServer === @unix_listener
56
+ assert_equal @unix_listener_path, sock_name(@unix_listener)
57
+ assert File.readable?(@unix_listener_path), "not readable"
58
+ assert File.writable?(@unix_listener_path), "not writable"
59
+ assert_equal 0777, File.umask
60
+ ensure
61
+ File.umask(old_umask)
62
+ end
63
+
64
+ def test_bind_listen_unix_umask
65
+ old_umask = File.umask(0777)
66
+ tmp = Tempfile.new 'unix.sock'
67
+ @unix_listener_path = tmp.path
68
+ File.unlink(@unix_listener_path)
69
+ @unix_listener = bind_listen(@unix_listener_path, :umask => 077)
70
+ assert UNIXServer === @unix_listener
71
+ assert_equal @unix_listener_path, sock_name(@unix_listener)
72
+ assert_equal 0140700, File.stat(@unix_listener_path).mode
73
+ assert_equal 0777, File.umask
74
+ ensure
75
+ File.umask(old_umask)
76
+ end
77
+
78
+ def test_bind_listen_unix_idempotent
79
+ test_bind_listen_unix
80
+ a = bind_listen(@unix_listener)
81
+ assert_equal a.fileno, @unix_listener.fileno
82
+ unix_server = server_cast(@unix_listener)
83
+ assert UNIXServer === unix_server
84
+ a = bind_listen(unix_server)
85
+ assert_equal a.fileno, unix_server.fileno
86
+ assert_equal a.fileno, @unix_listener.fileno
87
+ end
88
+
89
+ def test_bind_listen_tcp_idempotent
90
+ test_bind_listen_tcp
91
+ a = bind_listen(@tcp_listener)
92
+ assert_equal a.fileno, @tcp_listener.fileno
93
+ tcp_server = server_cast(@tcp_listener)
94
+ assert TCPServer === tcp_server
95
+ a = bind_listen(tcp_server)
96
+ assert_equal a.fileno, tcp_server.fileno
97
+ assert_equal a.fileno, @tcp_listener.fileno
98
+ end
99
+
100
+ def test_bind_listen_unix_rebind
101
+ test_bind_listen_unix
102
+ new_listener = nil
103
+ assert_raises(Errno::EADDRINUSE) do
104
+ new_listener = bind_listen(@unix_listener_path)
105
+ end
106
+
107
+ File.unlink(@unix_listener_path)
108
+ new_listener = bind_listen(@unix_listener_path)
109
+
110
+ assert UNIXServer === new_listener
111
+ assert new_listener.fileno != @unix_listener.fileno
112
+ assert_equal sock_name(new_listener), sock_name(@unix_listener)
113
+ assert_equal @unix_listener_path, sock_name(new_listener)
114
+ pid = fork do
115
+ client = server_cast(new_listener).accept
116
+ client.syswrite('abcde')
117
+ exit 0
118
+ end
119
+ s = UNIXSocket.new(@unix_listener_path)
120
+ IO.select([s])
121
+ assert_equal 'abcde', s.sysread(5)
122
+ pid, status = Process.waitpid2(pid)
123
+ assert status.success?
124
+ end
125
+
126
+ def test_server_cast
127
+ test_bind_listen_unix
128
+ test_bind_listen_tcp
129
+ unix_listener_socket = Socket.for_fd(@unix_listener.fileno)
130
+ assert Socket === unix_listener_socket
131
+ @unix_server = server_cast(unix_listener_socket)
132
+ assert_equal @unix_listener.fileno, @unix_server.fileno
133
+ assert UNIXServer === @unix_server
134
+ assert_equal(@unix_server.path, @unix_listener.path,
135
+ "##{@unix_server.path} != #{@unix_listener.path}")
136
+ assert File.socket?(@unix_server.path)
137
+ assert_equal @unix_listener_path, sock_name(@unix_server)
138
+
139
+ tcp_listener_socket = Socket.for_fd(@tcp_listener.fileno)
140
+ assert Socket === tcp_listener_socket
141
+ @tcp_server = server_cast(tcp_listener_socket)
142
+ assert_equal @tcp_listener.fileno, @tcp_server.fileno
143
+ assert TCPServer === @tcp_server
144
+ assert_equal @tcp_listener_name, sock_name(@tcp_server)
145
+ end
146
+
147
+ def test_sock_name
148
+ test_server_cast
149
+ sock_name(@unix_server)
150
+ end
151
+
152
+ def test_tcp_defer_accept_default
153
+ port = unused_port @test_addr
154
+ name = "#@test_addr:#{port}"
155
+ sock = bind_listen(name)
156
+ cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
157
+ assert cur >= 1
158
+ end if defined?(TCP_DEFER_ACCEPT)
159
+
160
+ def test_tcp_defer_accept_disable
161
+ port = unused_port @test_addr
162
+ name = "#@test_addr:#{port}"
163
+ sock = bind_listen(name, :tcp_defer_accept => false)
164
+ cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
165
+ assert_equal 0, cur
166
+ end if defined?(TCP_DEFER_ACCEPT)
167
+
168
+ def test_tcp_defer_accept_nr
169
+ port = unused_port @test_addr
170
+ name = "#@test_addr:#{port}"
171
+ sock = bind_listen(name, :tcp_defer_accept => 60)
172
+ cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
173
+ assert cur > 1
174
+ end if defined?(TCP_DEFER_ACCEPT)
175
+
176
+ def test_ipv6only
177
+ port = begin
178
+ unused_port "#@test6_addr"
179
+ rescue Errno::EINVAL
180
+ return
181
+ end
182
+ sock = bind_listen "[#@test6_addr]:#{port}", :ipv6only => true
183
+ cur = sock.getsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY).unpack('i')[0]
184
+ assert_equal 1, cur
185
+ rescue Errno::EAFNOSUPPORT
186
+ end
187
+
188
+ def test_reuseport
189
+ port = unused_port @test_addr
190
+ name = "#@test_addr:#{port}"
191
+ sock = bind_listen(name, :reuseport => true)
192
+ cur = sock.getsockopt(:SOL_SOCKET, :SO_REUSEPORT).int
193
+ assert_operator cur, :>, 0
194
+ rescue Errno::ENOPROTOOPT
195
+ # kernel does not support SO_REUSEPORT (older Linux)
196
+ end if defined?(Socket::SO_REUSEPORT)
197
+ end
@@ -0,0 +1,203 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'test/unit'
4
+ require 'digest/sha1'
5
+ require 'unicorn'
6
+
7
+ class TestStreamInput < Test::Unit::TestCase
8
+ def setup
9
+ @rs = $/
10
+ @env = {}
11
+ @rd, @wr = Kgio::UNIXSocket.pair
12
+ @rd.sync = @wr.sync = true
13
+ @start_pid = $$
14
+ end
15
+
16
+ def teardown
17
+ return if $$ != @start_pid
18
+ $/ = @rs
19
+ @rd.close rescue nil
20
+ @wr.close rescue nil
21
+ Process.waitall
22
+ end
23
+
24
+ def test_read_negative
25
+ r = init_request('hello')
26
+ si = Unicorn::StreamInput.new(@rd, r)
27
+ assert_raises(ArgumentError) { si.read(-1) }
28
+ assert_equal 'hello', si.read
29
+ end
30
+
31
+ def test_read_small
32
+ r = init_request('hello')
33
+ si = Unicorn::StreamInput.new(@rd, r)
34
+ assert_equal 'hello', si.read
35
+ assert_equal '', si.read
36
+ assert_nil si.read(5)
37
+ assert_nil si.gets
38
+ end
39
+
40
+ def test_gets_oneliner
41
+ r = init_request('hello')
42
+ si = Unicorn::StreamInput.new(@rd, r)
43
+ assert_equal 'hello', si.gets
44
+ assert_nil si.gets
45
+ end
46
+
47
+ def test_gets_multiline
48
+ r = init_request("a\nb\n\n")
49
+ si = Unicorn::StreamInput.new(@rd, r)
50
+ assert_equal "a\n", si.gets
51
+ assert_equal "b\n", si.gets
52
+ assert_equal "\n", si.gets
53
+ assert_nil si.gets
54
+ end
55
+
56
+ def test_gets_empty_rs
57
+ $/ = nil
58
+ r = init_request("a\nb\n\n")
59
+ si = Unicorn::StreamInput.new(@rd, r)
60
+ assert_equal "a\nb\n\n", si.gets
61
+ assert_nil si.gets
62
+ end
63
+
64
+ def test_read_with_equal_len
65
+ r = init_request("abcde")
66
+ si = Unicorn::StreamInput.new(@rd, r)
67
+ assert_equal "abcde", si.read(5)
68
+ assert_nil si.read(5)
69
+ end
70
+
71
+ def test_big_body_multi
72
+ r = init_request('.', Unicorn::Const::MAX_BODY + 1)
73
+ si = Unicorn::StreamInput.new(@rd, r)
74
+ assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
75
+ assert ! @parser.body_eof?
76
+ nr = Unicorn::Const::MAX_BODY / 4
77
+ pid = fork {
78
+ @rd.close
79
+ nr.times { @wr.write('....') }
80
+ @wr.close
81
+ }
82
+ @wr.close
83
+ assert_equal '.', si.read(1)
84
+ nr.times { |x|
85
+ assert_equal '....', si.read(4), "nr=#{x}"
86
+ }
87
+ assert_nil si.read(1)
88
+ pid, status = Process.waitpid2(pid)
89
+ assert status.success?
90
+ end
91
+
92
+ def test_gets_long
93
+ r = init_request("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size)
94
+ si = Unicorn::StreamInput.new(@rd, r)
95
+ status = line = nil
96
+ pid = fork {
97
+ @rd.close
98
+ 3.times { @wr.write("ffff" * 4096) }
99
+ @wr.write "#$/foo#$/"
100
+ @wr.close
101
+ }
102
+ @wr.close
103
+ line = si.gets
104
+ assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
105
+ assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
106
+ line = si.gets
107
+ assert_equal "foo#$/", line
108
+ assert_nil si.gets
109
+ pid, status = Process.waitpid2(pid)
110
+ assert status.success?
111
+ end
112
+
113
+ def test_read_with_buffer
114
+ r = init_request('hello')
115
+ si = Unicorn::StreamInput.new(@rd, r)
116
+ buf = ''
117
+ rv = si.read(4, buf)
118
+ assert_equal 'hell', rv
119
+ assert_equal 'hell', buf
120
+ assert_equal rv.object_id, buf.object_id
121
+ assert_equal 'o', si.read
122
+ assert_equal nil, si.read(5, buf)
123
+ end
124
+
125
+ def test_read_with_buffer_clobbers
126
+ r = init_request('hello')
127
+ si = Unicorn::StreamInput.new(@rd, r)
128
+ buf = 'foo'
129
+ assert_equal 'hello', si.read(nil, buf)
130
+ assert_equal 'hello', buf
131
+ assert_equal '', si.read(nil, buf)
132
+ assert_equal '', buf
133
+ buf = 'asdf'
134
+ assert_nil si.read(5, buf)
135
+ assert_equal '', buf
136
+ end
137
+
138
+ def test_read_zero
139
+ r = init_request('hello')
140
+ si = Unicorn::StreamInput.new(@rd, r)
141
+ assert_equal '', si.read(0)
142
+ buf = 'asdf'
143
+ rv = si.read(0, buf)
144
+ assert_equal rv.object_id, buf.object_id
145
+ assert_equal '', buf
146
+ assert_equal 'hello', si.read
147
+ assert_nil si.read(5)
148
+ assert_equal '', si.read(0)
149
+ buf = 'hello'
150
+ rv = si.read(0, buf)
151
+ assert_equal rv.object_id, buf.object_id
152
+ assert_equal '', rv
153
+ end
154
+
155
+ def test_gets_read_mix
156
+ r = init_request("hello\nasdfasdf")
157
+ si = Unicorn::StreamInput.new(@rd, r)
158
+ assert_equal "hello\n", si.gets
159
+ assert_equal "asdfasdf", si.read(9)
160
+ assert_nil si.read(9)
161
+ end
162
+
163
+ def test_gets_read_mix_chunked
164
+ r = @parser = Unicorn::HttpParser.new
165
+ body = "6\r\nhello"
166
+ @buf = "POST / HTTP/1.1\r\n" \
167
+ "Host: localhost\r\n" \
168
+ "Transfer-Encoding: chunked\r\n" \
169
+ "\r\n#{body}"
170
+ assert_equal @env, @parser.headers(@env, @buf)
171
+ assert_equal body, @buf
172
+ si = Unicorn::StreamInput.new(@rd, r)
173
+ @wr.syswrite "\n\r\n"
174
+ assert_equal "hello\n", si.gets
175
+ @wr.syswrite "8\r\nasdfasdf\r\n"
176
+ assert_equal"asdfasdf", si.read(9) + si.read(9)
177
+ @wr.syswrite "0\r\n\r\n"
178
+ assert_nil si.read(9)
179
+ end
180
+
181
+ def test_gets_read_mix_big
182
+ r = init_request("hello\n#{'.' * 65536}")
183
+ si = Unicorn::StreamInput.new(@rd, r)
184
+ assert_equal "hello\n", si.gets
185
+ assert_equal '.' * 16384, si.read(16384)
186
+ assert_equal '.' * 16383, si.read(16383)
187
+ assert_equal '.' * 16384, si.read(16384)
188
+ assert_equal '.' * 16385, si.read(16385)
189
+ assert_nil si.gets
190
+ end
191
+
192
+ def init_request(body, size = nil)
193
+ @parser = Unicorn::HttpParser.new
194
+ body = body.to_s.freeze
195
+ @buf = "POST / HTTP/1.1\r\n" \
196
+ "Host: localhost\r\n" \
197
+ "Content-Length: #{size || body.size}\r\n" \
198
+ "\r\n#{body}"
199
+ assert_equal @env, @parser.headers(@env, @buf)
200
+ assert_equal body, @buf
201
+ @parser
202
+ end
203
+ end