unicorn 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/test/aggregate.rb ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/ruby -n
2
+ BEGIN { $tests = $assertions = $failures = $errors = 0 }
3
+
4
+ $_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next
5
+ $tests += $1.to_i
6
+ $assertions += $2.to_i
7
+ $failures += $3.to_i
8
+ $errors += $4.to_i
9
+
10
+ END {
11
+ printf("\n%d tests, %d assertions, %d failures, %d errors\n",
12
+ $tests, $assertions, $failures, $errors)
13
+ }
@@ -0,0 +1,11 @@
1
+ # Benchmark to compare Mongrel performance against
2
+ # previous Mongrel version (the one installed as a gem).
3
+ #
4
+ # Run with:
5
+ #
6
+ # ruby previous.rb [num of request]
7
+ #
8
+
9
+ require File.dirname(__FILE__) + '/utils'
10
+
11
+ benchmark "print", %w(current gem), 1000, [1, 10, 100]
@@ -0,0 +1,11 @@
1
+ #
2
+ # Simple benchmark to compare Mongrel performance against
3
+ # other webservers supported by Rack.
4
+ #
5
+
6
+ require File.dirname(__FILE__) + '/utils'
7
+
8
+ libs = %w(current gem WEBrick EMongrel Thin)
9
+ libs = ARGV if ARGV.any?
10
+
11
+ benchmark "print", libs, 1000, [1, 10, 100]
@@ -0,0 +1,82 @@
1
+
2
+ require 'rubygems'
3
+ require 'rack'
4
+ require 'rack/lobster'
5
+
6
+ def run(handler_name, n=1000, c=1)
7
+ port = 7000
8
+
9
+ server = fork do
10
+ [STDOUT, STDERR].each { |o| o.reopen "/dev/null" }
11
+
12
+ case handler_name
13
+ when 'EMongrel'
14
+ require 'swiftcore/evented_mongrel'
15
+ handler_name = 'Mongrel'
16
+
17
+ when 'Thin'
18
+ require 'thin'
19
+ hander_name = 'Thin'
20
+
21
+ when 'gem' # Load the current Mongrel gem
22
+ require 'mongrel'
23
+ handler_name = 'Mongrel'
24
+
25
+ when 'current' # Load the current Mongrel version under /lib
26
+ require File.dirname(__FILE__) + '/../lib/mongrel'
27
+ handler_name = 'Mongrel'
28
+
29
+ end
30
+
31
+ app = Rack::Lobster.new
32
+
33
+ handler = Rack::Handler.const_get(handler_name)
34
+ handler.run app, :Host => '0.0.0.0', :Port => port
35
+ end
36
+
37
+ sleep 2
38
+
39
+ out = `nice -n20 ab -c #{c} -n #{n} http://127.0.0.1:#{port}/ 2> /dev/null`
40
+
41
+ Process.kill('SIGKILL', server)
42
+ Process.wait
43
+
44
+ if requests = out.match(/^Requests.+?(\d+\.\d+)/)
45
+ requests[1].to_i
46
+ else
47
+ 0
48
+ end
49
+ end
50
+
51
+ def benchmark(type, servers, request, concurrency_levels)
52
+ send "#{type}_benchmark", servers, request, concurrency_levels
53
+ end
54
+
55
+ def graph_benchmark(servers, request, concurrency_levels)
56
+ require '/usr/local/lib/ruby/gems/1.8/gems/gruff-0.2.9/lib/gruff'
57
+ g = Gruff::Area.new
58
+ g.title = "Server benchmark"
59
+
60
+ servers.each do |server|
61
+ g.data(server, concurrency_levels.collect { |c| print '.'; run(server, request, c) })
62
+ end
63
+ puts
64
+
65
+ g.x_axis_label = 'Concurrency'
66
+ g.y_axis_label = 'Requests / sec'
67
+ g.labels = {}
68
+ concurrency_levels.each_with_index { |c, i| g.labels[i] = c.to_s }
69
+
70
+ g.write('bench.png')
71
+ `open bench.png`
72
+ end
73
+
74
+ def print_benchmark(servers, request, concurrency_levels)
75
+ puts 'server request concurrency req/s'
76
+ puts '=' * 42
77
+ concurrency_levels.each do |c|
78
+ servers.each do |server|
79
+ puts "#{server.ljust(8)} #{request} #{c.to_s.ljust(4)} #{run(server, request, c)}"
80
+ end
81
+ end
82
+ end
data/test/exec/README ADDED
@@ -0,0 +1,5 @@
1
+ These tests require the "unicorn" executable script to be installed in
2
+ PATH and rack being directly "require"-able ("rubygems" will not be
3
+ loaded for you). The tester is responsible for setting up RUBYLIB and
4
+ PATH environment variables (or running tests via GNU Make instead of
5
+ Rake).
@@ -0,0 +1,570 @@
1
+ # Copyright (c) 2009 Eric Wong
2
+ STDIN.sync = STDOUT.sync = STDERR.sync = true
3
+ require 'test/test_helper'
4
+ require 'pathname'
5
+ require 'tempfile'
6
+ require 'fileutils'
7
+
8
+ do_test = true
9
+ DEFAULT_TRIES = 1000
10
+ DEFAULT_RES = 0.2
11
+
12
+ $unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
13
+ redirect_test_io do
14
+ do_test = system($unicorn_bin, '-v')
15
+ end
16
+
17
+ unless do_test
18
+ STDERR.puts "#{$unicorn_bin} not found in PATH=#{ENV['PATH']}, " \
19
+ "skipping this test"
20
+ end
21
+
22
+ begin
23
+ require 'rack'
24
+ rescue LoadError
25
+ STDERR.puts "Unable to load Rack, skipping this test"
26
+ do_test = false
27
+ end
28
+
29
+ class ExecTest < Test::Unit::TestCase
30
+ trap('QUIT', 'IGNORE')
31
+
32
+ HI = <<-EOS
33
+ use Rack::ContentLength
34
+ run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, "HI\\n" ] }
35
+ EOS
36
+
37
+ HELLO = <<-EOS
38
+ class Hello
39
+ def call(env)
40
+ [ 200, { 'Content-Type' => 'text/plain' }, "HI\\n" ]
41
+ end
42
+ end
43
+ EOS
44
+
45
+ COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
46
+
47
+ HEAVY_CFG = <<-EOS
48
+ worker_processes 4
49
+ timeout 30
50
+ backlog 128
51
+ logger Logger.new('#{COMMON_TMP.path}')
52
+ before_fork do |server, worker_nr|
53
+ server.logger.info "before_fork: worker=\#{worker_nr}"
54
+ end
55
+ EOS
56
+
57
+ def setup
58
+ @pwd = Dir.pwd
59
+ @tmpfile = Tempfile.new('unicorn_exec_test')
60
+ @tmpdir = @tmpfile.path
61
+ @tmpfile.close!
62
+ Dir.mkdir(@tmpdir)
63
+ Dir.chdir(@tmpdir)
64
+ @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
65
+ @port = unused_port(@addr)
66
+ @sockets = []
67
+ @start_pid = $$
68
+ end
69
+
70
+ def teardown
71
+ return if @start_pid != $$
72
+ Dir.chdir(@pwd)
73
+ FileUtils.rmtree(@tmpdir)
74
+ @sockets.each { |path| File.unlink(path) rescue nil }
75
+ loop do
76
+ Process.kill('-QUIT', 0)
77
+ begin
78
+ Process.waitpid(-1, Process::WNOHANG) or break
79
+ rescue Errno::ECHILD
80
+ break
81
+ end
82
+ end
83
+ end
84
+
85
+ def test_basic
86
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
87
+ pid = fork do
88
+ redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
89
+ end
90
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
91
+ assert_equal String, results[0].class
92
+ assert_shutdown(pid)
93
+ end
94
+
95
+ def test_help
96
+ redirect_test_io do
97
+ assert(system($unicorn_bin, "-h"), "help text returns true")
98
+ end
99
+ assert_equal 0, File.stat("test_stderr.#$$.log").size
100
+ assert_not_equal 0, File.stat("test_stdout.#$$.log").size
101
+ lines = File.readlines("test_stdout.#$$.log")
102
+
103
+ # Be considerate of the on-call technician working from their
104
+ # mobile phone or netbook on a slow connection :)
105
+ assert lines.size <= 24, "help height fits in an ANSI terminal window"
106
+ lines.each do |line|
107
+ assert line.size <= 80, "help width fits in an ANSI terminal window"
108
+ end
109
+ end
110
+
111
+ def test_broken_reexec_config
112
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
113
+ pid_file = "#{@tmpdir}/test.pid"
114
+ old_file = "#{pid_file}.oldbin"
115
+ ucfg = Tempfile.new('unicorn_test_config')
116
+ ucfg.syswrite("listeners %w(#{@addr}:#{@port})\n")
117
+ ucfg.syswrite("pid %(#{pid_file})\n")
118
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
119
+ pid = xfork do
120
+ redirect_test_io do
121
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
122
+ end
123
+ end
124
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
125
+ assert_equal String, results[0].class
126
+
127
+ wait_for_file(pid_file)
128
+ Process.waitpid(pid)
129
+ Process.kill('USR2', File.read(pid_file).to_i)
130
+ wait_for_file(old_file)
131
+ wait_for_file(pid_file)
132
+ Process.kill('QUIT', File.read(old_file).to_i)
133
+
134
+ ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
135
+ current_pid = File.read(pid_file).to_i
136
+ Process.kill('USR2', current_pid)
137
+
138
+ # wait for pid_file to restore itself
139
+ tries = DEFAULT_TRIES
140
+ begin
141
+ while current_pid != File.read(pid_file).to_i
142
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
143
+ end
144
+ rescue Errno::ENOENT
145
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
146
+ end
147
+ assert_equal current_pid, File.read(pid_file).to_i
148
+
149
+ tries = DEFAULT_TRIES
150
+ while File.exist?(old_file)
151
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
152
+ end
153
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
154
+ port2 = unused_port(@addr)
155
+
156
+ # fix the bug
157
+ ucfg.sysseek(0)
158
+ ucfg.truncate(0)
159
+ ucfg.syswrite("listeners %w(#{@addr}:#{@port} #{@addr}:#{port2})\n")
160
+ ucfg.syswrite("pid %(#{pid_file})\n")
161
+ Process.kill('USR2', current_pid)
162
+ wait_for_file(old_file)
163
+ wait_for_file(pid_file)
164
+ new_pid = File.read(pid_file).to_i
165
+ assert_not_equal current_pid, new_pid
166
+ assert_equal current_pid, File.read(old_file).to_i
167
+ results = retry_hit(["http://#{@addr}:#{@port}/",
168
+ "http://#{@addr}:#{port2}/"])
169
+ assert_equal String, results[0].class
170
+ assert_equal String, results[1].class
171
+
172
+ assert_nothing_raised do
173
+ Process.kill('QUIT', current_pid)
174
+ Process.kill('QUIT', new_pid)
175
+ end
176
+ end
177
+
178
+ def test_broken_reexec_ru
179
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
180
+ pid_file = "#{@tmpdir}/test.pid"
181
+ old_file = "#{pid_file}.oldbin"
182
+ ucfg = Tempfile.new('unicorn_test_config')
183
+ ucfg.syswrite("pid %(#{pid_file})\n")
184
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
185
+ pid = xfork do
186
+ redirect_test_io do
187
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
188
+ end
189
+ end
190
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
191
+ assert_equal String, results[0].class
192
+
193
+ wait_for_file(pid_file)
194
+ Process.waitpid(pid)
195
+ Process.kill('USR2', File.read(pid_file).to_i)
196
+ wait_for_file(old_file)
197
+ wait_for_file(pid_file)
198
+ Process.kill('QUIT', File.read(old_file).to_i)
199
+
200
+ File.unlink("config.ru") # break reloading
201
+ current_pid = File.read(pid_file).to_i
202
+ Process.kill('USR2', current_pid)
203
+
204
+ # wait for pid_file to restore itself
205
+ tries = DEFAULT_TRIES
206
+ begin
207
+ while current_pid != File.read(pid_file).to_i
208
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
209
+ end
210
+ rescue Errno::ENOENT
211
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
212
+ end
213
+ assert_equal current_pid, File.read(pid_file).to_i
214
+
215
+ tries = DEFAULT_TRIES
216
+ while File.exist?(old_file)
217
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
218
+ end
219
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
220
+
221
+ # fix the bug
222
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
223
+ Process.kill('USR2', current_pid)
224
+ wait_for_file(old_file)
225
+ wait_for_file(pid_file)
226
+ new_pid = File.read(pid_file).to_i
227
+ assert_not_equal current_pid, new_pid
228
+ assert_equal current_pid, File.read(old_file).to_i
229
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
230
+ assert_equal String, results[0].class
231
+
232
+ assert_nothing_raised do
233
+ Process.kill('QUIT', current_pid)
234
+ Process.kill('QUIT', new_pid)
235
+ end
236
+ end
237
+
238
+ def test_unicorn_config_listeners_overrides_cli
239
+ port2 = unused_port(@addr)
240
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
241
+ # listeners = [ ... ] => should _override_ command-line options
242
+ ucfg = Tempfile.new('unicorn_test_config')
243
+ ucfg.syswrite("listeners %w(#{@addr}:#{@port})\n")
244
+ pid = xfork do
245
+ redirect_test_io do
246
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
247
+ end
248
+ end
249
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
250
+ assert_raises(Errno::ECONNREFUSED) { TCPSocket.new(@addr, port2) }
251
+ assert_equal String, results[0].class
252
+ assert_shutdown(pid)
253
+ end
254
+
255
+ def test_unicorn_config_listen_augments_cli
256
+ port2 = unused_port(@addr)
257
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
258
+ ucfg = Tempfile.new('unicorn_test_config')
259
+ ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
260
+ pid = xfork do
261
+ redirect_test_io do
262
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
263
+ end
264
+ end
265
+ uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
266
+ results = retry_hit(uris)
267
+ assert_equal results.size, uris.size
268
+ assert_equal String, results[0].class
269
+ assert_equal String, results[1].class
270
+ assert_shutdown(pid)
271
+ end
272
+
273
+ def test_weird_config_settings
274
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
275
+ ucfg = Tempfile.new('unicorn_test_config')
276
+ ucfg.syswrite(HEAVY_CFG)
277
+ pid = xfork do
278
+ redirect_test_io do
279
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
280
+ end
281
+ end
282
+
283
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
284
+ assert_equal String, results[0].class
285
+ wait_master_ready(COMMON_TMP.path)
286
+ bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
287
+ assert_equal 4, bf.size
288
+ rotate = Tempfile.new('unicorn_rotate')
289
+ assert_nothing_raised do
290
+ File.rename(COMMON_TMP.path, rotate.path)
291
+ Process.kill('USR1', pid)
292
+ end
293
+ wait_for_file(COMMON_TMP.path)
294
+ assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
295
+ # USR1 should've been passed to all workers
296
+ tries = DEFAULT_TRIES
297
+ log = File.readlines(rotate.path)
298
+ while (tries -= 1) > 0 && log.grep(/rotating logs\.\.\./).size < 4
299
+ sleep DEFAULT_RES
300
+ log = File.readlines(rotate.path)
301
+ end
302
+ assert_equal 4, log.grep(/rotating logs\.\.\./).size
303
+ assert_equal 0, log.grep(/done rotating logs/).size
304
+
305
+ tries = DEFAULT_TRIES
306
+ log = File.readlines(COMMON_TMP.path)
307
+ while (tries -= 1) > 0 && log.grep(/done rotating logs/).size < 4
308
+ sleep DEFAULT_RES
309
+ log = File.readlines(COMMON_TMP.path)
310
+ end
311
+ assert_equal 4, log.grep(/done rotating logs/).size
312
+ assert_equal 0, log.grep(/rotating logs\.\.\./).size
313
+ assert_nothing_raised { Process.kill('QUIT', pid) }
314
+ status = nil
315
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
316
+ assert status.success?, "exited successfully"
317
+ end
318
+
319
+ def test_read_embedded_cli_switches
320
+ File.open("config.ru", "wb") do |fp|
321
+ fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
322
+ fp.syswrite(HI)
323
+ end
324
+ pid = fork { redirect_test_io { exec($unicorn_bin) } }
325
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
326
+ assert_equal String, results[0].class
327
+ assert_shutdown(pid)
328
+ end
329
+
330
+ def test_config_ru_alt_path
331
+ config_path = "#{@tmpdir}/foo.ru"
332
+ File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
333
+ pid = fork do
334
+ redirect_test_io do
335
+ Dir.chdir("/")
336
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
337
+ end
338
+ end
339
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
340
+ assert_equal String, results[0].class
341
+ assert_shutdown(pid)
342
+ end
343
+
344
+ def test_load_module
345
+ libdir = "#{@tmpdir}/lib"
346
+ FileUtils.mkpath([ libdir ])
347
+ config_path = "#{libdir}/hello.rb"
348
+ File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
349
+ pid = fork do
350
+ redirect_test_io do
351
+ Dir.chdir("/")
352
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
353
+ end
354
+ end
355
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
356
+ assert_equal String, results[0].class
357
+ assert_shutdown(pid)
358
+ end
359
+
360
+ def test_reexec
361
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
362
+ pid_file = "#{@tmpdir}/test.pid"
363
+ pid = fork do
364
+ redirect_test_io do
365
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
366
+ end
367
+ end
368
+ reexec_basic_test(pid, pid_file)
369
+ end
370
+
371
+ def test_reexec_alt_config
372
+ config_file = "#{@tmpdir}/foo.ru"
373
+ File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
374
+ pid_file = "#{@tmpdir}/test.pid"
375
+ pid = fork do
376
+ redirect_test_io do
377
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
378
+ end
379
+ end
380
+ reexec_basic_test(pid, pid_file)
381
+ end
382
+
383
+ def test_unicorn_config_file
384
+ pid_file = "#{@tmpdir}/test.pid"
385
+ sock = Tempfile.new('unicorn_test_sock')
386
+ sock_path = sock.path
387
+ sock.close!
388
+ @sockets << sock_path
389
+
390
+ log = Tempfile.new('unicorn_test_log')
391
+ ucfg = Tempfile.new('unicorn_test_config')
392
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
393
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
394
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
395
+ ucfg.close
396
+
397
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
398
+ pid = xfork do
399
+ redirect_test_io do
400
+ exec($unicorn_bin, "-l#{@addr}:#{@port}",
401
+ "-P#{pid_file}", "-c#{ucfg.path}")
402
+ end
403
+ end
404
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
405
+ assert_equal String, results[0].class
406
+ wait_master_ready(log.path)
407
+ assert File.exist?(pid_file), "pid_file created"
408
+ assert_equal pid, File.read(pid_file).to_i
409
+ assert File.socket?(sock_path), "socket created"
410
+ assert_nothing_raised do
411
+ sock = UNIXSocket.new(sock_path)
412
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
413
+ results = sock.sysread(4096)
414
+ end
415
+ assert_equal String, results.class
416
+
417
+ # try reloading the config
418
+ sock = Tempfile.new('unicorn_test_sock')
419
+ new_sock_path = sock.path
420
+ @sockets << new_sock_path
421
+ sock.close!
422
+ new_log = Tempfile.new('unicorn_test_log')
423
+ new_log.sync = true
424
+ assert_equal 0, new_log.size
425
+
426
+ assert_nothing_raised do
427
+ ucfg = File.open(ucfg.path, "wb")
428
+ ucfg.syswrite("listen \"#{new_sock_path}\"\n")
429
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
430
+ ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
431
+ ucfg.close
432
+ Process.kill('HUP', pid)
433
+ end
434
+
435
+ wait_for_file(new_sock_path)
436
+ assert File.socket?(new_sock_path), "socket exists"
437
+ @sockets.each do |path|
438
+ assert_nothing_raised do
439
+ sock = UNIXSocket.new(path)
440
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
441
+ results = sock.sysread(4096)
442
+ end
443
+ assert_equal String, results.class
444
+ end
445
+
446
+ assert_not_equal 0, new_log.size
447
+ reexec_usr2_quit_test(pid, pid_file)
448
+ end
449
+
450
+ def test_daemonize_reexec
451
+ pid_file = "#{@tmpdir}/test.pid"
452
+ log = Tempfile.new('unicorn_test_log')
453
+ ucfg = Tempfile.new('unicorn_test_config')
454
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
455
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
456
+ ucfg.close
457
+
458
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
459
+ pid = xfork do
460
+ redirect_test_io do
461
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
462
+ end
463
+ end
464
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
465
+ assert_equal String, results[0].class
466
+ wait_for_file(pid_file)
467
+ new_pid = File.read(pid_file).to_i
468
+ assert_not_equal pid, new_pid
469
+ pid, status = Process.waitpid2(pid)
470
+ assert status.success?, "original process exited successfully"
471
+ assert_nothing_raised { Process.kill(0, new_pid) }
472
+ reexec_usr2_quit_test(new_pid, pid_file)
473
+ end
474
+
475
+ private
476
+
477
+ # sometimes the server may not come up right away
478
+ def retry_hit(uris = [])
479
+ tries = DEFAULT_TRIES
480
+ begin
481
+ hit(uris)
482
+ rescue Errno::ECONNREFUSED => err
483
+ if (tries -= 1) > 0
484
+ sleep DEFAULT_RES
485
+ retry
486
+ end
487
+ raise err
488
+ end
489
+ end
490
+
491
+ def assert_shutdown(pid)
492
+ wait_master_ready("#{@tmpdir}/test_stderr.#{pid}.log")
493
+ assert_nothing_raised { Process.kill('QUIT', pid) }
494
+ status = nil
495
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
496
+ assert status.success?, "exited successfully"
497
+ end
498
+
499
+ def wait_master_ready(master_log)
500
+ tries = DEFAULT_TRIES
501
+ while (tries -= 1) > 0
502
+ begin
503
+ File.readlines(master_log).grep(/master process ready/)[0] and return
504
+ rescue Errno::ENOENT
505
+ end
506
+ sleep DEFAULT_RES
507
+ end
508
+ raise "master process never became ready"
509
+ end
510
+
511
+ def reexec_usr2_quit_test(pid, pid_file)
512
+ assert File.exist?(pid_file), "pid file OK"
513
+ assert ! File.exist?("#{pid_file}.oldbin"), "oldbin pid file"
514
+ assert_nothing_raised { Process.kill('USR2', pid) }
515
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
516
+ wait_for_file("#{pid_file}.oldbin")
517
+ wait_for_file(pid_file)
518
+
519
+ # kill old master process
520
+ assert_not_equal pid, File.read(pid_file).to_i
521
+ assert_equal pid, File.read("#{pid_file}.oldbin").to_i
522
+ assert_nothing_raised { Process.kill('QUIT', pid) }
523
+ assert_not_equal pid, File.read(pid_file).to_i
524
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
525
+ wait_for_file(pid_file)
526
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
527
+ assert_nothing_raised { Process.kill('QUIT', File.read(pid_file).to_i) }
528
+ end
529
+
530
+ def reexec_basic_test(pid, pid_file)
531
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
532
+ assert_equal String, results[0].class
533
+ assert_nothing_raised { Process.kill(0, pid) }
534
+ master_log = "#{@tmpdir}/test_stderr.#{pid}.log"
535
+ wait_master_ready(master_log)
536
+ File.truncate(master_log, 0)
537
+ nr = 50
538
+ kill_point = 2
539
+ assert_nothing_raised do
540
+ nr.times do |i|
541
+ hit(["http://#{@addr}:#{@port}/#{i}"])
542
+ i == kill_point and Process.kill('HUP', pid)
543
+ end
544
+ end
545
+ wait_master_ready(master_log)
546
+ assert File.exist?(pid_file), "pid=#{pid_file} exists"
547
+ new_pid = File.read(pid_file).to_i
548
+ assert_not_equal pid, new_pid
549
+ assert_nothing_raised { Process.kill(0, new_pid) }
550
+ assert_nothing_raised { Process.kill('QUIT', new_pid) }
551
+ end
552
+
553
+ def wait_for_file(path)
554
+ tries = DEFAULT_TRIES
555
+ while (tries -= 1) > 0 && ! File.exist?(path)
556
+ sleep DEFAULT_RES
557
+ end
558
+ assert File.exist?(path), "path=#{path} exists"
559
+ end
560
+
561
+ def xfork(&block)
562
+ fork do
563
+ ObjectSpace.each_object(Tempfile) do |tmp|
564
+ ObjectSpace.undefine_finalizer(tmp)
565
+ end
566
+ yield
567
+ end
568
+ end
569
+
570
+ end if do_test