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 Process.kill(sig, pid)
234 pid, status = Process.waitpid2(pid)
236 reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
237 assert_equal 1, reaped.size
238 assert status.exited?
243 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
245 redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
247 results = retry_hit(["http://#{@addr}:#{@port}/"])
248 assert_equal String, results[0].class
252 def test_rack_env_unset
253 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
254 pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
255 results = retry_hit(["http://#{@addr}:#{@port}/"])
256 assert_equal "development", results.first
260 def test_rack_env_cli_set
261 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
263 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
265 results = retry_hit(["http://#{@addr}:#{@port}/"])
266 assert_equal "asdf", results.first
270 def test_rack_env_ENV_set
271 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
273 ENV["RACK_ENV"] = "foobar"
274 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") }
276 results = retry_hit(["http://#{@addr}:#{@port}/"])
277 assert_equal "foobar", results.first
281 def test_rack_env_cli_override_ENV
282 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
284 ENV["RACK_ENV"] = "foobar"
285 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
287 results = retry_hit(["http://#{@addr}:#{@port}/"])
288 assert_equal "asdf", results.first
293 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
294 pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
295 log = "test_stderr.#{pid}.log"
296 wait_master_ready(log)
298 Process.kill(:TTIN, pid)
299 wait_workers_ready(log, i)
301 File.truncate(log, 0)
303 [ 2, 1, 0].each { |i|
304 Process.kill(:TTOU, pid)
305 DEFAULT_TRIES.times {
307 reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
308 break if reaped.size == 1
310 assert_equal 1, reaped.size
316 assert(system($unicorn_bin, "-h"), "help text returns true")
318 assert_equal 0, File.stat("test_stderr.#$$.log").size
319 assert_not_equal 0, File.stat("test_stdout.#$$.log").size
320 lines = File.readlines("test_stdout.#$$.log")
322 # Be considerate of the on-call technician working from their
323 # mobile phone or netbook on a slow connection :)
324 assert lines.size <= 24, "help height fits in an ANSI terminal window"
326 assert line.size <= 80, "help width fits in an ANSI terminal window"
330 def test_broken_reexec_config
331 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
332 pid_file = "#{@tmpdir}/test.pid"
333 old_file = "#{pid_file}.oldbin"
334 ucfg = Tempfile.new('unicorn_test_config')
335 ucfg.syswrite("listen %(#@addr:#@port)\n")
336 ucfg.syswrite("pid %(#{pid_file})\n")
337 ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
340 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
343 results = retry_hit(["http://#{@addr}:#{@port}/"])
344 assert_equal String, results[0].class
346 wait_for_file(pid_file)
348 Process.kill(:USR2, File.read(pid_file).to_i)
349 wait_for_file(old_file)
350 wait_for_file(pid_file)
351 old_pid = File.read(old_file).to_i
352 Process.kill(:QUIT, old_pid)
353 wait_for_death(old_pid)
355 ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
356 current_pid = File.read(pid_file).to_i
357 Process.kill(:USR2, current_pid)
359 # wait for pid_file to restore itself
360 tries = DEFAULT_TRIES
362 while current_pid != File.read(pid_file).to_i
363 sleep(DEFAULT_RES) and (tries -= 1) > 0
366 (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
368 assert_equal current_pid, File.read(pid_file).to_i
370 tries = DEFAULT_TRIES
371 while File.exist?(old_file)
372 (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
374 assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
375 port2 = unused_port(@addr)
380 ucfg.syswrite("listen %(#@addr:#@port)\n")
381 ucfg.syswrite("listen %(#@addr:#{port2})\n")
382 ucfg.syswrite("pid %(#{pid_file})\n")
383 Process.kill(:USR2, current_pid)
385 wait_for_file(old_file)
386 wait_for_file(pid_file)
387 new_pid = File.read(pid_file).to_i
388 assert_not_equal current_pid, new_pid
389 assert_equal current_pid, File.read(old_file).to_i
390 results = retry_hit(["http://#{@addr}:#{@port}/",
391 "http://#{@addr}:#{port2}/"])
392 assert_equal String, results[0].class
393 assert_equal String, results[1].class
395 Process.kill(:QUIT, current_pid)
396 Process.kill(:QUIT, new_pid)
399 def test_broken_reexec_ru
400 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
401 pid_file = "#{@tmpdir}/test.pid"
402 old_file = "#{pid_file}.oldbin"
403 ucfg = Tempfile.new('unicorn_test_config')
404 ucfg.syswrite("pid %(#{pid_file})\n")
405 ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
408 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
411 results = retry_hit(["http://#{@addr}:#{@port}/"])
412 assert_equal String, results[0].class
414 wait_for_file(pid_file)
416 Process.kill(:USR2, File.read(pid_file).to_i)
417 wait_for_file(old_file)
418 wait_for_file(pid_file)
419 old_pid = File.read(old_file).to_i
420 Process.kill(:QUIT, old_pid)
421 wait_for_death(old_pid)
423 File.unlink("config.ru") # break reloading
424 current_pid = File.read(pid_file).to_i
425 Process.kill(:USR2, current_pid)
427 # wait for pid_file to restore itself
428 tries = DEFAULT_TRIES
430 while current_pid != File.read(pid_file).to_i
431 sleep(DEFAULT_RES) and (tries -= 1) > 0
434 (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
437 tries = DEFAULT_TRIES
438 while File.exist?(old_file)
439 (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
441 assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
442 assert_equal current_pid, File.read(pid_file).to_i
445 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
446 Process.kill(:USR2, current_pid)
447 wait_for_file(old_file)
448 wait_for_file(pid_file)
449 new_pid = File.read(pid_file).to_i
450 assert_not_equal current_pid, new_pid
451 assert_equal current_pid, File.read(old_file).to_i
452 results = retry_hit(["http://#{@addr}:#{@port}/"])
453 assert_equal String, results[0].class
455 Process.kill(:QUIT, current_pid)
456 Process.kill(:QUIT, new_pid)
459 def test_unicorn_config_listener_swap
460 port_cli = unused_port
461 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
462 ucfg = Tempfile.new('unicorn_test_config')
463 ucfg.syswrite("listen '#@addr:#@port'\n")
466 exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
469 results = retry_hit(["http://#@addr:#{port_cli}/"])
470 assert_equal String, results[0].class
471 results = retry_hit(["http://#@addr:#@port/"])
472 assert_equal String, results[0].class
474 port2 = unused_port(@addr)
477 ucfg.syswrite("listen '#@addr:#{port2}'\n")
478 Process.kill(:HUP, pid)
480 results = retry_hit(["http://#@addr:#{port2}/"])
481 assert_equal String, results[0].class
482 results = retry_hit(["http://#@addr:#{port_cli}/"])
483 assert_equal String, results[0].class
484 reuse = TCPServer.new(@addr, @port)
489 def test_unicorn_config_listen_with_options
490 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
491 ucfg = Tempfile.new('unicorn_test_config')
492 ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
493 ucfg.syswrite(" :rcvbuf => 4096,\n")
494 ucfg.syswrite(" :sndbuf => 4096\n")
496 redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
498 results = retry_hit(["http://#{@addr}:#{@port}/"])
499 assert_equal String, results[0].class
503 def test_unicorn_config_per_worker_listen
505 pid_spit = 'use Rack::ContentLength;' \
506 'run proc { |e| [ 200, {"Content-Type"=>"text/plain"}, ["#$$\\n"] ] }'
507 File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
508 tmp = Tempfile.new('test.socket')
509 File.unlink(tmp.path)
510 ucfg = Tempfile.new('unicorn_test_config')
511 ucfg.syswrite("listen '#@addr:#@port'\n")
512 ucfg.syswrite("after_fork { |s,w|\n")
513 ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
514 ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
515 ucfg.syswrite("\n}\n")
517 redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
519 results = retry_hit(["http://#{@addr}:#{@port}/"])
520 assert_equal String, results[0].class
521 worker_pid = results[0].to_i
522 assert_not_equal pid, worker_pid
523 s = UNIXSocket.new(tmp.path)
524 s.syswrite("GET / HTTP/1.0\r\n\r\n")
526 loop { results << s.sysread(4096) } rescue nil
528 assert_equal worker_pid, results.split(/\r\n/).last.to_i
529 results = hit(["http://#@addr:#{port2}/"])
530 assert_equal String, results[0].class
531 assert_equal worker_pid, results[0].to_i
535 def test_unicorn_config_listen_augments_cli
536 port2 = unused_port(@addr)
537 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
538 ucfg = Tempfile.new('unicorn_test_config')
539 ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
542 exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
545 uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
546 results = retry_hit(uris)
547 assert_equal results.size, uris.size
548 assert_equal String, results[0].class
549 assert_equal String, results[1].class
553 def test_weird_config_settings
554 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
555 ucfg = Tempfile.new('unicorn_test_config')
556 ucfg.syswrite(HEAVY_CFG)
559 exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
563 results = retry_hit(["http://#{@addr}:#{@port}/"])
564 assert_equal String, results[0].class
565 wait_master_ready(COMMON_TMP.path)
566 wait_workers_ready(COMMON_TMP.path, 4)
567 bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
568 assert_equal 4, bf.size
569 rotate = Tempfile.new('unicorn_rotate')
571 File.rename(COMMON_TMP.path, rotate.path)
572 Process.kill(:USR1, pid)
574 wait_for_file(COMMON_TMP.path)
575 assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
576 # USR1 should've been passed to all workers
577 tries = DEFAULT_TRIES
578 log = File.readlines(rotate.path)
579 while (tries -= 1) > 0 &&
580 log.grep(/reopening logs\.\.\./).size < 5
582 log = File.readlines(rotate.path)
584 assert_equal 5, log.grep(/reopening logs\.\.\./).size
585 assert_equal 0, log.grep(/done reopening logs/).size
587 tries = DEFAULT_TRIES
588 log = File.readlines(COMMON_TMP.path)
589 while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
591 log = File.readlines(COMMON_TMP.path)
593 assert_equal 5, log.grep(/done reopening logs/).size
594 assert_equal 0, log.grep(/reopening logs\.\.\./).size
596 Process.kill(:QUIT, pid)
597 pid, status = Process.waitpid2(pid)
599 assert status.success?, "exited successfully"
602 def test_read_embedded_cli_switches
603 File.open("config.ru", "wb") do |fp|
604 fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
607 pid = fork { redirect_test_io { exec($unicorn_bin) } }
608 results = retry_hit(["http://#{@addr}:#{@port}/"])
609 assert_equal String, results[0].class
613 def test_config_ru_alt_path
614 config_path = "#{@tmpdir}/foo.ru"
615 File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
619 exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
622 results = retry_hit(["http://#{@addr}:#{@port}/"])
623 assert_equal String, results[0].class
628 libdir = "#{@tmpdir}/lib"
629 FileUtils.mkpath([ libdir ])
630 config_path = "#{libdir}/hello.rb"
631 File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
635 exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
638 results = retry_hit(["http://#{@addr}:#{@port}/"])
639 assert_equal String, results[0].class
644 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
645 pid_file = "#{@tmpdir}/test.pid"
648 exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
651 reexec_basic_test(pid, pid_file)
654 def test_reexec_alt_config
655 config_file = "#{@tmpdir}/foo.ru"
656 File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
657 pid_file = "#{@tmpdir}/test.pid"
660 exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
663 reexec_basic_test(pid, pid_file)
666 def test_socket_unlinked_restore
668 sock = Tempfile.new('unicorn_test_sock')
669 sock_path = sock.path
670 @sockets << sock_path
672 ucfg = Tempfile.new('unicorn_test_config')
673 ucfg.syswrite("listen \"#{sock_path}\"\n")
675 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
676 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
677 wait_for_file(sock_path)
678 assert File.socket?(sock_path)
680 sock = UNIXSocket.new(sock_path)
681 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
682 results = sock.sysread(4096)
684 assert_equal String, results.class
685 File.unlink(sock_path)
686 Process.kill(:HUP, pid)
687 wait_for_file(sock_path)
688 assert File.socket?(sock_path)
690 sock = UNIXSocket.new(sock_path)
691 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
692 results = sock.sysread(4096)
694 assert_equal String, results.class
697 def test_unicorn_config_file
698 pid_file = "#{@tmpdir}/test.pid"
699 sock = Tempfile.new('unicorn_test_sock')
700 sock_path = sock.path
702 @sockets << sock_path
704 log = Tempfile.new('unicorn_test_log')
705 ucfg = Tempfile.new('unicorn_test_config')
706 ucfg.syswrite("listen \"#{sock_path}\"\n")
707 ucfg.syswrite("pid \"#{pid_file}\"\n")
708 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
711 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
714 exec($unicorn_bin, "-l#{@addr}:#{@port}",
715 "-P#{pid_file}", "-c#{ucfg.path}")
718 results = retry_hit(["http://#{@addr}:#{@port}/"])
719 assert_equal String, results[0].class
720 wait_master_ready(log.path)
721 assert File.exist?(pid_file), "pid_file created"
722 assert_equal pid, File.read(pid_file).to_i
723 assert File.socket?(sock_path), "socket created"
725 sock = UNIXSocket.new(sock_path)
726 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
727 results = sock.sysread(4096)
729 assert_equal String, results.class
731 # try reloading the config
732 sock = Tempfile.new('new_test_sock')
733 new_sock_path = sock.path
734 @sockets << new_sock_path
736 new_log = Tempfile.new('unicorn_test_log')
738 assert_equal 0, new_log.size
740 ucfg = File.open(ucfg.path, "wb")
741 ucfg.syswrite("listen \"#{sock_path}\"\n")
742 ucfg.syswrite("listen \"#{new_sock_path}\"\n")
743 ucfg.syswrite("pid \"#{pid_file}\"\n")
744 ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
746 Process.kill(:HUP, pid)
748 wait_for_file(new_sock_path)
749 assert File.socket?(new_sock_path), "socket exists"
750 @sockets.each do |path|
751 sock = UNIXSocket.new(path)
752 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
753 results = sock.sysread(4096)
754 assert_equal String, results.class
757 assert_not_equal 0, new_log.size
758 reexec_usr2_quit_test(pid, pid_file)
761 def test_daemonize_reexec
762 pid_file = "#{@tmpdir}/test.pid"
763 log = Tempfile.new('unicorn_test_log')
764 ucfg = Tempfile.new('unicorn_test_config')
765 ucfg.syswrite("pid \"#{pid_file}\"\n")
766 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
769 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
772 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
775 results = retry_hit(["http://#{@addr}:#{@port}/"])
776 assert_equal String, results[0].class
777 wait_for_file(pid_file)
778 new_pid = File.read(pid_file).to_i
779 assert_not_equal pid, new_pid
780 pid, status = Process.waitpid2(pid)
781 assert status.success?, "original process exited successfully"
782 Process.kill(0, new_pid)
783 reexec_usr2_quit_test(new_pid, pid_file)
786 def test_daemonize_redirect_fail
787 pid_file = "#{@tmpdir}/test.pid"
788 ucfg = Tempfile.new('unicorn_test_config')
789 ucfg.syswrite("pid #{pid_file}\"\n")
790 err = Tempfile.new('stderr')
791 out = Tempfile.new('stdout ')
793 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
795 $stderr.reopen(err.path, "a")
796 $stdout.reopen(out.path, "a")
797 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
799 pid, status = Process.waitpid2(pid)
800 assert ! status.success?, "original process exited successfully"
801 sleep 1 # can't waitpid on a daemonized process :<
802 assert err.stat.size > 0
805 def test_reexec_fd_leak
806 unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
807 warn "FD leak test only works on Linux at the moment"
810 pid_file = "#{@tmpdir}/test.pid"
811 log = Tempfile.new('unicorn_test_log')
813 ucfg = Tempfile.new('unicorn_test_config')
814 ucfg.syswrite("pid \"#{pid_file}\"\n")
815 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
816 ucfg.syswrite("stderr_path '#{log.path}'\n")
817 ucfg.syswrite("stdout_path '#{log.path}'\n")
820 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
823 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
827 wait_master_ready(log.path)
828 wait_workers_ready(log.path, 1)
829 File.truncate(log.path, 0)
830 wait_for_file(pid_file)
831 orig_pid = pid = File.read(pid_file).to_i
832 orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
834 expect_size = orig_fds.size
836 Process.kill(:USR2, pid)
837 wait_for_file("#{pid_file}.oldbin")
838 Process.kill(:QUIT, pid)
842 wait_master_ready(log.path)
843 wait_workers_ready(log.path, 1)
844 File.truncate(log.path, 0)
845 wait_for_file(pid_file)
846 pid = File.read(pid_file).to_i
847 assert_not_equal orig_pid, pid
848 curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
851 # we could've inherited descriptors the first time around
852 assert expect_size >= curr_fds.size, curr_fds.inspect
853 expect_size = curr_fds.size
855 Process.kill(:USR2, pid)
856 wait_for_file("#{pid_file}.oldbin")
857 Process.kill(:QUIT, pid)
861 wait_master_ready(log.path)
862 wait_workers_ready(log.path, 1)
863 File.truncate(log.path, 0)
864 wait_for_file(pid_file)
865 pid = File.read(pid_file).to_i
866 curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
868 assert_equal expect_size, curr_fds.size, curr_fds.inspect
870 Process.kill(:QUIT, pid)
874 def hup_test_common(preload)
875 File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
876 pid_file = Tempfile.new('pid')
877 ucfg = Tempfile.new('unicorn_test_config')
878 ucfg.syswrite("listen '#@addr:#@port'\n")
879 ucfg.syswrite("pid '#{pid_file.path}'\n")
880 ucfg.syswrite("preload_app true\n") if preload
881 ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
882 ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
884 redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
886 _, status = Process.waitpid2(pid)
887 assert status.success?
888 wait_master_ready("test_stderr.#$$.log")
889 wait_workers_ready("test_stderr.#$$.log", 1)
890 uri = URI.parse("http://#@addr:#@port/")
891 pids = Tempfile.new('worker_pids')
896 at_exit { pids.syswrite(bodies.inspect) }
897 trap(:TERM) { exit(0) }
900 rv = Net::HTTP.get(uri)
907 elsif bodies.size > 1
914 assert_equal '1', r.read(1)
915 daemon_pid = File.read(pid_file.path).to_i
916 assert daemon_pid > 0
917 Process.kill(:HUP, daemon_pid)
918 assert_equal '2', r.read(1)
919 Process.kill(:TERM, hitter)
920 _, hitter_status = Process.waitpid2(hitter)
921 assert(hitter_status.success?,
922 "invalid: #{hitter_status.inspect} #{File.read(pids.path)}" \
923 "#{File.read("test_stderr.#$$.log")}")
925 pids = eval(pids.read)
926 assert_kind_of(Hash, pids)
927 assert_equal 2, pids.size
929 assert_kind_of(Integer, x)
933 Process.kill(:QUIT, daemon_pid)
934 wait_for_death(daemon_pid)
937 def test_preload_app_hup
938 hup_test_common(true)
942 hup_test_common(false)
945 def test_default_listen_hup_holds_listener
946 default_listen_lock do
947 res, pid_path = default_listen_setup
948 daemon_pid = File.read(pid_path).to_i
949 Process.kill(:HUP, daemon_pid)
950 wait_workers_ready("test_stderr.#$$.log", 1)
951 res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
952 assert_match %r{\d+}, res2.first
953 assert res2.first != res.first
954 Process.kill(:QUIT, daemon_pid)
955 wait_for_death(daemon_pid)
959 def test_default_listen_upgrade_holds_listener
960 default_listen_lock do
961 res, pid_path = default_listen_setup
962 daemon_pid = File.read(pid_path).to_i
964 Process.kill(:USR2, daemon_pid)
965 wait_for_file("#{pid_path}.oldbin")
966 wait_for_file(pid_path)
967 Process.kill(:QUIT, daemon_pid)
968 wait_for_death(daemon_pid)
970 daemon_pid = File.read(pid_path).to_i
971 wait_workers_ready("test_stderr.#$$.log", 1)
972 File.truncate("test_stderr.#$$.log", 0)
974 res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
975 assert_match %r{\d+}, res2.first
976 assert res2.first != res.first
978 Process.kill(:HUP, daemon_pid)
979 wait_workers_ready("test_stderr.#$$.log", 1)
980 File.truncate("test_stderr.#$$.log", 0)
981 res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
982 assert res2.first != res3.first
984 Process.kill(:QUIT, daemon_pid)
985 wait_for_death(daemon_pid)
989 def default_listen_setup
990 File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
991 pid_path = (tmp = Tempfile.new('pid')).path
993 ucfg = Tempfile.new('unicorn_test_config')
994 ucfg.syswrite("pid '#{pid_path}'\n")
995 ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
996 ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
998 redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
1000 _, status = Process.waitpid2(pid)
1001 assert status.success?
1002 wait_master_ready("test_stderr.#$$.log")
1003 wait_workers_ready("test_stderr.#$$.log", 1)
1004 File.truncate("test_stderr.#$$.log", 0)
1005 res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1006 assert_match %r{\d+}, res.first
1010 # we need to flock() something to prevent these tests from running
1011 def default_listen_lock(&block)
1012 fp = File.open(FLOCK_PATH, "rb")
1014 fp.flock(File::LOCK_EX)
1016 TCPServer.new(Unicorn::Const::DEFAULT_HOST,
1017 Unicorn::Const::DEFAULT_PORT).close
1018 rescue Errno::EADDRINUSE, Errno::EACCES
1019 warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
1023 # unused_port should never take this, but we may run an environment
1024 # where tests are being run against older unicorns...
1025 lock_path = "#{Dir::tmpdir}/unicorn_test." \
1026 "#{Unicorn::Const::DEFAULT_LISTEN}.lock"
1028 File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
1030 rescue Errno::EEXIST
1034 File.unlink(lock_path) if lock_path
1037 fp.flock(File::LOCK_UN)