unicorn-fotopedia 0.99.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 (163) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +19 -0
  3. data/.gitignore +21 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +32 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +171 -0
  11. data/Documentation/unicorn_rails.1.txt +172 -0
  12. data/FAQ +52 -0
  13. data/GIT-VERSION-GEN +40 -0
  14. data/GNUmakefile +292 -0
  15. data/HACKING +116 -0
  16. data/ISSUES +36 -0
  17. data/KNOWN_ISSUES +50 -0
  18. data/LICENSE +55 -0
  19. data/PHILOSOPHY +145 -0
  20. data/README +149 -0
  21. data/Rakefile +191 -0
  22. data/SIGNALS +109 -0
  23. data/Sandbox +78 -0
  24. data/TODO +5 -0
  25. data/TUNING +70 -0
  26. data/bin/unicorn +126 -0
  27. data/bin/unicorn_rails +203 -0
  28. data/examples/big_app_gc.rb +33 -0
  29. data/examples/echo.ru +27 -0
  30. data/examples/git.ru +13 -0
  31. data/examples/init.sh +58 -0
  32. data/examples/logger_mp_safe.rb +25 -0
  33. data/examples/nginx.conf +139 -0
  34. data/examples/unicorn.conf.rb +78 -0
  35. data/ext/unicorn_http/CFLAGS +13 -0
  36. data/ext/unicorn_http/c_util.h +124 -0
  37. data/ext/unicorn_http/common_field_optimization.h +111 -0
  38. data/ext/unicorn_http/ext_help.h +77 -0
  39. data/ext/unicorn_http/extconf.rb +14 -0
  40. data/ext/unicorn_http/global_variables.h +89 -0
  41. data/ext/unicorn_http/unicorn_http.rl +714 -0
  42. data/ext/unicorn_http/unicorn_http_common.rl +75 -0
  43. data/lib/unicorn.rb +847 -0
  44. data/lib/unicorn/app/exec_cgi.rb +150 -0
  45. data/lib/unicorn/app/inetd.rb +109 -0
  46. data/lib/unicorn/app/old_rails.rb +33 -0
  47. data/lib/unicorn/app/old_rails/static.rb +58 -0
  48. data/lib/unicorn/cgi_wrapper.rb +145 -0
  49. data/lib/unicorn/configurator.rb +421 -0
  50. data/lib/unicorn/const.rb +34 -0
  51. data/lib/unicorn/http_request.rb +72 -0
  52. data/lib/unicorn/http_response.rb +75 -0
  53. data/lib/unicorn/launcher.rb +65 -0
  54. data/lib/unicorn/oob_gc.rb +58 -0
  55. data/lib/unicorn/socket_helper.rb +152 -0
  56. data/lib/unicorn/tee_input.rb +217 -0
  57. data/lib/unicorn/util.rb +90 -0
  58. data/local.mk.sample +62 -0
  59. data/setup.rb +1586 -0
  60. data/t/.gitignore +2 -0
  61. data/t/GNUmakefile +67 -0
  62. data/t/README +42 -0
  63. data/t/bin/content-md5-put +36 -0
  64. data/t/bin/sha1sum.rb +23 -0
  65. data/t/bin/unused_listen +40 -0
  66. data/t/bin/utee +12 -0
  67. data/t/env.ru +3 -0
  68. data/t/my-tap-lib.sh +200 -0
  69. data/t/t0000-http-basic.sh +50 -0
  70. data/t/t0001-reload-bad-config.sh +52 -0
  71. data/t/t0002-config-conflict.sh +49 -0
  72. data/t/test-lib.sh +100 -0
  73. data/test/aggregate.rb +15 -0
  74. data/test/benchmark/README +50 -0
  75. data/test/benchmark/dd.ru +18 -0
  76. data/test/exec/README +5 -0
  77. data/test/exec/test_exec.rb +1038 -0
  78. data/test/rails/app-1.2.3/.gitignore +2 -0
  79. data/test/rails/app-1.2.3/Rakefile +7 -0
  80. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  81. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  82. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  83. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  84. data/test/rails/app-1.2.3/config/database.yml +12 -0
  85. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  86. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  87. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  88. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  89. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  90. data/test/rails/app-1.2.3/public/404.html +1 -0
  91. data/test/rails/app-1.2.3/public/500.html +1 -0
  92. data/test/rails/app-2.0.2/.gitignore +2 -0
  93. data/test/rails/app-2.0.2/Rakefile +7 -0
  94. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  95. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  96. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  97. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  98. data/test/rails/app-2.0.2/config/database.yml +12 -0
  99. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  100. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  101. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  102. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  103. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  104. data/test/rails/app-2.0.2/public/404.html +1 -0
  105. data/test/rails/app-2.0.2/public/500.html +1 -0
  106. data/test/rails/app-2.1.2/.gitignore +2 -0
  107. data/test/rails/app-2.1.2/Rakefile +7 -0
  108. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  109. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  110. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  111. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  112. data/test/rails/app-2.1.2/config/database.yml +12 -0
  113. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  114. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  115. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  116. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  117. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  118. data/test/rails/app-2.1.2/public/404.html +1 -0
  119. data/test/rails/app-2.1.2/public/500.html +1 -0
  120. data/test/rails/app-2.2.2/.gitignore +2 -0
  121. data/test/rails/app-2.2.2/Rakefile +7 -0
  122. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  123. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  124. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  125. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  126. data/test/rails/app-2.2.2/config/database.yml +12 -0
  127. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  128. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  129. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  130. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  131. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  132. data/test/rails/app-2.2.2/public/404.html +1 -0
  133. data/test/rails/app-2.2.2/public/500.html +1 -0
  134. data/test/rails/app-2.3.5/.gitignore +2 -0
  135. data/test/rails/app-2.3.5/Rakefile +7 -0
  136. data/test/rails/app-2.3.5/app/controllers/application_controller.rb +5 -0
  137. data/test/rails/app-2.3.5/app/controllers/foo_controller.rb +36 -0
  138. data/test/rails/app-2.3.5/app/helpers/application_helper.rb +4 -0
  139. data/test/rails/app-2.3.5/config/boot.rb +109 -0
  140. data/test/rails/app-2.3.5/config/database.yml +12 -0
  141. data/test/rails/app-2.3.5/config/environment.rb +17 -0
  142. data/test/rails/app-2.3.5/config/environments/development.rb +7 -0
  143. data/test/rails/app-2.3.5/config/environments/production.rb +6 -0
  144. data/test/rails/app-2.3.5/config/routes.rb +6 -0
  145. data/test/rails/app-2.3.5/db/.gitignore +0 -0
  146. data/test/rails/app-2.3.5/public/404.html +1 -0
  147. data/test/rails/app-2.3.5/public/500.html +1 -0
  148. data/test/rails/app-2.3.5/public/x.txt +1 -0
  149. data/test/rails/test_rails.rb +280 -0
  150. data/test/test_helper.rb +301 -0
  151. data/test/unit/test_configurator.rb +150 -0
  152. data/test/unit/test_http_parser.rb +555 -0
  153. data/test/unit/test_http_parser_ng.rb +443 -0
  154. data/test/unit/test_request.rb +184 -0
  155. data/test/unit/test_response.rb +110 -0
  156. data/test/unit/test_server.rb +291 -0
  157. data/test/unit/test_signals.rb +206 -0
  158. data/test/unit/test_socket_helper.rb +147 -0
  159. data/test/unit/test_tee_input.rb +257 -0
  160. data/test/unit/test_upload.rb +298 -0
  161. data/test/unit/test_util.rb +96 -0
  162. data/unicorn.gemspec +52 -0
  163. metadata +283 -0
@@ -0,0 +1,110 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2005 Zed A. Shaw
4
+ # You can redistribute it and/or modify it under the same terms as Ruby.
5
+ #
6
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
7
+ # for more information.
8
+
9
+ require 'test/test_helper'
10
+
11
+ include Unicorn
12
+
13
+ class ResponseTest < Test::Unit::TestCase
14
+
15
+ def test_response_headers
16
+ out = StringIO.new
17
+ HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, ["cool"]])
18
+ assert out.closed?
19
+
20
+ assert out.length > 0, "output didn't have data"
21
+ end
22
+
23
+ def test_response_string_status
24
+ out = StringIO.new
25
+ HttpResponse.write(out,['200', {}, []])
26
+ assert out.closed?
27
+ assert out.length > 0, "output didn't have data"
28
+ assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/).size
29
+ end
30
+
31
+ def test_response_OFS_set
32
+ old_ofs = $,
33
+ $, = "\f\v"
34
+ out = StringIO.new
35
+ HttpResponse.write(out,[200, {"X-k" => "cd","X-y" => "z"}, ["cool"]])
36
+ assert out.closed?
37
+ resp = out.string
38
+ assert ! resp.include?("\f\v"), "output didn't use $, ($OFS)"
39
+ ensure
40
+ $, = old_ofs
41
+ end
42
+
43
+ def test_response_200
44
+ io = StringIO.new
45
+ HttpResponse.write(io, [200, {}, []])
46
+ assert io.closed?
47
+ assert io.length > 0, "output didn't have data"
48
+ end
49
+
50
+ def test_response_with_default_reason
51
+ code = 400
52
+ io = StringIO.new
53
+ HttpResponse.write(io, [code, {}, []])
54
+ assert io.closed?
55
+ lines = io.string.split(/\r\n/)
56
+ assert_match(/.* Bad Request$/, lines.first,
57
+ "wrong default reason phrase")
58
+ end
59
+
60
+ def test_rack_multivalue_headers
61
+ out = StringIO.new
62
+ HttpResponse.write(out,[200, {"X-Whatever" => "stuff\nbleh"}, []])
63
+ assert out.closed?
64
+ assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
65
+ end
66
+
67
+ # Even though Rack explicitly forbids "Status" in the header hash,
68
+ # some broken clients still rely on it
69
+ def test_status_header_added
70
+ out = StringIO.new
71
+ HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, []])
72
+ assert out.closed?
73
+ assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
74
+ end
75
+
76
+ # we always favor the code returned by the application, since "Status"
77
+ # in the header hash is not allowed by Rack (but not every app is
78
+ # fully Rack-compliant).
79
+ def test_status_header_ignores_app_hash
80
+ out = StringIO.new
81
+ header_hash = {"X-Whatever" => "stuff", 'StaTus' => "666" }
82
+ HttpResponse.write(out,[200, header_hash, []])
83
+ assert out.closed?
84
+ assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
85
+ assert_equal 1, out.string.split(/\r\n/).grep(/^Status:/i).size
86
+ end
87
+
88
+ def test_body_closed
89
+ expect_body = %w(1 2 3 4).join("\n")
90
+ body = StringIO.new(expect_body)
91
+ body.rewind
92
+ out = StringIO.new
93
+ HttpResponse.write(out,[200, {}, body])
94
+ assert out.closed?
95
+ assert body.closed?
96
+ assert_match(expect_body, out.string.split(/\r\n/).last)
97
+ end
98
+
99
+ def test_unknown_status_pass_through
100
+ out = StringIO.new
101
+ HttpResponse.write(out,["666 I AM THE BEAST", {}, [] ])
102
+ assert out.closed?
103
+ headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
104
+ assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0])
105
+ status = headers.grep(/\AStatus:/i).first
106
+ assert status
107
+ assert_equal "Status: 666 I AM THE BEAST", status
108
+ end
109
+
110
+ end
@@ -0,0 +1,291 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2005 Zed A. Shaw
4
+ # You can redistribute it and/or modify it under the same terms as Ruby.
5
+ #
6
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
7
+ # for more information.
8
+
9
+ require 'test/test_helper'
10
+
11
+ include Unicorn
12
+
13
+ class TestHandler
14
+
15
+ def call(env)
16
+ while env['rack.input'].read(4096)
17
+ end
18
+ [200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
19
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
20
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
21
+ raise e
22
+ end
23
+ end
24
+
25
+
26
+ class WebServerTest < Test::Unit::TestCase
27
+
28
+ def setup
29
+ @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
30
+ @port = unused_port
31
+ @tester = TestHandler.new
32
+ redirect_test_io do
33
+ @server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
34
+ @server.start
35
+ end
36
+ end
37
+
38
+ def teardown
39
+ redirect_test_io do
40
+ wait_workers_ready("test_stderr.#$$.log", 1)
41
+ File.truncate("test_stderr.#$$.log", 0)
42
+ @server.stop(true)
43
+ end
44
+ reset_sig_handlers
45
+ end
46
+
47
+ def test_preload_app_config
48
+ teardown
49
+ tmp = Tempfile.new('test_preload_app_config')
50
+ ObjectSpace.undefine_finalizer(tmp)
51
+ app = lambda { ||
52
+ tmp.sysseek(0)
53
+ tmp.truncate(0)
54
+ tmp.syswrite($$)
55
+ lambda { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "#$$\n" ] ] }
56
+ }
57
+ redirect_test_io do
58
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
59
+ @server.start
60
+ end
61
+ results = hit(["http://localhost:#@port/"])
62
+ worker_pid = results[0].to_i
63
+ assert worker_pid != 0
64
+ tmp.sysseek(0)
65
+ loader_pid = tmp.sysread(4096).to_i
66
+ assert loader_pid != 0
67
+ assert_equal worker_pid, loader_pid
68
+ teardown
69
+
70
+ redirect_test_io do
71
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"],
72
+ :preload_app => true)
73
+ @server.start
74
+ end
75
+ results = hit(["http://localhost:#@port/"])
76
+ worker_pid = results[0].to_i
77
+ assert worker_pid != 0
78
+ tmp.sysseek(0)
79
+ loader_pid = tmp.sysread(4096).to_i
80
+ assert_equal $$, loader_pid
81
+ assert worker_pid != loader_pid
82
+ ensure
83
+ tmp.close!
84
+ end
85
+
86
+ def test_broken_app
87
+ teardown
88
+ app = lambda { |env| raise RuntimeError, "hello" }
89
+ # [200, {}, []] }
90
+ redirect_test_io do
91
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
92
+ @server.start
93
+ end
94
+ sock = nil
95
+ assert_nothing_raised do
96
+ sock = TCPSocket.new('127.0.0.1', @port)
97
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
98
+ end
99
+
100
+ assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
101
+ assert_nothing_raised { sock.close }
102
+ end
103
+
104
+ def test_simple_server
105
+ results = hit(["http://localhost:#{@port}/test"])
106
+ assert_equal 'hello!\n', results[0], "Handler didn't really run"
107
+ end
108
+
109
+ def test_client_shutdown_writes
110
+ sock = nil
111
+ buf = nil
112
+ bs = 15609315 * rand
113
+ assert_nothing_raised do
114
+ sock = TCPSocket.new('127.0.0.1', @port)
115
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
116
+ sock.syswrite("Host: example.com\r\n")
117
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
118
+ sock.syswrite("Trailer: X-Foo\r\n")
119
+ sock.syswrite("\r\n")
120
+ sock.syswrite("%x\r\n" % [ bs ])
121
+ sock.syswrite("F" * bs)
122
+ sock.syswrite("\r\n0\r\nX-")
123
+ "Foo: bar\r\n\r\n".each_byte do |x|
124
+ sock.syswrite x.chr
125
+ sleep 0.05
126
+ end
127
+ # we wrote the entire request before shutting down, server should
128
+ # continue to process our request and never hit EOFError on our sock
129
+ sock.shutdown(Socket::SHUT_WR)
130
+ buf = sock.read
131
+ end
132
+ assert_equal 'hello!\n', buf.split(/\r\n\r\n/).last
133
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
134
+ assert_equal 'hello!\n', next_client
135
+ lines = File.readlines("test_stderr.#$$.log")
136
+ assert lines.grep(/^Unicorn::ClientShutdown: /).empty?
137
+ assert_nothing_raised { sock.close }
138
+ end
139
+
140
+ def test_client_shutdown_write_truncates
141
+ sock = nil
142
+ buf = nil
143
+ bs = 15609315 * rand
144
+ assert_nothing_raised do
145
+ sock = TCPSocket.new('127.0.0.1', @port)
146
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
147
+ sock.syswrite("Host: example.com\r\n")
148
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
149
+ sock.syswrite("Trailer: X-Foo\r\n")
150
+ sock.syswrite("\r\n")
151
+ sock.syswrite("%x\r\n" % [ bs ])
152
+ sock.syswrite("F" * (bs / 2.0))
153
+
154
+ # shutdown prematurely, this will force the server to abort
155
+ # processing on us even during app dispatch
156
+ sock.shutdown(Socket::SHUT_WR)
157
+ IO.select([sock], nil, nil, 60) or raise "Timed out"
158
+ buf = sock.read
159
+ end
160
+ assert_equal "", buf
161
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
162
+ assert_equal 'hello!\n', next_client
163
+ lines = File.readlines("test_stderr.#$$.log")
164
+ lines = lines.grep(/^Unicorn::ClientShutdown: bytes_read=\d+/)
165
+ assert_equal 1, lines.size
166
+ assert_match %r{\AUnicorn::ClientShutdown: bytes_read=\d+ true$}, lines[0]
167
+ assert_nothing_raised { sock.close }
168
+ end
169
+
170
+ def test_client_malformed_body
171
+ sock = nil
172
+ buf = nil
173
+ bs = 15653984
174
+ assert_nothing_raised do
175
+ sock = TCPSocket.new('127.0.0.1', @port)
176
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
177
+ sock.syswrite("Host: example.com\r\n")
178
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
179
+ sock.syswrite("Trailer: X-Foo\r\n")
180
+ sock.syswrite("\r\n")
181
+ sock.syswrite("%x\r\n" % [ bs ])
182
+ sock.syswrite("F" * bs)
183
+ end
184
+ begin
185
+ File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
186
+ rescue
187
+ end
188
+ assert_nothing_raised { sock.close }
189
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
190
+ assert_equal 'hello!\n', next_client
191
+ lines = File.readlines("test_stderr.#$$.log")
192
+ lines = lines.grep(/^Unicorn::HttpParserError: .* true$/)
193
+ assert_equal 1, lines.size
194
+ end
195
+
196
+ def do_test(string, chunk, close_after=nil, shutdown_delay=0)
197
+ # Do not use instance variables here, because it needs to be thread safe
198
+ socket = TCPSocket.new("127.0.0.1", @port);
199
+ request = StringIO.new(string)
200
+ chunks_out = 0
201
+
202
+ while data = request.read(chunk)
203
+ chunks_out += socket.write(data)
204
+ socket.flush
205
+ sleep 0.2
206
+ if close_after and chunks_out > close_after
207
+ socket.close
208
+ sleep 1
209
+ end
210
+ end
211
+ sleep(shutdown_delay)
212
+ socket.write(" ") # Some platforms only raise the exception on attempted write
213
+ socket.flush
214
+ end
215
+
216
+ def test_trickle_attack
217
+ do_test(@valid_request, 3)
218
+ end
219
+
220
+ def test_close_client
221
+ assert_raises IOError do
222
+ do_test(@valid_request, 10, 20)
223
+ end
224
+ end
225
+
226
+ def test_bad_client
227
+ redirect_test_io do
228
+ do_test("GET /test HTTP/BAD", 3)
229
+ end
230
+ end
231
+
232
+ def test_logger_set
233
+ assert_equal @server.logger, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
234
+ end
235
+
236
+ def test_logger_changed
237
+ tmp = Logger.new($stdout)
238
+ @server.logger = tmp
239
+ assert_equal tmp, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
240
+ end
241
+
242
+ def test_bad_client_400
243
+ sock = nil
244
+ assert_nothing_raised do
245
+ sock = TCPSocket.new('127.0.0.1', @port)
246
+ sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
247
+ end
248
+ assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
249
+ assert_nothing_raised { sock.close }
250
+ end
251
+
252
+ def test_http_0_9
253
+ sock = nil
254
+ assert_nothing_raised do
255
+ sock = TCPSocket.new('127.0.0.1', @port)
256
+ sock.syswrite("GET /hello\r\n")
257
+ end
258
+ assert_match 'hello!\n', sock.sysread(4096)
259
+ assert_nothing_raised { sock.close }
260
+ end
261
+
262
+ def test_header_is_too_long
263
+ redirect_test_io do
264
+ long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
265
+ assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do
266
+ do_test(long, long.length/2, 10)
267
+ end
268
+ end
269
+ end
270
+
271
+ def test_file_streamed_request
272
+ body = "a" * (Unicorn::Const::MAX_BODY * 2)
273
+ long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
274
+ do_test(long, Unicorn::Const::CHUNK_SIZE * 2 -400)
275
+ end
276
+
277
+ def test_file_streamed_request_bad_body
278
+ body = "a" * (Unicorn::Const::MAX_BODY * 2)
279
+ long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
280
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
281
+ Errno::EBADF) {
282
+ do_test(long, Unicorn::Const::CHUNK_SIZE * 2 -400)
283
+ }
284
+ end
285
+
286
+ def test_listener_names
287
+ assert_equal [ "127.0.0.1:#@port" ], Unicorn.listener_names
288
+ end
289
+
290
+ end
291
+
@@ -0,0 +1,206 @@
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.
5
+ #
6
+ # Ensure we stay sane in the face of signals being sent to us
7
+
8
+ require 'test/test_helper'
9
+
10
+ include Unicorn
11
+
12
+ class Dd
13
+ def initialize(bs, count)
14
+ @count = count
15
+ @buf = ' ' * bs
16
+ end
17
+
18
+ def each(&block)
19
+ @count.times { yield @buf }
20
+ end
21
+ end
22
+
23
+ class SignalsTest < Test::Unit::TestCase
24
+
25
+ def setup
26
+ @bs = 1 * 1024 * 1024
27
+ @count = 100
28
+ @port = unused_port
29
+ @sock = Tempfile.new('unicorn.sock')
30
+ @tmp = Tempfile.new('unicorn.write')
31
+ @tmp.sync = true
32
+ File.unlink(@sock.path)
33
+ File.unlink(@tmp.path)
34
+ @server_opts = {
35
+ :listeners => [ "127.0.0.1:#@port", @sock.path ],
36
+ :after_fork => lambda { |server,worker|
37
+ trap(:HUP) { @tmp.syswrite('.') }
38
+ },
39
+ }
40
+ @server = nil
41
+ end
42
+
43
+ def teardown
44
+ reset_sig_handlers
45
+ end
46
+
47
+ def test_worker_dies_on_dead_master
48
+ pid = fork {
49
+ app = lambda { |env| [ 200, {'X-Pid' => "#$$" }, [] ] }
50
+ opts = @server_opts.merge(:timeout => 3)
51
+ redirect_test_io { HttpServer.new(app, opts).start.join }
52
+ }
53
+ child = sock = buf = t0 = nil
54
+ assert_nothing_raised do
55
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
56
+ sock = TCPSocket.new('127.0.0.1', @port)
57
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
58
+ buf = sock.readpartial(4096)
59
+ sock.close
60
+ buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
61
+ child = $1.to_i
62
+ wait_master_ready("test_stderr.#{pid}.log")
63
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
64
+ Process.kill(:KILL, pid)
65
+ Process.waitpid(pid)
66
+ File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
67
+ t0 = Time.now
68
+ end
69
+ assert child
70
+ assert t0
71
+ assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
72
+ assert((Time.now - t0) < 60)
73
+ end
74
+
75
+ def test_sleepy_kill
76
+ rd, wr = IO.pipe
77
+ pid = fork {
78
+ rd.close
79
+ app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
80
+ redirect_test_io { HttpServer.new(app, @server_opts).start.join }
81
+ }
82
+ sock = buf = nil
83
+ wr.close
84
+ assert_nothing_raised do
85
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
86
+ sock = TCPSocket.new('127.0.0.1', @port)
87
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
88
+ buf = rd.readpartial(1)
89
+ wait_master_ready("test_stderr.#{pid}.log")
90
+ Process.kill(:INT, pid)
91
+ Process.waitpid(pid)
92
+ end
93
+ assert_equal '.', buf
94
+ buf = nil
95
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
96
+ Errno::EBADF) do
97
+ buf = sock.sysread(4096)
98
+ end
99
+ assert_nil buf
100
+ ensure
101
+ end
102
+
103
+ def test_timeout_slow_response
104
+ pid = fork {
105
+ app = lambda { |env| sleep }
106
+ opts = @server_opts.merge(:timeout => 3)
107
+ redirect_test_io { HttpServer.new(app, opts).start.join }
108
+ }
109
+ t0 = Time.now
110
+ sock = nil
111
+ assert_nothing_raised do
112
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
113
+ sock = TCPSocket.new('127.0.0.1', @port)
114
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
115
+ end
116
+
117
+ buf = nil
118
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
119
+ Errno::EBADF) do
120
+ buf = sock.sysread(4096)
121
+ end
122
+ diff = Time.now - t0
123
+ assert_nil buf
124
+ assert diff > 1.0, "diff was #{diff.inspect}"
125
+ assert diff < 60.0
126
+ ensure
127
+ Process.kill(:QUIT, pid) rescue nil
128
+ end
129
+
130
+ def test_response_write
131
+ app = lambda { |env|
132
+ [ 200, { 'Content-Type' => 'text/plain', 'X-Pid' => Process.pid.to_s },
133
+ Dd.new(@bs, @count) ]
134
+ }
135
+ redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
136
+ sock = nil
137
+ assert_nothing_raised do
138
+ wait_workers_ready("test_stderr.#{$$}.log", 1)
139
+ sock = TCPSocket.new('127.0.0.1', @port)
140
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
141
+ end
142
+ buf = ''
143
+ header_len = pid = nil
144
+ assert_nothing_raised do
145
+ buf = sock.sysread(16384, buf)
146
+ pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
147
+ header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
148
+ end
149
+ assert pid > 0, "pid not positive: #{pid.inspect}"
150
+ read = buf.size
151
+ size_before = @tmp.stat.size
152
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
153
+ Errno::EBADF) do
154
+ loop do
155
+ 3.times { Process.kill(:HUP, pid) }
156
+ sock.sysread(16384, buf)
157
+ read += buf.size
158
+ 3.times { Process.kill(:HUP, pid) }
159
+ end
160
+ end
161
+
162
+ redirect_test_io { @server.stop(true) }
163
+ # can't check for == since pending signals get merged
164
+ assert size_before < @tmp.stat.size
165
+ got = read - header_len
166
+ expect = @bs * @count
167
+ assert_equal(expect, got, "expect=#{expect} got=#{got}")
168
+ assert_nothing_raised { sock.close }
169
+ end
170
+
171
+ def test_request_read
172
+ app = lambda { |env|
173
+ while env['rack.input'].read(4096)
174
+ end
175
+ [ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ]
176
+ }
177
+ redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
178
+ pid = nil
179
+
180
+ assert_nothing_raised do
181
+ wait_workers_ready("test_stderr.#{$$}.log", 1)
182
+ sock = TCPSocket.new('127.0.0.1', @port)
183
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
184
+ pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
185
+ sock.close
186
+ end
187
+
188
+ assert pid > 0, "pid not positive: #{pid.inspect}"
189
+ sock = TCPSocket.new('127.0.0.1', @port)
190
+ sock.syswrite("PUT / HTTP/1.0\r\n")
191
+ sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
192
+ 1000.times { Process.kill(:HUP, pid) }
193
+ size_before = @tmp.stat.size
194
+ killer = fork { loop { Process.kill(:HUP, pid); sleep(0.0001) } }
195
+ buf = ' ' * @bs
196
+ @count.times { sock.syswrite(buf) }
197
+ Process.kill(:KILL, killer)
198
+ Process.waitpid2(killer)
199
+ redirect_test_io { @server.stop(true) }
200
+ # can't check for == since pending signals get merged
201
+ assert size_before < @tmp.stat.size
202
+ assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
203
+ sock.close
204
+ end
205
+
206
+ end