about summary refs log tree commit homepage
path: root/test/exec/test_exec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/exec/test_exec.rb')
-rw-r--r--test/exec/test_exec.rb364
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