about summary refs log tree commit homepage
path: root/test/test_helper.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_helper.rb')
-rw-r--r--test/test_helper.rb169
1 files changed, 164 insertions, 5 deletions
diff --git a/test/test_helper.rb b/test/test_helper.rb
index f809af3..55aa70c 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -4,9 +4,16 @@
 # Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html
 # for more information.
 
+STDIN.sync = STDOUT.sync = STDERR.sync = true # buffering makes debugging hard
+
+# Some tests watch a log file or a pid file to spring up to check state
+# Can't rely on inotify on non-Linux and logging to a pipe makes things
+# more complicated
+DEFAULT_TRIES = 1000
+DEFAULT_RES = 0.2
 
 HERE = File.dirname(__FILE__) unless defined?(HERE)
-%w(lib ext bin test).each do |dir|
+%w(lib ext).each do |dir|
   $LOAD_PATH.unshift "#{HERE}/../#{dir}"
 end
 
@@ -15,8 +22,10 @@ require 'net/http'
 require 'digest/sha1'
 require 'uri'
 require 'stringio'
+require 'pathname'
+require 'tempfile'
+require 'fileutils'
 require 'unicorn'
-require 'tmpdir'
 
 if ENV['DEBUG']
   require 'ruby-debug'
@@ -26,8 +35,9 @@ end
 def redirect_test_io
   orig_err = STDERR.dup
   orig_out = STDOUT.dup
-  STDERR.reopen("test_stderr.#{$$}.log")
-  STDOUT.reopen("test_stdout.#{$$}.log")
+  STDERR.reopen("test_stderr.#{$$}.log", "a")
+  STDOUT.reopen("test_stdout.#{$$}.log", "a")
+  STDERR.sync = STDOUT.sync = true
 
   at_exit do
     File.unlink("test_stderr.#{$$}.log") rescue nil
@@ -41,7 +51,18 @@ def redirect_test_io
     STDOUT.reopen(orig_out)
   end
 end
-    
+
+# which(1) exit codes cannot be trusted on some systems
+# We use UNIX shell utilities in some tests because we don't trust
+# ourselves to write Ruby 100% correctly :)
+def which(bin)
+  ex = ENV['PATH'].split(/:/).detect do |x|
+    x << "/#{bin}"
+    File.executable?(x)
+  end or warn "`#{bin}' not found in PATH=#{ENV['PATH']}"
+  ex
+end
+
 # Either takes a string to do a get request against, or a tuple of [URI, HTTP] where
 # HTTP is some kind of Net::HTTP request object (POST, HEAD, etc.)
 def hit(uris)
@@ -101,3 +122,141 @@ def unused_port(addr = '127.0.0.1')
   sock.close rescue nil
   port
 end
+
+def try_require(lib)
+  begin
+    require lib
+    true
+  rescue LoadError
+    false
+  end
+end
+
+# sometimes the server may not come up right away
+def retry_hit(uris = [])
+  tries = DEFAULT_TRIES
+  begin
+    hit(uris)
+  rescue Errno::ECONNREFUSED => err
+    if (tries -= 1) > 0
+      sleep DEFAULT_RES
+      retry
+    end
+    raise err
+  end
+end
+
+def assert_shutdown(pid)
+  wait_master_ready("test_stderr.#{pid}.log")
+  assert_nothing_raised { Process.kill(:QUIT, pid) }
+  status = nil
+  assert_nothing_raised { pid, status = Process.waitpid2(pid) }
+  assert status.success?, "exited successfully"
+end
+
+def wait_workers_ready(path, nr_workers)
+  tries = DEFAULT_TRIES
+  lines = []
+  while (tries -= 1) > 0
+    begin
+      lines = File.readlines(path).grep(/worker=\d+ ready/)
+      lines.size == nr_workers and return
+    rescue Errno::ENOENT
+    end
+    sleep DEFAULT_RES
+  end
+  raise "#{nr_workers} workers never became ready:" \
+        "\n\t#{lines.join("\n\t")}\n"
+end
+
+def wait_master_ready(master_log)
+  tries = DEFAULT_TRIES
+  while (tries -= 1) > 0
+    begin
+      File.readlines(master_log).grep(/master process ready/)[0] and return
+    rescue Errno::ENOENT
+    end
+    sleep DEFAULT_RES
+  end
+  raise "master process never became ready"
+end
+
+def reexec_usr2_quit_test(pid, pid_file)
+  assert File.exist?(pid_file), "pid file OK"
+  assert ! File.exist?("#{pid_file}.oldbin"), "oldbin pid file"
+  assert_nothing_raised { Process.kill(:USR2, pid) }
+  assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
+  wait_for_file("#{pid_file}.oldbin")
+  wait_for_file(pid_file)
+
+  old_pid = File.read("#{pid_file}.oldbin").to_i
+  new_pid = File.read(pid_file).to_i
+
+  # kill old master process
+  assert_not_equal pid, new_pid
+  assert_equal pid, old_pid
+  assert_nothing_raised { Process.kill(:QUIT, old_pid) }
+  assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
+  wait_for_death(old_pid)
+  assert_equal new_pid, File.read(pid_file).to_i
+  assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
+  assert_nothing_raised { Process.kill(:QUIT, new_pid) }
+end
+
+def reexec_basic_test(pid, pid_file)
+  results = retry_hit(["http://#{@addr}:#{@port}/"])
+  assert_equal String, results[0].class
+  assert_nothing_raised { Process.kill(0, pid) }
+  master_log = "#{@tmpdir}/test_stderr.#{pid}.log"
+  wait_master_ready(master_log)
+  File.truncate(master_log, 0)
+  nr = 50
+  kill_point = 2
+  assert_nothing_raised do
+    nr.times do |i|
+      hit(["http://#{@addr}:#{@port}/#{i}"])
+      i == kill_point and Process.kill(:HUP, pid)
+    end
+  end
+  wait_master_ready(master_log)
+  assert File.exist?(pid_file), "pid=#{pid_file} exists"
+  new_pid = File.read(pid_file).to_i
+  assert_not_equal pid, new_pid
+  assert_nothing_raised { Process.kill(0, new_pid) }
+  assert_nothing_raised { Process.kill(:QUIT, new_pid) }
+end
+
+def wait_for_file(path)
+  tries = DEFAULT_TRIES
+  while (tries -= 1) > 0 && ! File.exist?(path)
+    sleep DEFAULT_RES
+  end
+  assert File.exist?(path), "path=#{path} exists #{caller.inspect}"
+end
+
+def xfork(&block)
+  fork do
+    ObjectSpace.each_object(Tempfile) do |tmp|
+      ObjectSpace.undefine_finalizer(tmp)
+    end
+    yield
+  end
+end
+
+# can't waitpid on detached processes
+def wait_for_death(pid)
+  tries = DEFAULT_TRIES
+  while (tries -= 1) > 0
+    begin
+      Process.kill(0, pid)
+      begin
+        Process.waitpid(pid, Process::WNOHANG)
+      rescue Errno::ECHILD
+      end
+      sleep(DEFAULT_RES)
+    rescue Errno::ESRCH
+      return
+    end
+  end
+  raise "PID:#{pid} never died!"
+end