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_sd_listen_fds_emulation
100 File.open("config.ru", "wb") { |fp| fp.write(HI) }
101 sock = TCPServer.new(@addr, @port)
103 [ %W(-l #@addr:#@port), nil ].each do |l|
104 sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 0)
108 # pretend to be systemd
109 ENV['LISTEN_PID'] = "#$$"
110 ENV['LISTEN_FDS'] = '1'
112 # 3 = SD_LISTEN_FDS_START
113 args = [ $unicorn_bin ]
115 args << { 3 => sock }
119 res = hit(["http://#@addr:#@port/"])
120 assert_equal [ "HI\n" ], res
122 assert_equal 1, sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).int,
123 'unicorn should always set SO_KEEPALIVE on inherited sockets'
127 # disabled test on old Rubies: https://bugs.ruby-lang.org/issues/11336
128 # [ruby-core:69895] [Bug #11336] fixed by r51576
129 end if RUBY_VERSION.to_f >= 2.3
131 def test_inherit_listener_unspecified
132 File.open("config.ru", "wb") { |fp| fp.write(HI) }
133 sock = TCPServer.new(@addr, @port)
134 sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 0)
138 ENV['UNICORN_FD'] = sock.fileno.to_s
139 exec($unicorn_bin, sock.fileno => sock.fileno)
142 res = hit(["http://#@addr:#@port/"])
143 assert_equal [ "HI\n" ], res
145 assert_equal 1, sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).int,
146 'unicorn should always set SO_KEEPALIVE on inherited sockets'
151 def test_working_directory_rel_path_config_file
152 other = Tempfile.new('unicorn.wd')
153 File.unlink(other.path)
154 Dir.mkdir(other.path)
155 File.open("config.ru", "wb") do |fp|
156 fp.syswrite WORKING_DIRECTORY_CHECK_RU
158 FileUtils.cp("config.ru", other.path + "/config.ru")
161 tmp = File.open('unicorn.config', 'wb')
163 working_directory '#@tmpdir'
164 listen '#@addr:#@port'
166 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
167 wait_workers_ready("test_stderr.#{pid}.log", 1)
168 results = hit(["http://#@addr:#@port/"])
169 assert_equal @tmpdir, results.first
170 File.truncate("test_stderr.#{pid}.log", 0)
175 working_directory '#{other.path}'
176 listen '#@addr:#@port'
179 Process.kill(:HUP, pid)
181 re = /config_file=(.+) would not be accessible in working_directory=(.+)/
184 lines = File.readlines("test_stderr.#{pid}.log")
187 File.truncate("test_stderr.#{pid}.log", 0)
188 FileUtils.cp('unicorn.config', other.path + "/unicorn.config")
189 Process.kill(:HUP, pid)
190 wait_workers_ready("test_stderr.#{pid}.log", 1)
191 results = hit(["http://#@addr:#@port/"])
192 assert_equal other.path, results.first
194 Process.kill(:QUIT, pid)
196 FileUtils.rmtree(other.path)
199 def test_working_directory
200 other = Tempfile.new('unicorn.wd')
201 File.unlink(other.path)
202 Dir.mkdir(other.path)
203 File.open("config.ru", "wb") do |fp|
204 fp.syswrite WORKING_DIRECTORY_CHECK_RU
206 FileUtils.cp("config.ru", other.path + "/config.ru")
207 tmp = Tempfile.new('unicorn.config')
209 working_directory '#@tmpdir'
210 listen '#@addr:#@port'
212 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
213 wait_workers_ready("test_stderr.#{pid}.log", 1)
214 results = hit(["http://#@addr:#@port/"])
215 assert_equal @tmpdir, results.first
216 File.truncate("test_stderr.#{pid}.log", 0)
221 working_directory '#{other.path}'
222 listen '#@addr:#@port'
225 Process.kill(:HUP, pid)
226 wait_workers_ready("test_stderr.#{pid}.log", 1)
227 results = hit(["http://#@addr:#@port/"])
228 assert_equal other.path, results.first
230 Process.kill(:QUIT, pid)
232 FileUtils.rmtree(other.path)
235 def test_working_directory_controls_relative_paths
236 other = Tempfile.new('unicorn.wd')
237 File.unlink(other.path)
238 Dir.mkdir(other.path)
239 File.open("config.ru", "wb") do |fp|
240 fp.syswrite WORKING_DIRECTORY_CHECK_RU
242 FileUtils.cp("config.ru", other.path + "/config.ru")
243 system('mkfifo', "#{other.path}/fifo")
244 tmp = Tempfile.new('unicorn.config')
247 stderr_path "stderr_log_here"
248 stdout_path "stdout_log_here"
249 working_directory '#{other.path}'
250 listen '#@addr:#@port'
251 after_fork do |server, worker|
252 File.open("fifo", "wb").close
255 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
256 File.open("#{other.path}/fifo", "rb").close
258 assert ! File.exist?("stderr_log_here")
259 assert ! File.exist?("stdout_log_here")
260 assert ! File.exist?("pid_file_here")
262 assert ! File.exist?("#@tmpdir/stderr_log_here")
263 assert ! File.exist?("#@tmpdir/stdout_log_here")
264 assert ! File.exist?("#@tmpdir/pid_file_here")
266 assert File.exist?("#{other.path}/pid_file_here")
267 assert_equal "#{pid}\n", File.read("#{other.path}/pid_file_here")
268 assert File.exist?("#{other.path}/stderr_log_here")
269 assert File.exist?("#{other.path}/stdout_log_here")
270 wait_master_ready("#{other.path}/stderr_log_here")
272 Process.kill(:QUIT, pid)
274 FileUtils.rmtree(other.path)
278 def test_exit_signals
279 %w(INT TERM QUIT).each do |sig|
280 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
281 pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
282 wait_master_ready("test_stderr.#{pid}.log")
283 wait_workers_ready("test_stderr.#{pid}.log", 1)
285 Process.kill(sig, pid)
286 pid, status = Process.waitpid2(pid)
288 reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
289 assert_equal 1, reaped.size
290 assert status.exited?
295 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
297 redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
299 results = retry_hit(["http://#{@addr}:#{@port}/"])
300 assert_equal String, results[0].class
304 def test_rack_env_unset
305 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
306 pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
307 results = retry_hit(["http://#{@addr}:#{@port}/"])
308 assert_equal "development", results.first
312 def test_rack_env_cli_set
313 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
315 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
317 results = retry_hit(["http://#{@addr}:#{@port}/"])
318 assert_equal "asdf", results.first
322 def test_rack_env_ENV_set
323 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
325 ENV["RACK_ENV"] = "foobar"
326 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") }
328 results = retry_hit(["http://#{@addr}:#{@port}/"])
329 assert_equal "foobar", results.first
333 def test_rack_env_cli_override_ENV
334 File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
336 ENV["RACK_ENV"] = "foobar"
337 redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
339 results = retry_hit(["http://#{@addr}:#{@port}/"])
340 assert_equal "asdf", results.first
345 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
346 pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
347 log = "test_stderr.#{pid}.log"
348 wait_master_ready(log)
350 Process.kill(:TTIN, pid)
351 wait_workers_ready(log, i)
353 File.truncate(log, 0)
355 [ 2, 1, 0].each { |i|
356 Process.kill(:TTOU, pid)
357 DEFAULT_TRIES.times {
359 reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
360 break if reaped.size == 1
362 assert_equal 1, reaped.size
368 assert(system($unicorn_bin, "-h"), "help text returns true")
370 assert_equal 0, File.stat("test_stderr.#$$.log").size
371 assert_not_equal 0, File.stat("test_stdout.#$$.log").size
372 lines = File.readlines("test_stdout.#$$.log")
374 # Be considerate of the on-call technician working from their
375 # mobile phone or netbook on a slow connection :)
376 assert lines.size <= 24, "help height fits in an ANSI terminal window"
379 assert line.size <= 80, "help width fits in an ANSI terminal window"
383 def test_broken_reexec_config
384 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
385 pid_file = "#{@tmpdir}/test.pid"
386 old_file = "#{pid_file}.oldbin"
387 ucfg = Tempfile.new('unicorn_test_config')
388 ucfg.syswrite("listen %(#@addr:#@port)\n")
389 ucfg.syswrite("pid %(#{pid_file})\n")
390 ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
393 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
396 results = retry_hit(["http://#{@addr}:#{@port}/"])
397 assert_equal String, results[0].class
399 wait_for_file(pid_file)
401 Process.kill(:USR2, File.read(pid_file).to_i)
402 wait_for_file(old_file)
403 wait_for_file(pid_file)
404 old_pid = File.read(old_file).to_i
405 Process.kill(:QUIT, old_pid)
406 wait_for_death(old_pid)
408 ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
409 current_pid = File.read(pid_file).to_i
410 Process.kill(:USR2, current_pid)
412 # wait for pid_file to restore itself
413 tries = DEFAULT_TRIES
415 while current_pid != File.read(pid_file).to_i
416 sleep(DEFAULT_RES) and (tries -= 1) > 0
419 (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
421 assert_equal current_pid, File.read(pid_file).to_i
423 tries = DEFAULT_TRIES
424 while File.exist?(old_file)
425 (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
427 assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
428 port2 = unused_port(@addr)
433 ucfg.syswrite("listen %(#@addr:#@port)\n")
434 ucfg.syswrite("listen %(#@addr:#{port2})\n")
435 ucfg.syswrite("pid %(#{pid_file})\n")
436 Process.kill(:USR2, current_pid)
438 wait_for_file(old_file)
439 wait_for_file(pid_file)
440 new_pid = File.read(pid_file).to_i
441 assert_not_equal current_pid, new_pid
442 assert_equal current_pid, File.read(old_file).to_i
443 results = retry_hit(["http://#{@addr}:#{@port}/",
444 "http://#{@addr}:#{port2}/"])
445 assert_equal String, results[0].class
446 assert_equal String, results[1].class
448 Process.kill(:QUIT, current_pid)
449 Process.kill(:QUIT, new_pid)
452 def test_broken_reexec_ru
453 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
454 pid_file = "#{@tmpdir}/test.pid"
455 old_file = "#{pid_file}.oldbin"
456 ucfg = Tempfile.new('unicorn_test_config')
457 ucfg.syswrite("pid %(#{pid_file})\n")
458 ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
461 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
464 results = retry_hit(["http://#{@addr}:#{@port}/"])
465 assert_equal String, results[0].class
467 wait_for_file(pid_file)
469 Process.kill(:USR2, File.read(pid_file).to_i)
470 wait_for_file(old_file)
471 wait_for_file(pid_file)
472 old_pid = File.read(old_file).to_i
473 Process.kill(:QUIT, old_pid)
474 wait_for_death(old_pid)
476 File.unlink("config.ru") # break reloading
477 current_pid = File.read(pid_file).to_i
478 Process.kill(:USR2, current_pid)
480 # wait for pid_file to restore itself
481 tries = DEFAULT_TRIES
483 while current_pid != File.read(pid_file).to_i
484 sleep(DEFAULT_RES) and (tries -= 1) > 0
487 (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
490 tries = DEFAULT_TRIES
491 while File.exist?(old_file)
492 (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
494 assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
495 assert_equal current_pid, File.read(pid_file).to_i
498 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
499 Process.kill(:USR2, current_pid)
500 wait_for_file(old_file)
501 wait_for_file(pid_file)
502 new_pid = File.read(pid_file).to_i
503 assert_not_equal current_pid, new_pid
504 assert_equal current_pid, File.read(old_file).to_i
505 results = retry_hit(["http://#{@addr}:#{@port}/"])
506 assert_equal String, results[0].class
508 Process.kill(:QUIT, current_pid)
509 Process.kill(:QUIT, new_pid)
512 def test_unicorn_config_listener_swap
513 port_cli = unused_port
514 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
515 ucfg = Tempfile.new('unicorn_test_config')
516 ucfg.syswrite("listen '#@addr:#@port'\n")
519 exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
522 results = retry_hit(["http://#@addr:#{port_cli}/"])
523 assert_equal String, results[0].class
524 results = retry_hit(["http://#@addr:#@port/"])
525 assert_equal String, results[0].class
527 port2 = unused_port(@addr)
530 ucfg.syswrite("listen '#@addr:#{port2}'\n")
531 Process.kill(:HUP, pid)
533 results = retry_hit(["http://#@addr:#{port2}/"])
534 assert_equal String, results[0].class
535 results = retry_hit(["http://#@addr:#{port_cli}/"])
536 assert_equal String, results[0].class
537 reuse = TCPServer.new(@addr, @port)
542 def test_unicorn_config_listen_with_options
543 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
544 ucfg = Tempfile.new('unicorn_test_config')
545 ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
546 ucfg.syswrite(" :rcvbuf => 4096,\n")
547 ucfg.syswrite(" :sndbuf => 4096\n")
549 redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
551 results = retry_hit(["http://#{@addr}:#{@port}/"])
552 assert_equal String, results[0].class
556 def test_unicorn_config_per_worker_listen
558 pid_spit = 'use Rack::ContentLength;' \
559 'run proc { |e| [ 200, {"Content-Type"=>"text/plain"}, ["#$$\\n"] ] }'
560 File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
561 tmp = Tempfile.new('test.socket')
562 File.unlink(tmp.path)
563 ucfg = Tempfile.new('unicorn_test_config')
564 ucfg.syswrite("listen '#@addr:#@port'\n")
565 ucfg.syswrite("after_fork { |s,w|\n")
566 ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
567 ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
568 ucfg.syswrite("\n}\n")
570 redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
572 results = retry_hit(["http://#{@addr}:#{@port}/"])
573 assert_equal String, results[0].class
574 worker_pid = results[0].to_i
575 assert_not_equal pid, worker_pid
576 s = UNIXSocket.new(tmp.path)
577 s.syswrite("GET / HTTP/1.0\r\n\r\n")
579 loop { results << s.sysread(4096) } rescue nil
581 assert_equal worker_pid, results.split(/\r\n/).last.to_i
582 results = hit(["http://#@addr:#{port2}/"])
583 assert_equal String, results[0].class
584 assert_equal worker_pid, results[0].to_i
588 def test_unicorn_config_listen_augments_cli
589 port2 = unused_port(@addr)
590 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
591 ucfg = Tempfile.new('unicorn_test_config')
592 ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
595 exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
598 uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
599 results = retry_hit(uris)
600 assert_equal results.size, uris.size
601 assert_equal String, results[0].class
602 assert_equal String, results[1].class
606 def test_weird_config_settings
607 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
608 ucfg = Tempfile.new('unicorn_test_config')
609 ucfg.syswrite(HEAVY_CFG)
612 exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
616 results = retry_hit(["http://#{@addr}:#{@port}/"])
617 assert_equal String, results[0].class
618 wait_master_ready(COMMON_TMP.path)
619 wait_workers_ready(COMMON_TMP.path, 4)
620 bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
621 assert_equal 4, bf.size
622 rotate = Tempfile.new('unicorn_rotate')
624 File.rename(COMMON_TMP.path, rotate.path)
625 Process.kill(:USR1, pid)
627 wait_for_file(COMMON_TMP.path)
628 assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
629 # USR1 should've been passed to all workers
630 tries = DEFAULT_TRIES
631 log = File.readlines(rotate.path)
632 while (tries -= 1) > 0 &&
633 log.grep(/reopening logs\.\.\./).size < 5
635 log = File.readlines(rotate.path)
637 assert_equal 5, log.grep(/reopening logs\.\.\./).size
638 assert_equal 0, log.grep(/done reopening logs/).size
640 tries = DEFAULT_TRIES
641 log = File.readlines(COMMON_TMP.path)
642 while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
644 log = File.readlines(COMMON_TMP.path)
646 assert_equal 5, log.grep(/done reopening logs/).size
647 assert_equal 0, log.grep(/reopening logs\.\.\./).size
649 Process.kill(:QUIT, pid)
650 pid, status = Process.waitpid2(pid)
652 assert status.success?, "exited successfully"
655 def test_read_embedded_cli_switches
656 File.open("config.ru", "wb") do |fp|
657 fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
660 pid = fork { redirect_test_io { exec($unicorn_bin) } }
661 results = retry_hit(["http://#{@addr}:#{@port}/"])
662 assert_equal String, results[0].class
666 def test_config_ru_alt_path
667 config_path = "#{@tmpdir}/foo.ru"
668 File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
672 exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
675 results = retry_hit(["http://#{@addr}:#{@port}/"])
676 assert_equal String, results[0].class
681 libdir = "#{@tmpdir}/lib"
682 FileUtils.mkpath([ libdir ])
683 config_path = "#{libdir}/hello.rb"
684 File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
688 exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
691 results = retry_hit(["http://#{@addr}:#{@port}/"])
692 assert_equal String, results[0].class
697 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
698 pid_file = "#{@tmpdir}/test.pid"
701 exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
704 reexec_basic_test(pid, pid_file)
707 def test_reexec_alt_config
708 config_file = "#{@tmpdir}/foo.ru"
709 File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
710 pid_file = "#{@tmpdir}/test.pid"
713 exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
716 reexec_basic_test(pid, pid_file)
719 def test_socket_unlinked_restore
721 sock = Tempfile.new('unicorn_test_sock')
722 sock_path = sock.path
723 @sockets << sock_path
725 ucfg = Tempfile.new('unicorn_test_config')
726 ucfg.syswrite("listen \"#{sock_path}\"\n")
728 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
729 pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
730 wait_for_file(sock_path)
731 assert File.socket?(sock_path)
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
738 File.unlink(sock_path)
739 Process.kill(:HUP, pid)
740 wait_for_file(sock_path)
741 assert File.socket?(sock_path)
743 sock = UNIXSocket.new(sock_path)
744 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
745 results = sock.sysread(4096)
747 assert_equal String, results.class
750 def test_unicorn_config_file
751 pid_file = "#{@tmpdir}/test.pid"
752 sock = Tempfile.new('unicorn_test_sock')
753 sock_path = sock.path
755 @sockets << sock_path
757 log = Tempfile.new('unicorn_test_log')
758 ucfg = Tempfile.new('unicorn_test_config')
759 ucfg.syswrite("listen \"#{sock_path}\"\n")
760 ucfg.syswrite("pid \"#{pid_file}\"\n")
761 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
764 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
767 exec($unicorn_bin, "-l#{@addr}:#{@port}",
768 "-P#{pid_file}", "-c#{ucfg.path}")
771 results = retry_hit(["http://#{@addr}:#{@port}/"])
772 assert_equal String, results[0].class
773 wait_master_ready(log.path)
774 assert File.exist?(pid_file), "pid_file created"
775 assert_equal pid, File.read(pid_file).to_i
776 assert File.socket?(sock_path), "socket created"
778 sock = UNIXSocket.new(sock_path)
779 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
780 results = sock.sysread(4096)
782 assert_equal String, results.class
784 # try reloading the config
785 sock = Tempfile.new('new_test_sock')
786 new_sock_path = sock.path
787 @sockets << new_sock_path
789 new_log = Tempfile.new('unicorn_test_log')
791 assert_equal 0, new_log.size
793 ucfg = File.open(ucfg.path, "wb")
794 ucfg.syswrite("listen \"#{sock_path}\"\n")
795 ucfg.syswrite("listen \"#{new_sock_path}\"\n")
796 ucfg.syswrite("pid \"#{pid_file}\"\n")
797 ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
799 Process.kill(:HUP, pid)
801 wait_for_file(new_sock_path)
802 assert File.socket?(new_sock_path), "socket exists"
803 @sockets.each do |path|
804 sock = UNIXSocket.new(path)
805 sock.syswrite("GET / HTTP/1.0\r\n\r\n")
806 results = sock.sysread(4096)
807 assert_equal String, results.class
810 assert_not_equal 0, new_log.size
811 reexec_usr2_quit_test(pid, pid_file)
814 def test_daemonize_reexec
815 pid_file = "#{@tmpdir}/test.pid"
816 log = Tempfile.new('unicorn_test_log')
817 ucfg = Tempfile.new('unicorn_test_config')
818 ucfg.syswrite("pid \"#{pid_file}\"\n")
819 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
822 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
825 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
828 results = retry_hit(["http://#{@addr}:#{@port}/"])
829 assert_equal String, results[0].class
830 wait_for_file(pid_file)
831 new_pid = File.read(pid_file).to_i
832 assert_not_equal pid, new_pid
833 pid, status = Process.waitpid2(pid)
834 assert status.success?, "original process exited successfully"
835 Process.kill(0, new_pid)
836 reexec_usr2_quit_test(new_pid, pid_file)
839 def test_daemonize_redirect_fail
840 pid_file = "#{@tmpdir}/test.pid"
841 ucfg = Tempfile.new('unicorn_test_config')
842 ucfg.syswrite("pid #{pid_file}\"\n")
843 err = Tempfile.new('stderr')
844 out = Tempfile.new('stdout ')
846 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
848 $stderr.reopen(err.path, "a")
849 $stdout.reopen(out.path, "a")
850 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
852 pid, status = Process.waitpid2(pid)
853 assert ! status.success?, "original process exited successfully"
854 sleep 1 # can't waitpid on a daemonized process :<
855 assert err.stat.size > 0
858 def test_reexec_fd_leak
859 unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
860 warn "FD leak test only works on Linux at the moment"
863 pid_file = "#{@tmpdir}/test.pid"
864 log = Tempfile.new('unicorn_test_log')
866 ucfg = Tempfile.new('unicorn_test_config')
867 ucfg.syswrite("pid \"#{pid_file}\"\n")
868 ucfg.syswrite("logger Logger.new('#{log.path}')\n")
869 ucfg.syswrite("stderr_path '#{log.path}'\n")
870 ucfg.syswrite("stdout_path '#{log.path}'\n")
873 File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
876 exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
880 wait_master_ready(log.path)
881 wait_workers_ready(log.path, 1)
882 File.truncate(log.path, 0)
883 wait_for_file(pid_file)
884 orig_pid = pid = File.read(pid_file).to_i
885 orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
887 expect_size = orig_fds.size
889 Process.kill(:USR2, pid)
890 wait_for_file("#{pid_file}.oldbin")
891 Process.kill(:QUIT, pid)
895 wait_master_ready(log.path)
896 wait_workers_ready(log.path, 1)
897 File.truncate(log.path, 0)
898 wait_for_file(pid_file)
899 pid = File.read(pid_file).to_i
900 assert_not_equal orig_pid, pid
901 curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
904 # we could've inherited descriptors the first time around
905 assert expect_size >= curr_fds.size, curr_fds.inspect
906 expect_size = curr_fds.size
908 Process.kill(:USR2, pid)
909 wait_for_file("#{pid_file}.oldbin")
910 Process.kill(:QUIT, pid)
914 wait_master_ready(log.path)
915 wait_workers_ready(log.path, 1)
916 File.truncate(log.path, 0)
917 wait_for_file(pid_file)
918 pid = File.read(pid_file).to_i
919 curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
921 assert_equal expect_size, curr_fds.size, curr_fds.inspect
923 Process.kill(:QUIT, pid)
927 def hup_test_common(preload, check_client=false)
928 File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
929 pid_file = Tempfile.new('pid')
930 ucfg = Tempfile.new('unicorn_test_config')
931 ucfg.syswrite("listen '#@addr:#@port'\n")
932 ucfg.syswrite("pid '#{pid_file.path}'\n")
933 ucfg.syswrite("preload_app true\n") if preload
934 ucfg.syswrite("check_client_connection true\n") if check_client
935 ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
936 ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
938 redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
940 _, status = Process.waitpid2(pid)
941 assert status.success?
942 wait_master_ready("test_stderr.#$$.log")
943 wait_workers_ready("test_stderr.#$$.log", 1)
944 uri = URI.parse("http://#@addr:#@port/")
945 pids = Tempfile.new('worker_pids')
950 at_exit { pids.syswrite(bodies.inspect) }
951 trap(:TERM) { exit(0) }
954 rv = Net::HTTP.get(uri)
961 elsif bodies.size > 1
968 assert_equal '1', r.read(1)
969 daemon_pid = File.read(pid_file.path).to_i
970 assert daemon_pid > 0
971 Process.kill(:HUP, daemon_pid)
972 assert_equal '2', r.read(1)
973 Process.kill(:TERM, hitter)
974 _, hitter_status = Process.waitpid2(hitter)
975 assert(hitter_status.success?,
976 "invalid: #{hitter_status.inspect} #{File.read(pids.path)}" \
977 "#{File.read("test_stderr.#$$.log")}")
979 pids = eval(pids.read)
980 assert_kind_of(Hash, pids)
981 assert_equal 2, pids.size
983 assert_kind_of(Integer, x)
987 Process.kill(:QUIT, daemon_pid)
988 wait_for_death(daemon_pid)
991 def test_preload_app_hup
992 hup_test_common(true)
996 hup_test_common(false)
999 def test_check_client_hup
1000 hup_test_common(false, true)
1003 def test_default_listen_hup_holds_listener
1004 default_listen_lock do
1005 res, pid_path = default_listen_setup
1006 daemon_pid = File.read(pid_path).to_i
1007 Process.kill(:HUP, daemon_pid)
1008 wait_workers_ready("test_stderr.#$$.log", 1)
1009 res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1010 assert_match %r{\d+}, res2.first
1011 assert res2.first != res.first
1012 Process.kill(:QUIT, daemon_pid)
1013 wait_for_death(daemon_pid)
1017 def test_default_listen_upgrade_holds_listener
1018 default_listen_lock do
1019 res, pid_path = default_listen_setup
1020 daemon_pid = File.read(pid_path).to_i
1022 Process.kill(:USR2, daemon_pid)
1023 wait_for_file("#{pid_path}.oldbin")
1024 wait_for_file(pid_path)
1025 Process.kill(:QUIT, daemon_pid)
1026 wait_for_death(daemon_pid)
1028 daemon_pid = File.read(pid_path).to_i
1029 wait_workers_ready("test_stderr.#$$.log", 1)
1030 File.truncate("test_stderr.#$$.log", 0)
1032 res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1033 assert_match %r{\d+}, res2.first
1034 assert res2.first != res.first
1036 Process.kill(:HUP, daemon_pid)
1037 wait_workers_ready("test_stderr.#$$.log", 1)
1038 File.truncate("test_stderr.#$$.log", 0)
1039 res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1040 assert res2.first != res3.first
1042 Process.kill(:QUIT, daemon_pid)
1043 wait_for_death(daemon_pid)
1047 def default_listen_setup
1048 File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
1049 pid_path = (tmp = Tempfile.new('pid')).path
1051 ucfg = Tempfile.new('unicorn_test_config')
1052 ucfg.syswrite("pid '#{pid_path}'\n")
1053 ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
1054 ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
1056 redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
1058 _, status = Process.waitpid2(pid)
1059 assert status.success?
1060 wait_master_ready("test_stderr.#$$.log")
1061 wait_workers_ready("test_stderr.#$$.log", 1)
1062 File.truncate("test_stderr.#$$.log", 0)
1063 res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1064 assert_match %r{\d+}, res.first
1068 # we need to flock() something to prevent these tests from running
1069 def default_listen_lock(&block)
1070 fp = File.open(FLOCK_PATH, "rb")
1072 fp.flock(File::LOCK_EX)
1074 TCPServer.new(Unicorn::Const::DEFAULT_HOST,
1075 Unicorn::Const::DEFAULT_PORT).close
1076 rescue Errno::EADDRINUSE, Errno::EACCES
1077 warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
1081 # unused_port should never take this, but we may run an environment
1082 # where tests are being run against older unicorns...
1083 lock_path = "#{Dir::tmpdir}/unicorn_test." \
1084 "#{Unicorn::Const::DEFAULT_LISTEN}.lock"
1086 File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
1088 rescue Errno::EEXIST
1092 File.unlink(lock_path) if lock_path
1095 fp.flock(File::LOCK_UN)