1 # -*- encoding: binary -*-
2 # frozen_string_literal: false
3 # Don't add to this file, new tests are in Perl 5. See t/README
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)
50 worker_processes #{HEAVY_WORKERS}
52 logger Logger.new('#{COMMON_TMP.path}')
53 before_fork do |server, worker|
54 server.logger.info "before_fork: worker=\#{worker.nr}"
58 WORKING_DIRECTORY_CHECK_RU = <<-EOS
59 use Rack::ContentLength
63 b = ::File.stat(Dir.pwd)
64 if (a.ino == b.ino && a.dev == b.dev)
65 [ 200, { 'content-type' => 'text/plain' }, [ pwd ] ]
67 [ 404, { 'content-type' => 'text/plain' }, [] ]
74 @tmpfile = Tempfile.new('unicorn_exec_test')
75 @tmpdir = @tmpfile.path
79 @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
80 @port = unused_port(@addr)
86 return if @start_pid != $$
88 FileUtils.rmtree(@tmpdir)
89 @sockets.each { |path| File.unlink(path) rescue nil }
91 Process.kill('-QUIT', 0)
93 Process.waitpid(-1, Process::WNOHANG) or break
100 def test_working_directory_rel_path_config_file
101 other = Tempfile.new('unicorn.wd')
102 File.unlink(other.path)
103 Dir.mkdir(other.path)
104 File.open("config.ru", "wb") do |fp|
105 fp.syswrite WORKING_DIRECTORY_CHECK_RU
107 FileUtils.cp("config.ru", other.path + "/config.ru")
110 tmp = File.open('unicorn.config', 'wb')
112 working_directory '#@tmpdir'
113 listen '#@addr:#@port'
115 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
116 wait_workers_ready("test_stderr.#{pid}.log", 1)
117 results = hit(["http://#@addr:#@port/"])
118 assert_equal @tmpdir, results.first
119 File.truncate("test_stderr.#{pid}.log", 0)
124 working_directory '#{other.path}'
125 listen '#@addr:#@port'
128 Process.kill(:HUP, pid)
130 re = /config_file=(.+) would not be accessible in working_directory=(.+)/
133 lines = File.readlines("test_stderr.#{pid}.log")
136 File.truncate("test_stderr.#{pid}.log", 0)
137 FileUtils.cp('unicorn.config', other.path + "/unicorn.config")
138 Process.kill(:HUP, pid)
139 wait_workers_ready("test_stderr.#{pid}.log", 1)
140 results = hit(["http://#@addr:#@port/"])
141 assert_equal other.path, results.first
143 Process.kill(:QUIT, pid)
145 FileUtils.rmtree(other.path)
148 def test_working_directory
149 other = Tempfile.new('unicorn.wd')
150 File.unlink(other.path)
151 Dir.mkdir(other.path)
152 File.open("config.ru", "wb") do |fp|
153 fp.syswrite WORKING_DIRECTORY_CHECK_RU
155 FileUtils.cp("config.ru", other.path + "/config.ru")
156 tmp = Tempfile.new('unicorn.config')
158 working_directory '#@tmpdir'
159 listen '#@addr:#@port'
161 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
162 wait_workers_ready("test_stderr.#{pid}.log", 1)
163 results = hit(["http://#@addr:#@port/"])
164 assert_equal @tmpdir, results.first
165 File.truncate("test_stderr.#{pid}.log", 0)
170 working_directory '#{other.path}'
171 listen '#@addr:#@port'
174 Process.kill(:HUP, pid)
175 wait_workers_ready("test_stderr.#{pid}.log", 1)
176 results = hit(["http://#@addr:#@port/"])
177 assert_equal other.path, results.first
179 Process.kill(:QUIT, pid)
181 FileUtils.rmtree(other.path)
184 def test_working_directory_controls_relative_paths
185 other = Tempfile.new('unicorn.wd')
186 File.unlink(other.path)
187 Dir.mkdir(other.path)
188 File.open("config.ru", "wb") do |fp|
189 fp.syswrite WORKING_DIRECTORY_CHECK_RU
191 FileUtils.cp("config.ru", other.path + "/config.ru")
192 system('mkfifo', "#{other.path}/fifo")
193 tmp = Tempfile.new('unicorn.config')
196 stderr_path "stderr_log_here"
197 stdout_path "stdout_log_here"
198 working_directory '#{other.path}'
199 listen '#@addr:#@port'
200 after_fork do |server, worker|
201 File.open("fifo", "wb").close
204 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
206 fifo = File.open("#{other.path}/fifo", "rb")
208 # OpenBSD raises Errno::EINTR when opening
209 return if RUBY_PLATFORM =~ /openbsd/
213 assert ! File.exist?("stderr_log_here")
214 assert ! File.exist?("stdout_log_here")
215 assert ! File.exist?("pid_file_here")
217 assert ! File.exist?("#@tmpdir/stderr_log_here")
218 assert ! File.exist?("#@tmpdir/stdout_log_here")
219 assert ! File.exist?("#@tmpdir/pid_file_here")
221 assert File.exist?("#{other.path}/pid_file_here")
222 assert_equal "#{pid}\n", File.read("#{other.path}/pid_file_here")
223 assert File.exist?("#{other.path}/stderr_log_here")
224 assert File.exist?("#{other.path}/stdout_log_here")
225 wait_master_ready("#{other.path}/stderr_log_here")
227 Process.kill(:QUIT, pid)
229 FileUtils.rmtree(other.path)
232 def test_exit_signals
233 %w(INT TERM QUIT).each do |sig|
234 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
235 pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
236 wait_master_ready("test_stderr.#{pid}.log")
237 wait_workers_ready("test_stderr.#{pid}.log", 1)
239 Process.kill(sig, pid)
240 pid, status = Process.waitpid2(pid)
242 reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
243 assert_equal 1, reaped.size
244 assert status.exited?
248 def test_rack_env_unset
249 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
250 pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
251 results = retry_hit(["http://#{@addr}:#{@port}/"])
252 assert_equal "development", results.first
256 def test_rack_env_cli_set
257 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
259 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
261 results = retry_hit(["http://#{@addr}:#{@port}/"])
262 assert_equal "asdf", results.first
266 def test_rack_env_ENV_set
267 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
269 ENV["RACK_ENV"] = "foobar"
270 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") }
272 results = retry_hit(["http://#{@addr}:#{@port}/"])
273 assert_equal "foobar", results.first
277 def test_rack_env_cli_override_ENV
278 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
280 ENV["RACK_ENV"] = "foobar"
281 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
283 results = retry_hit(["http://#{@addr}:#{@port}/"])
284 assert_equal "asdf", results.first
289 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
290 pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
291 log = "test_stderr.#{pid}.log"
292 wait_master_ready(log)
294 Process.kill(:TTIN, pid)
295 wait_workers_ready(log, i)
297 File.truncate(log, 0)
299 [ 2, 1, 0].each { |i|
300 Process.kill(:TTOU, pid)
301 DEFAULT_TRIES.times {
303 reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
304 break if reaped.size == 1
306 assert_equal 1, reaped.size
312 assert(system($unicorn_bin, "-h"), "help text returns true")
314 assert_equal 0, File.stat("test_stderr.#$$.log").size
315 assert_not_equal 0, File.stat("test_stdout.#$$.log").size
316 lines = File.readlines("test_stdout.#$$.log")
318 # Be considerate of the on-call technician working from their
319 # mobile phone or netbook on a slow connection :)
320 assert lines.size <= 24, "help height fits in an ANSI terminal window"
323 assert line.size <= 80, "help width fits in an ANSI terminal window"
327 def test_broken_reexec_config
328 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
329 pid_file = "#{@tmpdir}/test.pid"
330 old_file = "#{pid_file}.oldbin"
331 ucfg = Tempfile.new('unicorn_test_config')
332 ucfg.syswrite("listen %(#@addr:#@port)\n")
333 ucfg.syswrite("pid %(#{pid_file})\n")
334 ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
337 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
340 results = retry_hit(["http://#{@addr}:#{@port}/"])
341 assert_equal String, results[0].class
343 wait_for_file(pid_file)
345 Process.kill(:USR2, File.read(pid_file).to_i)
346 wait_for_file(old_file)
347 wait_for_file(pid_file)
348 old_pid = File.read(old_file).to_i
349 Process.kill(:QUIT, old_pid)
350 wait_for_death(old_pid)
352 ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
353 current_pid = File.read(pid_file).to_i
354 Process.kill(:USR2, current_pid)
356 # wait for pid_file to restore itself
357 tries = DEFAULT_TRIES
359 while current_pid != File.read(pid_file).to_i
360 sleep(DEFAULT_RES) and (tries -= 1) > 0
363 (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
365 assert_equal current_pid, File.read(pid_file).to_i
367 tries = DEFAULT_TRIES
368 while File.exist?(old_file)
369 (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
371 assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
372 port2 = unused_port(@addr)
377 ucfg.syswrite("listen %(#@addr:#@port)\n")
378 ucfg.syswrite("listen %(#@addr:#{port2})\n")
379 ucfg.syswrite("pid %(#{pid_file})\n")
380 Process.kill(:USR2, current_pid)
382 wait_for_file(old_file)
383 wait_for_file(pid_file)
384 new_pid = File.read(pid_file).to_i
385 assert_not_equal current_pid, new_pid
386 assert_equal current_pid, File.read(old_file).to_i
387 results = retry_hit(["http://#{@addr}:#{@port}/",
388 "http://#{@addr}:#{port2}/"])
389 assert_equal String, results[0].class
390 assert_equal String, results[1].class
392 Process.kill(:QUIT, current_pid)
393 Process.kill(:QUIT, new_pid)
396 def test_broken_reexec_ru
397 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
398 pid_file = "#{@tmpdir}/test.pid"
399 old_file = "#{pid_file}.oldbin"
400 ucfg = Tempfile.new('unicorn_test_config')
401 ucfg.syswrite("pid %(#{pid_file})\n")
402 ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
405 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
408 results = retry_hit(["http://#{@addr}:#{@port}/"])
409 assert_equal String, results[0].class
411 wait_for_file(pid_file)
413 Process.kill(:USR2, File.read(pid_file).to_i)
414 wait_for_file(old_file)
415 wait_for_file(pid_file)
416 old_pid = File.read(old_file).to_i
417 Process.kill(:QUIT, old_pid)
418 wait_for_death(old_pid)
420 File.unlink("config.ru") # break reloading
421 current_pid = File.read(pid_file).to_i
422 Process.kill(:USR2, current_pid)
424 # wait for pid_file to restore itself
425 tries = DEFAULT_TRIES
427 while current_pid != File.read(pid_file).to_i
428 sleep(DEFAULT_RES) and (tries -= 1) > 0
431 (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
434 tries = DEFAULT_TRIES
435 while File.exist?(old_file)
436 (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
438 assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
439 assert_equal current_pid, File.read(pid_file).to_i
442 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
443 Process.kill(:USR2, current_pid)
444 wait_for_file(old_file)
445 wait_for_file(pid_file)
446 new_pid = File.read(pid_file).to_i
447 assert_not_equal current_pid, new_pid
448 assert_equal current_pid, File.read(old_file).to_i
449 results = retry_hit(["http://#{@addr}:#{@port}/"])
450 assert_equal String, results[0].class
452 Process.kill(:QUIT, current_pid)
453 Process.kill(:QUIT, new_pid)
456 def test_unicorn_config_listener_swap
457 port_cli = unused_port
458 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
459 ucfg = Tempfile.new('unicorn_test_config')
460 ucfg.syswrite("listen '#@addr:#@port'\n")
463 exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
466 results = retry_hit(["http://#@addr:#{port_cli}/"])
467 assert_equal String, results[0].class
468 results = retry_hit(["http://#@addr:#@port/"])
469 assert_equal String, results[0].class
471 port2 = unused_port(@addr)
474 ucfg.syswrite("listen '#@addr:#{port2}'\n")
475 Process.kill(:HUP, pid)
477 results = retry_hit(["http://#@addr:#{port2}/"])
478 assert_equal String, results[0].class
479 results = retry_hit(["http://#@addr:#{port_cli}/"])
480 assert_equal String, results[0].class
481 reuse = TCPServer.new(@addr, @port)
486 def test_unicorn_config_listen_with_options
487 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
488 ucfg = Tempfile.new('unicorn_test_config')
489 ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
490 ucfg.syswrite(" :rcvbuf => 4096,\n")
491 ucfg.syswrite(" :sndbuf => 4096\n")
493 redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
495 results = retry_hit(["http://#{@addr}:#{@port}/"])
496 assert_equal String, results[0].class
500 def test_unicorn_config_per_worker_listen
502 pid_spit = 'use Rack::ContentLength;' \
503 'run proc { |e| [ 200, {"content-type"=>"text/plain"}, ["#$$\\n"] ] }'
504 File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
505 tmp = Tempfile.new('test.socket')
506 File.unlink(tmp.path)
507 ucfg = Tempfile.new('unicorn_test_config')
508 ucfg.syswrite("listen '#@addr:#@port'\n")
509 ucfg.syswrite("after_fork { |s,w|\n")
510 ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
511 ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
512 ucfg.syswrite("\n}\n")
514 redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
516 results = retry_hit(["http://#{@addr}:#{@port}/"])
517 assert_equal String, results[0].class
518 worker_pid = results[0].to_i
519 assert_not_equal pid, worker_pid
520 s = unix_socket(tmp.path)
521 s.syswrite("GET / HTTP/1.0\r\n\r\n")
523 loop { results << s.sysread(4096) } rescue nil
525 assert_equal worker_pid, results.split(/\r\n/).last.to_i
526 results = hit(["http://#@addr:#{port2}/"])
527 assert_equal String, results[0].class
528 assert_equal worker_pid, results[0].to_i
532 def test_unicorn_config_listen_augments_cli
533 port2 = unused_port(@addr)
534 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
535 ucfg = Tempfile.new('unicorn_test_config')
536 ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
539 exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
542 uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
543 results = retry_hit(uris)
544 assert_equal results.size, uris.size
545 assert_equal String, results[0].class
546 assert_equal String, results[1].class
550 def test_weird_config_settings
551 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
552 ucfg = Tempfile.new('unicorn_test_config')
553 proc_total = HEAVY_WORKERS + 1 # + 1 for master
554 ucfg.syswrite(HEAVY_CFG)
557 exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
561 results = retry_hit(["http://#{@addr}:#{@port}/"])
562 assert_equal String, results[0].class
563 wait_master_ready(COMMON_TMP.path)
564 wait_workers_ready(COMMON_TMP.path, HEAVY_WORKERS)
565 bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
566 assert_equal HEAVY_WORKERS, bf.size
567 rotate = Tempfile.new('unicorn_rotate')
569 File.rename(COMMON_TMP.path, rotate.path)
570 Process.kill(:USR1, pid)
572 wait_for_file(COMMON_TMP.path)
573 assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
574 # USR1 should've been passed to all workers
575 tries = DEFAULT_TRIES
576 log = File.readlines(rotate.path)
577 while (tries -= 1) > 0 &&
578 log.grep(/reopening logs\.\.\./).size < proc_total
580 log = File.readlines(rotate.path)
582 assert_equal proc_total, log.grep(/reopening logs\.\.\./).size
583 assert_equal 0, log.grep(/done reopening logs/).size
585 tries = DEFAULT_TRIES
586 log = File.readlines(COMMON_TMP.path)
587 while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < proc_total
589 log = File.readlines(COMMON_TMP.path)
591 assert_equal proc_total, log.grep(/done reopening logs/).size
592 assert_equal 0, log.grep(/reopening logs\.\.\./).size
594 Process.kill(:QUIT, pid)
595 pid, status = Process.waitpid2(pid)
597 assert status.success?, "exited successfully"
600 def test_read_embedded_cli_switches
601 File.open("config.ru", "wb") do |fp|
602 fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
605 pid = fork { redirect_test_io { exec($unicorn_bin) } }
606 results = retry_hit(["http://#{@addr}:#{@port}/"])
607 assert_equal String, results[0].class
612 libdir = "#{@tmpdir}/lib"
613 FileUtils.mkpath([ libdir ])
614 config_path = "#{libdir}/hello.rb"
615 File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
619 exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
622 results = retry_hit(["http://#{@addr}:#{@port}/"])
623 assert_equal String, results[0].class
628 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
629 pid_file = "#{@tmpdir}/test.pid"
632 exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
635 reexec_basic_test(pid, pid_file)
638 def test_reexec_alt_config
639 config_file = "#{@tmpdir}/foo.ru"
640 File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
641 pid_file = "#{@tmpdir}/test.pid"
644 exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
647 reexec_basic_test(pid, pid_file)
650 def test_socket_unlinked_restore
652 sock = Tempfile.new('unicorn_test_sock')
653 sock_path = sock.path
654 @sockets << sock_path
656 ucfg = Tempfile.new('unicorn_test_config')
657 ucfg.syswrite("listen \"#{sock_path}\"\n")
659 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
660 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
661 wait_for_file(sock_path)
662 assert File.socket?(sock_path)
664 sock = unix_socket(sock_path)
665 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
666 results = sock.sysread(4096)
668 assert_equal String, results.class
669 File.unlink(sock_path)
670 Process.kill(:HUP, pid)
671 wait_for_file(sock_path)
672 assert File.socket?(sock_path)
674 sock = unix_socket(sock_path)
675 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
676 results = sock.sysread(4096)
678 assert_equal String, results.class
681 def test_unicorn_config_file
682 pid_file = "#{@tmpdir}/test.pid"
683 sock = Tempfile.new('unicorn_test_sock')
684 sock_path = sock.path
686 @sockets << sock_path
688 log = Tempfile.new('unicorn_test_log')
689 ucfg = Tempfile.new('unicorn_test_config')
690 ucfg.syswrite("listen \"#{sock_path}\"\n")
691 ucfg.syswrite("pid \"#{pid_file}\"\n")
692 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
695 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
698 exec($unicorn_bin, "-l#{@addr}:#{@port}",
699 "-P#{pid_file}", "-c#{ucfg.path}")
702 results = retry_hit(["http://#{@addr}:#{@port}/"])
703 assert_equal String, results[0].class
704 wait_master_ready(log.path)
705 assert File.exist?(pid_file), "pid_file created"
706 assert_equal pid, File.read(pid_file).to_i
707 assert File.socket?(sock_path), "socket created"
709 sock = unix_socket(sock_path)
710 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
711 results = sock.sysread(4096)
713 assert_equal String, results.class
715 # try reloading the config
716 sock = Tempfile.new('new_test_sock')
717 new_sock_path = sock.path
718 @sockets << new_sock_path
720 new_log = Tempfile.new('unicorn_test_log')
722 assert_equal 0, new_log.size
724 ucfg = File.open(ucfg.path, "wb")
725 ucfg.syswrite("listen \"#{sock_path}\"\n")
726 ucfg.syswrite("listen \"#{new_sock_path}\"\n")
727 ucfg.syswrite("pid \"#{pid_file}\"\n")
728 ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
730 Process.kill(:HUP, pid)
732 wait_for_file(new_sock_path)
733 assert File.socket?(new_sock_path), "socket exists"
734 @sockets.each do |path|
735 sock = unix_socket(path)
736 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
737 results = sock.sysread(4096)
738 assert_equal String, results.class
741 assert_not_equal 0, new_log.size
742 reexec_usr2_quit_test(pid, pid_file)
745 def test_daemonize_reexec
746 pid_file = "#{@tmpdir}/test.pid"
747 log = Tempfile.new('unicorn_test_log')
748 ucfg = Tempfile.new('unicorn_test_config')
749 ucfg.syswrite("pid \"#{pid_file}\"\n")
750 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
753 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
756 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
759 results = retry_hit(["http://#{@addr}:#{@port}/"])
760 assert_equal String, results[0].class
761 wait_for_file(pid_file)
762 new_pid = File.read(pid_file).to_i
763 assert_not_equal pid, new_pid
764 pid, status = Process.waitpid2(pid)
765 assert status.success?, "original process exited successfully"
766 Process.kill(0, new_pid)
767 reexec_usr2_quit_test(new_pid, pid_file)
770 def test_daemonize_redirect_fail
771 pid_file = "#{@tmpdir}/test.pid"
772 ucfg = Tempfile.new('unicorn_test_config')
773 ucfg.syswrite("pid #{pid_file}\"\n")
774 err = Tempfile.new('stderr')
775 out = Tempfile.new('stdout ')
777 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
779 $stderr.reopen(err.path, "a")
780 $stdout.reopen(out.path, "a")
781 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
783 pid, status = Process.waitpid2(pid)
784 assert ! status.success?, "original process exited successfully"
785 sleep 1 # can't waitpid on a daemonized process :<
786 assert err.stat.size > 0
789 def test_reexec_fd_leak
790 unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
791 warn "FD leak test only works on Linux at the moment"
794 pid_file = "#{@tmpdir}/test.pid"
795 log = Tempfile.new('unicorn_test_log')
797 ucfg = Tempfile.new('unicorn_test_config')
798 ucfg.syswrite("pid \"#{pid_file}\"\n")
799 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
800 ucfg.syswrite("stderr_path '#{log.path}'\n")
801 ucfg.syswrite("stdout_path '#{log.path}'\n")
804 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
807 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
811 wait_master_ready(log.path)
812 wait_workers_ready(log.path, 1)
813 File.truncate(log.path, 0)
814 wait_for_file(pid_file)
815 orig_pid = pid = File.read(pid_file).to_i
816 orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
818 expect_size = orig_fds.size
820 Process.kill(:USR2, pid)
821 wait_for_file("#{pid_file}.oldbin")
822 Process.kill(:QUIT, pid)
826 wait_master_ready(log.path)
827 wait_workers_ready(log.path, 1)
828 File.truncate(log.path, 0)
829 wait_for_file(pid_file)
830 pid = File.read(pid_file).to_i
831 assert_not_equal orig_pid, pid
832 curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
835 # we could've inherited descriptors the first time around
836 assert expect_size >= curr_fds.size, curr_fds.inspect
837 expect_size = curr_fds.size
839 Process.kill(:USR2, pid)
840 wait_for_file("#{pid_file}.oldbin")
841 Process.kill(:QUIT, pid)
845 wait_master_ready(log.path)
846 wait_workers_ready(log.path, 1)
847 File.truncate(log.path, 0)
848 wait_for_file(pid_file)
849 pid = File.read(pid_file).to_i
850 curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
852 assert_equal expect_size, curr_fds.size, curr_fds.inspect
854 Process.kill(:QUIT, pid)
858 def hup_test_common(preload, check_client=false)
859 File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
860 pid_file = Tempfile.new('pid')
861 ucfg = Tempfile.new('unicorn_test_config')
862 ucfg.syswrite("listen '#@addr:#@port'\n")
863 ucfg.syswrite("pid '#{pid_file.path}'\n")
864 ucfg.syswrite("preload_app true\n") if preload
865 ucfg.syswrite("check_client_connection true\n") if check_client
866 ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
867 ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
869 redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
871 _, status = Process.waitpid2(pid)
872 assert status.success?
873 wait_master_ready("test_stderr.#$$.log")
874 wait_workers_ready("test_stderr.#$$.log", 1)
875 uri = URI.parse("http://#@addr:#@port/")
876 pids = Tempfile.new('worker_pids')
881 at_exit { pids.syswrite(bodies.inspect) }
882 trap(:TERM) { exit(0) }
885 rv = Net::HTTP.get(uri)
892 elsif bodies.size > 1
899 assert_equal '1', r.read(1)
900 daemon_pid = File.read(pid_file.path).to_i
901 assert daemon_pid > 0
902 Process.kill(:HUP, daemon_pid)
903 assert_equal '2', r.read(1)
904 Process.kill(:TERM, hitter)
905 _, hitter_status = Process.waitpid2(hitter)
906 assert(hitter_status.success?,
907 "invalid: #{hitter_status.inspect} #{File.read(pids.path)}" \
908 "#{File.read("test_stderr.#$$.log")}")
910 pids = eval(pids.read)
911 assert_kind_of(Hash, pids)
912 assert_equal 2, pids.size
914 assert_kind_of(Integer, x)
918 Process.kill(:QUIT, daemon_pid)
919 wait_for_death(daemon_pid)
922 def test_preload_app_hup
923 hup_test_common(true)
927 hup_test_common(false)
930 def test_check_client_hup
931 hup_test_common(false, true)
934 def test_default_listen_hup_holds_listener
935 default_listen_lock do
936 res, pid_path = default_listen_setup
937 daemon_pid = File.read(pid_path).to_i
938 Process.kill(:HUP, daemon_pid)
939 wait_workers_ready("test_stderr.#$$.log", 1)
940 res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
941 assert_match %r{\d+}, res2.first
942 assert res2.first != res.first
943 Process.kill(:QUIT, daemon_pid)
944 wait_for_death(daemon_pid)
948 def test_default_listen_upgrade_holds_listener
949 default_listen_lock do
950 res, pid_path = default_listen_setup
951 daemon_pid = File.read(pid_path).to_i
953 Process.kill(:USR2, daemon_pid)
954 wait_for_file("#{pid_path}.oldbin")
955 wait_for_file(pid_path)
956 Process.kill(:QUIT, daemon_pid)
957 wait_for_death(daemon_pid)
959 daemon_pid = File.read(pid_path).to_i
960 wait_workers_ready("test_stderr.#$$.log", 1)
961 File.truncate("test_stderr.#$$.log", 0)
963 res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
964 assert_match %r{\d+}, res2.first
965 assert res2.first != res.first
967 Process.kill(:HUP, daemon_pid)
968 wait_workers_ready("test_stderr.#$$.log", 1)
969 File.truncate("test_stderr.#$$.log", 0)
970 res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
971 assert res2.first != res3.first
973 Process.kill(:QUIT, daemon_pid)
974 wait_for_death(daemon_pid)
978 def default_listen_setup
979 File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
980 pid_path = (tmp = Tempfile.new('pid')).path
982 ucfg = Tempfile.new('unicorn_test_config')
983 ucfg.syswrite("pid '#{pid_path}'\n")
984 ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
985 ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
987 redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
989 _, status = Process.waitpid2(pid)
990 assert status.success?
991 wait_master_ready("test_stderr.#$$.log")
992 wait_workers_ready("test_stderr.#$$.log", 1)
993 File.truncate("test_stderr.#$$.log", 0)
994 res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
995 assert_match %r{\d+}, res.first
999 # we need to flock() something to prevent these tests from running
1000 def default_listen_lock(&block)
1001 fp = File.open(FLOCK_PATH, "rb")
1003 fp.flock(File::LOCK_EX)
1005 TCPServer.new(Unicorn::Const::DEFAULT_HOST,
1006 Unicorn::Const::DEFAULT_PORT).close
1007 rescue Errno::EADDRINUSE, Errno::EACCES
1008 warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
1012 # unused_port should never take this, but we may run an environment
1013 # where tests are being run against older unicorns...
1014 lock_path = "#{Dir::tmpdir}/unicorn_test." \
1015 "#{Unicorn::Const::DEFAULT_LISTEN}.lock"
1017 File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
1019 rescue Errno::EEXIST
1023 File.unlink(lock_path) if lock_path
1026 fp.flock(File::LOCK_UN)