diff options
Diffstat (limited to 'test/unit/test_signals.rb')
-rw-r--r-- | test/unit/test_signals.rb | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/test/unit/test_signals.rb b/test/unit/test_signals.rb new file mode 100644 index 0000000..ef66ed6 --- /dev/null +++ b/test/unit/test_signals.rb @@ -0,0 +1,191 @@ +# Copyright (c) 2009 Eric Wong +# You can redistribute it and/or modify it under the same terms as Ruby. +# +# Ensure we stay sane in the face of signals being sent to us + +require 'test/test_helper' + +include Unicorn + +class Dd + def initialize(bs, count) + @count = count + @buf = ' ' * bs + end + + def each(&block) + @count.times { yield @buf } + end +end + +class SignalsTest < Test::Unit::TestCase + + def setup + @bs = 1 * 1024 * 1024 + @count = 100 + @port = unused_port + tmp = @tmp = Tempfile.new('unicorn.sock') + File.unlink(@tmp.path) + n = 0 + tmp.chmod(0) + @server_opts = { + :listeners => [ "127.0.0.1:#@port", @tmp.path ], + :after_fork => lambda { |server,worker| + trap(:HUP) { tmp.chmod(n += 1) } + }, + } + @server = nil + end + + def test_worker_dies_on_dead_master + pid = fork { + app = lambda { |env| [ 200, {'X-Pid' => "#$$" }, [] ] } + opts = @server_opts.merge(:timeout => 3) + redirect_test_io { HttpServer.new(app, opts).start.join } + } + child = sock = buf = t0 = nil + assert_nothing_raised do + wait_workers_ready("test_stderr.#{pid}.log", 1) + sock = TCPSocket.new('127.0.0.1', @port) + sock.syswrite("GET / HTTP/1.0\r\n\r\n") + buf = sock.readpartial(4096) + sock.close + buf =~ /\bX-Pid: (\d+)\b/ or raise Exception + child = $1.to_i + wait_master_ready("test_stderr.#{pid}.log") + Process.kill(:KILL, pid) + Process.waitpid(pid) + t0 = Time.now + end + assert child + assert t0 + assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } } + assert((Time.now - t0) < 60) + end + + def test_sleepy_kill + rd, wr = IO.pipe + pid = fork { + rd.close + app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] } + redirect_test_io { HttpServer.new(app, @server_opts).start.join } + } + sock = buf = nil + wr.close + assert_nothing_raised do + wait_workers_ready("test_stderr.#{pid}.log", 1) + sock = TCPSocket.new('127.0.0.1', @port) + sock.syswrite("GET / HTTP/1.0\r\n\r\n") + buf = rd.readpartial(1) + wait_master_ready("test_stderr.#{pid}.log") + Process.kill(:INT, pid) + Process.waitpid(pid) + end + assert_equal '.', buf + buf = nil + assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL, + Errno::EBADF) do + buf = sock.sysread(4096) + end + assert_nil buf + ensure + end + + def test_timeout_slow_response + pid = fork { + app = lambda { |env| sleep } + opts = @server_opts.merge(:timeout => 3) + redirect_test_io { HttpServer.new(app, opts).start.join } + } + t0 = Time.now + sock = nil + assert_nothing_raised do + wait_workers_ready("test_stderr.#{pid}.log", 1) + sock = TCPSocket.new('127.0.0.1', @port) + sock.syswrite("GET / HTTP/1.0\r\n\r\n") + end + + buf = nil + assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL, + Errno::EBADF) do + buf = sock.sysread(4096) + end + diff = Time.now - t0 + assert_nil buf + assert diff > 1.0, "diff was #{diff.inspect}" + assert diff < 60.0 + ensure + Process.kill(:QUIT, pid) rescue nil + end + + def test_response_write + app = lambda { |env| + [ 200, { 'Content-Type' => 'text/plain', 'X-Pid' => Process.pid.to_s }, + Dd.new(@bs, @count) ] + } + redirect_test_io { @server = HttpServer.new(app, @server_opts).start } + sock = nil + assert_nothing_raised do + wait_workers_ready("test_stderr.#{$$}.log", 1) + sock = TCPSocket.new('127.0.0.1', @port) + sock.syswrite("GET / HTTP/1.0\r\n\r\n") + end + buf = '' + header_len = pid = nil + assert_nothing_raised do + buf = sock.sysread(16384, buf) + pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i + header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size + end + read = buf.size + mode_before = @tmp.stat.mode + assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL, + Errno::EBADF) do + loop do + 3.times { Process.kill(:HUP, pid) } + sock.sysread(16384, buf) + read += buf.size + 3.times { Process.kill(:HUP, pid) } + end + end + + redirect_test_io { @server.stop(true) } + # can't check for == since pending signals get merged + assert mode_before < @tmp.stat.mode + assert_equal(read - header_len, @bs * @count) + assert_nothing_raised { sock.close } + end + + def test_request_read + app = lambda { |env| + [ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ] + } + redirect_test_io { @server = HttpServer.new(app, @server_opts).start } + pid = nil + + assert_nothing_raised do + wait_workers_ready("test_stderr.#{$$}.log", 1) + sock = TCPSocket.new('127.0.0.1', @port) + sock.syswrite("GET / HTTP/1.0\r\n\r\n") + pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i + sock.close + end + + sock = TCPSocket.new('127.0.0.1', @port) + sock.syswrite("PUT / HTTP/1.0\r\n") + sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n") + 1000.times { Process.kill(:HUP, pid) } + mode_before = @tmp.stat.mode + killer = fork { loop { Process.kill(:HUP, pid); sleep(0.0001) } } + buf = ' ' * @bs + @count.times { sock.syswrite(buf) } + Process.kill(:TERM, killer) + Process.waitpid2(killer) + redirect_test_io { @server.stop(true) } + # can't check for == since pending signals get merged + assert mode_before < @tmp.stat.mode + assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i + sock.close + end + +end |