diff options
Diffstat (limited to 'test/exec/test_exec.rb')
-rw-r--r-- | test/exec/test_exec.rb | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/test/exec/test_exec.rb b/test/exec/test_exec.rb index 014b270..24ba856 100644 --- a/test/exec/test_exec.rb +++ b/test/exec/test_exec.rb @@ -1,4 +1,7 @@ +# -*- encoding: binary -*- + # Copyright (c) 2009 Eric Wong +FLOCK_PATH = File.expand_path(__FILE__) require 'test/test_helper' do_test = true @@ -25,6 +28,13 @@ use Rack::ContentLength run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ] } EOS + SHOW_RACK_ENV = <<-EOS +use Rack::ContentLength +run proc { |env| + [ 200, { 'Content-Type' => 'text/plain' }, [ ENV['RACK_ENV'] ] ] +} + EOS + HELLO = <<-EOS class Hello def call(env) @@ -72,11 +82,148 @@ end end end + def test_working_directory_rel_path_config_file + other = Tempfile.new('unicorn.wd') + File.unlink(other.path) + Dir.mkdir(other.path) + File.open("config.ru", "wb") do |fp| + fp.syswrite <<EOF +use Rack::ContentLength +run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ Dir.pwd ] ] } +EOF + end + FileUtils.cp("config.ru", other.path + "/config.ru") + Dir.chdir(@tmpdir) + + tmp = File.open('unicorn.config', 'wb') + tmp.syswrite <<EOF +working_directory '#@tmpdir' +listen '#@addr:#@port' +EOF + pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } } + wait_workers_ready("test_stderr.#{pid}.log", 1) + results = hit(["http://#@addr:#@port/"]) + assert_equal @tmpdir, results.first + File.truncate("test_stderr.#{pid}.log", 0) + + tmp.sysseek(0) + tmp.truncate(0) + tmp.syswrite <<EOF +working_directory '#{other.path}' +listen '#@addr:#@port' +EOF + + Process.kill(:HUP, pid) + lines = [] + re = /config_file=(.+) would not be accessible in working_directory=(.+)/ + until lines.grep(re) + sleep 0.1 + lines = File.readlines("test_stderr.#{pid}.log") + end + + File.truncate("test_stderr.#{pid}.log", 0) + FileUtils.cp('unicorn.config', other.path + "/unicorn.config") + Process.kill(:HUP, pid) + wait_workers_ready("test_stderr.#{pid}.log", 1) + results = hit(["http://#@addr:#@port/"]) + assert_equal other.path, results.first + + Process.kill(:QUIT, pid) + ensure + FileUtils.rmtree(other.path) + end + + def test_working_directory + other = Tempfile.new('unicorn.wd') + File.unlink(other.path) + Dir.mkdir(other.path) + File.open("config.ru", "wb") do |fp| + fp.syswrite <<EOF +use Rack::ContentLength +run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ Dir.pwd ] ] } +EOF + end + FileUtils.cp("config.ru", other.path + "/config.ru") + tmp = Tempfile.new('unicorn.config') + tmp.syswrite <<EOF +working_directory '#@tmpdir' +listen '#@addr:#@port' +EOF + pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } } + wait_workers_ready("test_stderr.#{pid}.log", 1) + results = hit(["http://#@addr:#@port/"]) + assert_equal @tmpdir, results.first + File.truncate("test_stderr.#{pid}.log", 0) + + tmp.sysseek(0) + tmp.truncate(0) + tmp.syswrite <<EOF +working_directory '#{other.path}' +listen '#@addr:#@port' +EOF + + Process.kill(:HUP, pid) + wait_workers_ready("test_stderr.#{pid}.log", 1) + results = hit(["http://#@addr:#@port/"]) + assert_equal other.path, results.first + + Process.kill(:QUIT, pid) + ensure + FileUtils.rmtree(other.path) + end + + def test_working_directory_controls_relative_paths + other = Tempfile.new('unicorn.wd') + File.unlink(other.path) + Dir.mkdir(other.path) + File.open("config.ru", "wb") do |fp| + fp.syswrite <<EOF +use Rack::ContentLength +run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ Dir.pwd ] ] } +EOF + end + FileUtils.cp("config.ru", other.path + "/config.ru") + system('mkfifo', "#{other.path}/fifo") + tmp = Tempfile.new('unicorn.config') + tmp.syswrite <<EOF +pid "pid_file_here" +stderr_path "stderr_log_here" +stdout_path "stdout_log_here" +working_directory '#{other.path}' +listen '#@addr:#@port' +after_fork do |server, worker| + File.open("fifo", "wb").close +end +EOF + pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } } + File.open("#{other.path}/fifo", "rb").close + + assert ! File.exist?("stderr_log_here") + assert ! File.exist?("stdout_log_here") + assert ! File.exist?("pid_file_here") + + assert ! File.exist?("#@tmpdir/stderr_log_here") + assert ! File.exist?("#@tmpdir/stdout_log_here") + assert ! File.exist?("#@tmpdir/pid_file_here") + + assert File.exist?("#{other.path}/pid_file_here") + assert_equal "#{pid}\n", File.read("#{other.path}/pid_file_here") + assert File.exist?("#{other.path}/stderr_log_here") + assert File.exist?("#{other.path}/stdout_log_here") + wait_master_ready("#{other.path}/stderr_log_here") + + Process.kill(:QUIT, pid) + ensure + FileUtils.rmtree(other.path) + end + + def test_exit_signals %w(INT TERM QUIT).each do |sig| File.open("config.ru", "wb") { |fp| fp.syswrite(HI) } pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } } wait_master_ready("test_stderr.#{pid}.log") + wait_workers_ready("test_stderr.#{pid}.log", 1) status = nil assert_nothing_raised do Process.kill(sig, pid) @@ -98,6 +245,46 @@ end assert_shutdown(pid) end + def test_rack_env_unset + File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) } + pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } } + results = retry_hit(["http://#{@addr}:#{@port}/"]) + assert_equal "development", results.first + assert_shutdown(pid) + end + + def test_rack_env_cli_set + File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) } + pid = fork { + redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") } + } + results = retry_hit(["http://#{@addr}:#{@port}/"]) + assert_equal "asdf", results.first + assert_shutdown(pid) + end + + def test_rack_env_ENV_set + File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) } + pid = fork { + ENV["RACK_ENV"] = "foobar" + redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } + } + results = retry_hit(["http://#{@addr}:#{@port}/"]) + assert_equal "foobar", results.first + assert_shutdown(pid) + end + + def test_rack_env_cli_override_ENV + File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) } + pid = fork { + ENV["RACK_ENV"] = "foobar" + redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") } + } + results = retry_hit(["http://#{@addr}:#{@port}/"]) + assert_equal "asdf", results.first + assert_shutdown(pid) + end + def test_ttin_ttou File.open("config.ru", "wb") { |fp| fp.syswrite(HI) } pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } } @@ -603,6 +790,26 @@ end reexec_usr2_quit_test(new_pid, pid_file) end + def test_daemonize_redirect_fail + pid_file = "#{@tmpdir}/test.pid" + log = Tempfile.new('unicorn_test_log') + ucfg = Tempfile.new('unicorn_test_config') + ucfg.syswrite("pid #{pid_file}\"\n") + err = Tempfile.new('stderr') + out = Tempfile.new('stdout ') + + File.open("config.ru", "wb") { |fp| fp.syswrite(HI) } + pid = xfork do + $stderr.reopen(err.path, "a") + $stdout.reopen(out.path, "a") + exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}") + end + pid, status = Process.waitpid2(pid) + assert ! status.success?, "original process exited successfully" + sleep 1 # can't waitpid on a daemonized process :< + assert err.stat.size > 0 + end + def test_reexec_fd_leak unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget... warn "FD leak test only works on Linux at the moment" @@ -626,6 +833,7 @@ end end wait_master_ready(log.path) + wait_workers_ready(log.path, 1) File.truncate(log.path, 0) wait_for_file(pid_file) orig_pid = pid = File.read(pid_file).to_i @@ -641,6 +849,7 @@ end wait_for_death(pid) wait_master_ready(log.path) + wait_workers_ready(log.path, 1) File.truncate(log.path, 0) wait_for_file(pid_file) pid = File.read(pid_file).to_i @@ -660,6 +869,7 @@ end wait_for_death(pid) wait_master_ready(log.path) + wait_workers_ready(log.path, 1) File.truncate(log.path, 0) wait_for_file(pid_file) pid = File.read(pid_file).to_i @@ -671,4 +881,158 @@ end wait_for_death(pid) end + def hup_test_common(preload) + File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) } + pid_file = Tempfile.new('pid') + ucfg = Tempfile.new('unicorn_test_config') + ucfg.syswrite("listen '#@addr:#@port'\n") + ucfg.syswrite("pid '#{pid_file.path}'\n") + ucfg.syswrite("preload_app true\n") if preload + ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n") + ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n") + pid = xfork { + redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) } + } + _, status = Process.waitpid2(pid) + assert status.success? + wait_master_ready("test_stderr.#$$.log") + wait_workers_ready("test_stderr.#$$.log", 1) + uri = URI.parse("http://#@addr:#@port/") + pids = Tempfile.new('worker_pids') + hitter = fork { + bodies = Hash.new(0) + at_exit { pids.syswrite(bodies.inspect) } + trap(:TERM) { exit(0) } + loop { + rv = Net::HTTP.get(uri) + pid = rv.to_i + exit!(1) if pid <= 0 + bodies[pid] += 1 + } + } + sleep 5 # racy + daemon_pid = File.read(pid_file.path).to_i + assert daemon_pid > 0 + Process.kill(:HUP, daemon_pid) + sleep 5 # racy + assert_nothing_raised { Process.kill(:TERM, hitter) } + _, hitter_status = Process.waitpid2(hitter) + assert hitter_status.success? + pids.sysseek(0) + pids = eval(pids.read) + assert_kind_of(Hash, pids) + assert_equal 2, pids.size + pids.keys.each { |x| + assert_kind_of(Integer, x) + assert x > 0 + assert pids[x] > 0 + } + assert_nothing_raised { Process.kill(:QUIT, daemon_pid) } + wait_for_death(daemon_pid) + end + + def test_preload_app_hup + hup_test_common(true) + end + + def test_hup + hup_test_common(false) + end + + def test_default_listen_hup_holds_listener + default_listen_lock do + res, pid_path = default_listen_setup + daemon_pid = File.read(pid_path).to_i + assert_nothing_raised { Process.kill(:HUP, daemon_pid) } + wait_workers_ready("test_stderr.#$$.log", 1) + res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"]) + assert_match %r{\d+}, res2.first + assert res2.first != res.first + assert_nothing_raised { Process.kill(:QUIT, daemon_pid) } + wait_for_death(daemon_pid) + end + end + + def test_default_listen_upgrade_holds_listener + default_listen_lock do + res, pid_path = default_listen_setup + daemon_pid = File.read(pid_path).to_i + assert_nothing_raised { + Process.kill(:USR2, daemon_pid) + wait_for_file("#{pid_path}.oldbin") + wait_for_file(pid_path) + Process.kill(:QUIT, daemon_pid) + wait_for_death(daemon_pid) + } + daemon_pid = File.read(pid_path).to_i + wait_workers_ready("test_stderr.#$$.log", 1) + File.truncate("test_stderr.#$$.log", 0) + + res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"]) + assert_match %r{\d+}, res2.first + assert res2.first != res.first + + assert_nothing_raised { Process.kill(:HUP, daemon_pid) } + wait_workers_ready("test_stderr.#$$.log", 1) + File.truncate("test_stderr.#$$.log", 0) + res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"]) + assert res2.first != res3.first + + assert_nothing_raised { Process.kill(:QUIT, daemon_pid) } + wait_for_death(daemon_pid) + end + end + + def default_listen_setup + File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) } + pid_path = (tmp = Tempfile.new('pid')).path + tmp.close! + ucfg = Tempfile.new('unicorn_test_config') + ucfg.syswrite("pid '#{pid_path}'\n") + ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n") + ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n") + pid = xfork { + redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) } + } + _, status = Process.waitpid2(pid) + assert status.success? + wait_master_ready("test_stderr.#$$.log") + wait_workers_ready("test_stderr.#$$.log", 1) + File.truncate("test_stderr.#$$.log", 0) + res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"]) + assert_match %r{\d+}, res.first + [ res, pid_path ] + end + + # we need to flock() something to prevent these tests from running + def default_listen_lock(&block) + fp = File.open(FLOCK_PATH, "rb") + begin + fp.flock(File::LOCK_EX) + begin + TCPServer.new(Unicorn::Const::DEFAULT_HOST, + Unicorn::Const::DEFAULT_PORT).close + rescue Errno::EADDRINUSE, Errno::EACCES + warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}" + return false + end + + # unused_port should never take this, but we may run an environment + # where tests are being run against older unicorns... + lock_path = "#{Dir::tmpdir}/unicorn_test." \ + "#{Unicorn::Const::DEFAULT_LISTEN}.lock" + begin + lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600) + yield + rescue Errno::EEXIST + lock_path = nil + return false + ensure + File.unlink(lock_path) if lock_path + end + ensure + fp.flock(File::LOCK_UN) + end + end + end if do_test |