1 # -*- encoding: binary -*-
3 # Copyright (c) 2009 Eric Wong
4 FLOCK_PATH = File.expand_path(__FILE__)
5 require 'test/test_helper'
8 $unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
10 do_test = system($unicorn_bin, '-v')
14 warn "#{$unicorn_bin} not found in PATH=#{ENV['PATH']}, " \
18 unless try_require('rack')
19 warn "Unable to load Rack, skipping this test"
23 class ExecTest < Test::Unit::TestCase
27 use Rack::ContentLength
28 run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ] }
31 SHOW_RACK_ENV = <<-EOS
32 use Rack::ContentLength
34 [ 200, { 'Content-Type' => 'text/plain' }, [ ENV['RACK_ENV'] ] ]
41 [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ]
46 COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
51 logger Logger.new('#{COMMON_TMP.path}')
52 before_fork do |server, worker|
53 server.logger.info "before_fork: worker=\#{worker.nr}"
57 WORKING_DIRECTORY_CHECK_RU = <<-EOS
58 use Rack::ContentLength
62 b = ::File.stat(Dir.pwd)
63 if (a.ino == b.ino && a.dev == b.dev)
64 [ 200, { 'Content-Type' => 'text/plain' }, [ pwd ] ]
66 [ 404, { 'Content-Type' => 'text/plain' }, [] ]
73 @tmpfile = Tempfile.new('unicorn_exec_test')
74 @tmpdir = @tmpfile.path
78 @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
79 @port = unused_port(@addr)
85 return if @start_pid != $$
87 FileUtils.rmtree(@tmpdir)
88 @sockets.each { |path| File.unlink(path) rescue nil }
90 Process.kill('-QUIT', 0)
92 Process.waitpid(-1, Process::WNOHANG) or break
99 def test_working_directory_rel_path_config_file
100 other = Tempfile.new('unicorn.wd')
101 File.unlink(other.path)
102 Dir.mkdir(other.path)
103 File.open("config.ru", "wb") do |fp|
104 fp.syswrite WORKING_DIRECTORY_CHECK_RU
106 FileUtils.cp("config.ru", other.path + "/config.ru")
109 tmp = File.open('unicorn.config', 'wb')
111 working_directory '#@tmpdir'
112 listen '#@addr:#@port'
114 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
115 wait_workers_ready("test_stderr.#{pid}.log", 1)
116 results = hit(["http://#@addr:#@port/"])
117 assert_equal @tmpdir, results.first
118 File.truncate("test_stderr.#{pid}.log", 0)
123 working_directory '#{other.path}'
124 listen '#@addr:#@port'
127 Process.kill(:HUP, pid)
129 re = /config_file=(.+) would not be accessible in working_directory=(.+)/
132 lines = File.readlines("test_stderr.#{pid}.log")
135 File.truncate("test_stderr.#{pid}.log", 0)
136 FileUtils.cp('unicorn.config', other.path + "/unicorn.config")
137 Process.kill(:HUP, pid)
138 wait_workers_ready("test_stderr.#{pid}.log", 1)
139 results = hit(["http://#@addr:#@port/"])
140 assert_equal other.path, results.first
142 Process.kill(:QUIT, pid)
144 FileUtils.rmtree(other.path)
147 def test_working_directory
148 other = Tempfile.new('unicorn.wd')
149 File.unlink(other.path)
150 Dir.mkdir(other.path)
151 File.open("config.ru", "wb") do |fp|
152 fp.syswrite WORKING_DIRECTORY_CHECK_RU
154 FileUtils.cp("config.ru", other.path + "/config.ru")
155 tmp = Tempfile.new('unicorn.config')
157 working_directory '#@tmpdir'
158 listen '#@addr:#@port'
160 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
161 wait_workers_ready("test_stderr.#{pid}.log", 1)
162 results = hit(["http://#@addr:#@port/"])
163 assert_equal @tmpdir, results.first
164 File.truncate("test_stderr.#{pid}.log", 0)
169 working_directory '#{other.path}'
170 listen '#@addr:#@port'
173 Process.kill(:HUP, pid)
174 wait_workers_ready("test_stderr.#{pid}.log", 1)
175 results = hit(["http://#@addr:#@port/"])
176 assert_equal other.path, results.first
178 Process.kill(:QUIT, pid)
180 FileUtils.rmtree(other.path)
183 def test_working_directory_controls_relative_paths
184 other = Tempfile.new('unicorn.wd')
185 File.unlink(other.path)
186 Dir.mkdir(other.path)
187 File.open("config.ru", "wb") do |fp|
188 fp.syswrite WORKING_DIRECTORY_CHECK_RU
190 FileUtils.cp("config.ru", other.path + "/config.ru")
191 system('mkfifo', "#{other.path}/fifo")
192 tmp = Tempfile.new('unicorn.config')
195 stderr_path "stderr_log_here"
196 stdout_path "stdout_log_here"
197 working_directory '#{other.path}'
198 listen '#@addr:#@port'
199 after_fork do |server, worker|
200 File.open("fifo", "wb").close
203 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
204 File.open("#{other.path}/fifo", "rb").close
206 assert ! File.exist?("stderr_log_here")
207 assert ! File.exist?("stdout_log_here")
208 assert ! File.exist?("pid_file_here")
210 assert ! File.exist?("#@tmpdir/stderr_log_here")
211 assert ! File.exist?("#@tmpdir/stdout_log_here")
212 assert ! File.exist?("#@tmpdir/pid_file_here")
214 assert File.exist?("#{other.path}/pid_file_here")
215 assert_equal "#{pid}\n", File.read("#{other.path}/pid_file_here")
216 assert File.exist?("#{other.path}/stderr_log_here")
217 assert File.exist?("#{other.path}/stdout_log_here")
218 wait_master_ready("#{other.path}/stderr_log_here")
220 Process.kill(:QUIT, pid)
222 FileUtils.rmtree(other.path)
226 def test_exit_signals
227 %w(INT TERM QUIT).each do |sig|
228 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
229 pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
230 wait_master_ready("test_stderr.#{pid}.log")
231 wait_workers_ready("test_stderr.#{pid}.log", 1)
233 assert_nothing_raised do
234 Process.kill(sig, pid)
235 pid, status = Process.waitpid2(pid)
237 reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
238 assert_equal 1, reaped.size
239 assert status.exited?
244 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
246 redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
248 results = retry_hit(["http://#{@addr}:#{@port}/"])
249 assert_equal String, results[0].class
253 def test_rack_env_unset
254 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
255 pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
256 results = retry_hit(["http://#{@addr}:#{@port}/"])
257 assert_equal "development", results.first
261 def test_rack_env_cli_set
262 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
264 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
266 results = retry_hit(["http://#{@addr}:#{@port}/"])
267 assert_equal "asdf", results.first
271 def test_rack_env_ENV_set
272 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
274 ENV["RACK_ENV"] = "foobar"
275 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") }
277 results = retry_hit(["http://#{@addr}:#{@port}/"])
278 assert_equal "foobar", results.first
282 def test_rack_env_cli_override_ENV
283 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
285 ENV["RACK_ENV"] = "foobar"
286 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
288 results = retry_hit(["http://#{@addr}:#{@port}/"])
289 assert_equal "asdf", results.first
294 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
295 pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
296 log = "test_stderr.#{pid}.log"
297 wait_master_ready(log)
299 assert_nothing_raised { Process.kill(:TTIN, pid) }
300 wait_workers_ready(log, i)
302 File.truncate(log, 0)
304 [ 2, 1, 0].each { |i|
305 assert_nothing_raised { Process.kill(:TTOU, pid) }
306 DEFAULT_TRIES.times {
308 reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
309 break if reaped.size == 1
311 assert_equal 1, reaped.size
317 assert(system($unicorn_bin, "-h"), "help text returns true")
319 assert_equal 0, File.stat("test_stderr.#$$.log").size
320 assert_not_equal 0, File.stat("test_stdout.#$$.log").size
321 lines = File.readlines("test_stdout.#$$.log")
323 # Be considerate of the on-call technician working from their
324 # mobile phone or netbook on a slow connection :)
325 assert lines.size <= 24, "help height fits in an ANSI terminal window"
327 assert line.size <= 80, "help width fits in an ANSI terminal window"
331 def test_broken_reexec_config
332 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
333 pid_file = "#{@tmpdir}/test.pid"
334 old_file = "#{pid_file}.oldbin"
335 ucfg = Tempfile.new('unicorn_test_config')
336 ucfg.syswrite("listen %(#@addr:#@port)\n")
337 ucfg.syswrite("pid %(#{pid_file})\n")
338 ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
341 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
344 results = retry_hit(["http://#{@addr}:#{@port}/"])
345 assert_equal String, results[0].class
347 wait_for_file(pid_file)
349 Process.kill(:USR2, File.read(pid_file).to_i)
350 wait_for_file(old_file)
351 wait_for_file(pid_file)
352 old_pid = File.read(old_file).to_i
353 Process.kill(:QUIT, old_pid)
354 wait_for_death(old_pid)
356 ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
357 current_pid = File.read(pid_file).to_i
358 Process.kill(:USR2, current_pid)
360 # wait for pid_file to restore itself
361 tries = DEFAULT_TRIES
363 while current_pid != File.read(pid_file).to_i
364 sleep(DEFAULT_RES) and (tries -= 1) > 0
367 (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
369 assert_equal current_pid, File.read(pid_file).to_i
371 tries = DEFAULT_TRIES
372 while File.exist?(old_file)
373 (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
375 assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
376 port2 = unused_port(@addr)
381 ucfg.syswrite("listen %(#@addr:#@port)\n")
382 ucfg.syswrite("listen %(#@addr:#{port2})\n")
383 ucfg.syswrite("pid %(#{pid_file})\n")
384 assert_nothing_raised { Process.kill(:USR2, current_pid) }
386 wait_for_file(old_file)
387 wait_for_file(pid_file)
388 new_pid = File.read(pid_file).to_i
389 assert_not_equal current_pid, new_pid
390 assert_equal current_pid, File.read(old_file).to_i
391 results = retry_hit(["http://#{@addr}:#{@port}/",
392 "http://#{@addr}:#{port2}/"])
393 assert_equal String, results[0].class
394 assert_equal String, results[1].class
396 assert_nothing_raised do
397 Process.kill(:QUIT, current_pid)
398 Process.kill(:QUIT, new_pid)
402 def test_broken_reexec_ru
403 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
404 pid_file = "#{@tmpdir}/test.pid"
405 old_file = "#{pid_file}.oldbin"
406 ucfg = Tempfile.new('unicorn_test_config')
407 ucfg.syswrite("pid %(#{pid_file})\n")
408 ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
411 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
414 results = retry_hit(["http://#{@addr}:#{@port}/"])
415 assert_equal String, results[0].class
417 wait_for_file(pid_file)
419 Process.kill(:USR2, File.read(pid_file).to_i)
420 wait_for_file(old_file)
421 wait_for_file(pid_file)
422 old_pid = File.read(old_file).to_i
423 Process.kill(:QUIT, old_pid)
424 wait_for_death(old_pid)
426 File.unlink("config.ru") # break reloading
427 current_pid = File.read(pid_file).to_i
428 Process.kill(:USR2, current_pid)
430 # wait for pid_file to restore itself
431 tries = DEFAULT_TRIES
433 while current_pid != File.read(pid_file).to_i
434 sleep(DEFAULT_RES) and (tries -= 1) > 0
437 (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
440 tries = DEFAULT_TRIES
441 while File.exist?(old_file)
442 (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
444 assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
445 assert_equal current_pid, File.read(pid_file).to_i
448 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
449 assert_nothing_raised { Process.kill(:USR2, current_pid) }
450 wait_for_file(old_file)
451 wait_for_file(pid_file)
452 new_pid = File.read(pid_file).to_i
453 assert_not_equal current_pid, new_pid
454 assert_equal current_pid, File.read(old_file).to_i
455 results = retry_hit(["http://#{@addr}:#{@port}/"])
456 assert_equal String, results[0].class
458 assert_nothing_raised do
459 Process.kill(:QUIT, current_pid)
460 Process.kill(:QUIT, new_pid)
464 def test_unicorn_config_listener_swap
465 port_cli = unused_port
466 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
467 ucfg = Tempfile.new('unicorn_test_config')
468 ucfg.syswrite("listen '#@addr:#@port'\n")
471 exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
474 results = retry_hit(["http://#@addr:#{port_cli}/"])
475 assert_equal String, results[0].class
476 results = retry_hit(["http://#@addr:#@port/"])
477 assert_equal String, results[0].class
479 port2 = unused_port(@addr)
482 ucfg.syswrite("listen '#@addr:#{port2}'\n")
483 Process.kill(:HUP, pid)
485 results = retry_hit(["http://#@addr:#{port2}/"])
486 assert_equal String, results[0].class
487 results = retry_hit(["http://#@addr:#{port_cli}/"])
488 assert_equal String, results[0].class
489 assert_nothing_raised do
490 reuse = TCPServer.new(@addr, @port)
496 def test_unicorn_config_listen_with_options
497 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
498 ucfg = Tempfile.new('unicorn_test_config')
499 ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
500 ucfg.syswrite(" :rcvbuf => 4096,\n")
501 ucfg.syswrite(" :sndbuf => 4096\n")
503 redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
505 results = retry_hit(["http://#{@addr}:#{@port}/"])
506 assert_equal String, results[0].class
510 def test_unicorn_config_per_worker_listen
512 pid_spit = 'use Rack::ContentLength;' \
513 'run proc { |e| [ 200, {"Content-Type"=>"text/plain"}, ["#$$\\n"] ] }'
514 File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
515 tmp = Tempfile.new('test.socket')
516 File.unlink(tmp.path)
517 ucfg = Tempfile.new('unicorn_test_config')
518 ucfg.syswrite("listen '#@addr:#@port'\n")
519 ucfg.syswrite("before_fork { |s,w|\n")
520 ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
521 ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
522 ucfg.syswrite("\n}\n")
524 redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
526 results = retry_hit(["http://#{@addr}:#{@port}/"])
527 assert_equal String, results[0].class
528 worker_pid = results[0].to_i
529 assert_not_equal pid, worker_pid
530 s = UNIXSocket.new(tmp.path)
531 s.syswrite("GET / HTTP/1.0\r\n\r\n")
533 loop { results << s.sysread(4096) } rescue nil
534 assert_nothing_raised { s.close }
535 assert_equal worker_pid, results.split(/\r\n/).last.to_i
536 results = hit(["http://#@addr:#{port2}/"])
537 assert_equal String, results[0].class
538 assert_equal worker_pid, results[0].to_i
542 def test_unicorn_config_listen_augments_cli
543 port2 = unused_port(@addr)
544 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
545 ucfg = Tempfile.new('unicorn_test_config')
546 ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
549 exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
552 uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
553 results = retry_hit(uris)
554 assert_equal results.size, uris.size
555 assert_equal String, results[0].class
556 assert_equal String, results[1].class
560 def test_weird_config_settings
561 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
562 ucfg = Tempfile.new('unicorn_test_config')
563 ucfg.syswrite(HEAVY_CFG)
566 exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
570 results = retry_hit(["http://#{@addr}:#{@port}/"])
571 assert_equal String, results[0].class
572 wait_master_ready(COMMON_TMP.path)
573 wait_workers_ready(COMMON_TMP.path, 4)
574 bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
575 assert_equal 4, bf.size
576 rotate = Tempfile.new('unicorn_rotate')
577 assert_nothing_raised do
578 File.rename(COMMON_TMP.path, rotate.path)
579 Process.kill(:USR1, pid)
581 wait_for_file(COMMON_TMP.path)
582 assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
583 # USR1 should've been passed to all workers
584 tries = DEFAULT_TRIES
585 log = File.readlines(rotate.path)
586 while (tries -= 1) > 0 &&
587 log.grep(/reopening logs\.\.\./).size < 5
589 log = File.readlines(rotate.path)
591 assert_equal 5, log.grep(/reopening logs\.\.\./).size
592 assert_equal 0, log.grep(/done reopening logs/).size
594 tries = DEFAULT_TRIES
595 log = File.readlines(COMMON_TMP.path)
596 while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
598 log = File.readlines(COMMON_TMP.path)
600 assert_equal 5, log.grep(/done reopening logs/).size
601 assert_equal 0, log.grep(/reopening logs\.\.\./).size
602 assert_nothing_raised { Process.kill(:QUIT, pid) }
604 assert_nothing_raised { pid, status = Process.waitpid2(pid) }
605 assert status.success?, "exited successfully"
608 def test_read_embedded_cli_switches
609 File.open("config.ru", "wb") do |fp|
610 fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
613 pid = fork { redirect_test_io { exec($unicorn_bin) } }
614 results = retry_hit(["http://#{@addr}:#{@port}/"])
615 assert_equal String, results[0].class
619 def test_config_ru_alt_path
620 config_path = "#{@tmpdir}/foo.ru"
621 File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
625 exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
628 results = retry_hit(["http://#{@addr}:#{@port}/"])
629 assert_equal String, results[0].class
634 libdir = "#{@tmpdir}/lib"
635 FileUtils.mkpath([ libdir ])
636 config_path = "#{libdir}/hello.rb"
637 File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
641 exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
644 results = retry_hit(["http://#{@addr}:#{@port}/"])
645 assert_equal String, results[0].class
650 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
651 pid_file = "#{@tmpdir}/test.pid"
654 exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
657 reexec_basic_test(pid, pid_file)
660 def test_reexec_alt_config
661 config_file = "#{@tmpdir}/foo.ru"
662 File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
663 pid_file = "#{@tmpdir}/test.pid"
666 exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
669 reexec_basic_test(pid, pid_file)
672 def test_socket_unlinked_restore
674 sock = Tempfile.new('unicorn_test_sock')
675 sock_path = sock.path
676 @sockets << sock_path
678 ucfg = Tempfile.new('unicorn_test_config')
679 ucfg.syswrite("listen \"#{sock_path}\"\n")
681 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
682 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
683 wait_for_file(sock_path)
684 assert File.socket?(sock_path)
685 assert_nothing_raised do
686 sock = UNIXSocket.new(sock_path)
687 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
688 results = sock.sysread(4096)
690 assert_equal String, results.class
691 assert_nothing_raised do
692 File.unlink(sock_path)
693 Process.kill(:HUP, pid)
695 wait_for_file(sock_path)
696 assert File.socket?(sock_path)
697 assert_nothing_raised do
698 sock = UNIXSocket.new(sock_path)
699 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
700 results = sock.sysread(4096)
702 assert_equal String, results.class
705 def test_unicorn_config_file
706 pid_file = "#{@tmpdir}/test.pid"
707 sock = Tempfile.new('unicorn_test_sock')
708 sock_path = sock.path
710 @sockets << sock_path
712 log = Tempfile.new('unicorn_test_log')
713 ucfg = Tempfile.new('unicorn_test_config')
714 ucfg.syswrite("listen \"#{sock_path}\"\n")
715 ucfg.syswrite("pid \"#{pid_file}\"\n")
716 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
719 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
722 exec($unicorn_bin, "-l#{@addr}:#{@port}",
723 "-P#{pid_file}", "-c#{ucfg.path}")
726 results = retry_hit(["http://#{@addr}:#{@port}/"])
727 assert_equal String, results[0].class
728 wait_master_ready(log.path)
729 assert File.exist?(pid_file), "pid_file created"
730 assert_equal pid, File.read(pid_file).to_i
731 assert File.socket?(sock_path), "socket created"
732 assert_nothing_raised do
733 sock = UNIXSocket.new(sock_path)
734 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
735 results = sock.sysread(4096)
737 assert_equal String, results.class
739 # try reloading the config
740 sock = Tempfile.new('new_test_sock')
741 new_sock_path = sock.path
742 @sockets << new_sock_path
744 new_log = Tempfile.new('unicorn_test_log')
746 assert_equal 0, new_log.size
748 assert_nothing_raised do
749 ucfg = File.open(ucfg.path, "wb")
750 ucfg.syswrite("listen \"#{sock_path}\"\n")
751 ucfg.syswrite("listen \"#{new_sock_path}\"\n")
752 ucfg.syswrite("pid \"#{pid_file}\"\n")
753 ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
755 Process.kill(:HUP, pid)
758 wait_for_file(new_sock_path)
759 assert File.socket?(new_sock_path), "socket exists"
760 @sockets.each do |path|
761 assert_nothing_raised do
762 sock = UNIXSocket.new(path)
763 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
764 results = sock.sysread(4096)
766 assert_equal String, results.class
769 assert_not_equal 0, new_log.size
770 reexec_usr2_quit_test(pid, pid_file)
773 def test_daemonize_reexec
774 pid_file = "#{@tmpdir}/test.pid"
775 log = Tempfile.new('unicorn_test_log')
776 ucfg = Tempfile.new('unicorn_test_config')
777 ucfg.syswrite("pid \"#{pid_file}\"\n")
778 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
781 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
784 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
787 results = retry_hit(["http://#{@addr}:#{@port}/"])
788 assert_equal String, results[0].class
789 wait_for_file(pid_file)
790 new_pid = File.read(pid_file).to_i
791 assert_not_equal pid, new_pid
792 pid, status = Process.waitpid2(pid)
793 assert status.success?, "original process exited successfully"
794 assert_nothing_raised { Process.kill(0, new_pid) }
795 reexec_usr2_quit_test(new_pid, pid_file)
798 def test_daemonize_redirect_fail
799 pid_file = "#{@tmpdir}/test.pid"
800 log = Tempfile.new('unicorn_test_log')
801 ucfg = Tempfile.new('unicorn_test_config')
802 ucfg.syswrite("pid #{pid_file}\"\n")
803 err = Tempfile.new('stderr')
804 out = Tempfile.new('stdout ')
806 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
808 $stderr.reopen(err.path, "a")
809 $stdout.reopen(out.path, "a")
810 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
812 pid, status = Process.waitpid2(pid)
813 assert ! status.success?, "original process exited successfully"
814 sleep 1 # can't waitpid on a daemonized process :<
815 assert err.stat.size > 0
818 def test_reexec_fd_leak
819 unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
820 warn "FD leak test only works on Linux at the moment"
823 pid_file = "#{@tmpdir}/test.pid"
824 log = Tempfile.new('unicorn_test_log')
826 ucfg = Tempfile.new('unicorn_test_config')
827 ucfg.syswrite("pid \"#{pid_file}\"\n")
828 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
829 ucfg.syswrite("stderr_path '#{log.path}'\n")
830 ucfg.syswrite("stdout_path '#{log.path}'\n")
833 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
836 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
840 wait_master_ready(log.path)
841 wait_workers_ready(log.path, 1)
842 File.truncate(log.path, 0)
843 wait_for_file(pid_file)
844 orig_pid = pid = File.read(pid_file).to_i
845 orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
847 expect_size = orig_fds.size
849 assert_nothing_raised do
850 Process.kill(:USR2, pid)
851 wait_for_file("#{pid_file}.oldbin")
852 Process.kill(:QUIT, pid)
856 wait_master_ready(log.path)
857 wait_workers_ready(log.path, 1)
858 File.truncate(log.path, 0)
859 wait_for_file(pid_file)
860 pid = File.read(pid_file).to_i
861 assert_not_equal orig_pid, pid
862 curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
865 # we could've inherited descriptors the first time around
866 assert expect_size >= curr_fds.size, curr_fds.inspect
867 expect_size = curr_fds.size
869 assert_nothing_raised do
870 Process.kill(:USR2, pid)
871 wait_for_file("#{pid_file}.oldbin")
872 Process.kill(:QUIT, pid)
876 wait_master_ready(log.path)
877 wait_workers_ready(log.path, 1)
878 File.truncate(log.path, 0)
879 wait_for_file(pid_file)
880 pid = File.read(pid_file).to_i
881 curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
883 assert_equal expect_size, curr_fds.size, curr_fds.inspect
885 Process.kill(:QUIT, pid)
889 def hup_test_common(preload)
890 File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
891 pid_file = Tempfile.new('pid')
892 ucfg = Tempfile.new('unicorn_test_config')
893 ucfg.syswrite("listen '#@addr:#@port'\n")
894 ucfg.syswrite("pid '#{pid_file.path}'\n")
895 ucfg.syswrite("preload_app true\n") if preload
896 ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
897 ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
899 redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
901 _, status = Process.waitpid2(pid)
902 assert status.success?
903 wait_master_ready("test_stderr.#$$.log")
904 wait_workers_ready("test_stderr.#$$.log", 1)
905 uri = URI.parse("http://#@addr:#@port/")
906 pids = Tempfile.new('worker_pids')
911 at_exit { pids.syswrite(bodies.inspect) }
912 trap(:TERM) { exit(0) }
915 rv = Net::HTTP.get(uri)
922 elsif bodies.size > 1
929 assert_equal '1', r.read(1)
930 daemon_pid = File.read(pid_file.path).to_i
931 assert daemon_pid > 0
932 Process.kill(:HUP, daemon_pid)
933 assert_equal '2', r.read(1)
934 assert_nothing_raised { Process.kill(:TERM, hitter) }
935 _, hitter_status = Process.waitpid2(hitter)
936 assert(hitter_status.success?,
937 "invalid: #{hitter_status.inspect} #{File.read(pids.path)}" \
938 "#{File.read("test_stderr.#$$.log")}")
940 pids = eval(pids.read)
941 assert_kind_of(Hash, pids)
942 assert_equal 2, pids.size
944 assert_kind_of(Integer, x)
948 assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
949 wait_for_death(daemon_pid)
952 def test_preload_app_hup
953 hup_test_common(true)
957 hup_test_common(false)
960 def test_default_listen_hup_holds_listener
961 default_listen_lock do
962 res, pid_path = default_listen_setup
963 daemon_pid = File.read(pid_path).to_i
964 assert_nothing_raised { Process.kill(:HUP, daemon_pid) }
965 wait_workers_ready("test_stderr.#$$.log", 1)
966 res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
967 assert_match %r{\d+}, res2.first
968 assert res2.first != res.first
969 assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
970 wait_for_death(daemon_pid)
974 def test_default_listen_upgrade_holds_listener
975 default_listen_lock do
976 res, pid_path = default_listen_setup
977 daemon_pid = File.read(pid_path).to_i
978 assert_nothing_raised {
979 Process.kill(:USR2, daemon_pid)
980 wait_for_file("#{pid_path}.oldbin")
981 wait_for_file(pid_path)
982 Process.kill(:QUIT, daemon_pid)
983 wait_for_death(daemon_pid)
985 daemon_pid = File.read(pid_path).to_i
986 wait_workers_ready("test_stderr.#$$.log", 1)
987 File.truncate("test_stderr.#$$.log", 0)
989 res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
990 assert_match %r{\d+}, res2.first
991 assert res2.first != res.first
993 assert_nothing_raised { Process.kill(:HUP, daemon_pid) }
994 wait_workers_ready("test_stderr.#$$.log", 1)
995 File.truncate("test_stderr.#$$.log", 0)
996 res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
997 assert res2.first != res3.first
999 assert_nothing_raised { Process.kill(:QUIT, daemon_pid) }
1000 wait_for_death(daemon_pid)
1004 def default_listen_setup
1005 File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
1006 pid_path = (tmp = Tempfile.new('pid')).path
1008 ucfg = Tempfile.new('unicorn_test_config')
1009 ucfg.syswrite("pid '#{pid_path}'\n")
1010 ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
1011 ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
1013 redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
1015 _, status = Process.waitpid2(pid)
1016 assert status.success?
1017 wait_master_ready("test_stderr.#$$.log")
1018 wait_workers_ready("test_stderr.#$$.log", 1)
1019 File.truncate("test_stderr.#$$.log", 0)
1020 res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1021 assert_match %r{\d+}, res.first
1025 # we need to flock() something to prevent these tests from running
1026 def default_listen_lock(&block)
1027 fp = File.open(FLOCK_PATH, "rb")
1029 fp.flock(File::LOCK_EX)
1031 TCPServer.new(Unicorn::Const::DEFAULT_HOST,
1032 Unicorn::Const::DEFAULT_PORT).close
1033 rescue Errno::EADDRINUSE, Errno::EACCES
1034 warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
1038 # unused_port should never take this, but we may run an environment
1039 # where tests are being run against older unicorns...
1040 lock_path = "#{Dir::tmpdir}/unicorn_test." \
1041 "#{Unicorn::Const::DEFAULT_LISTEN}.lock"
1043 lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
1045 rescue Errno::EEXIST
1049 File.unlink(lock_path) if lock_path
1052 fp.flock(File::LOCK_UN)