port test/unit/test_ccc.rb to Perl 5
[unicorn.git] / test / unit / test_signals.rb
blob49ff3c71ce18b5df307592701a735366548d894c
1 # -*- encoding: binary -*-
2 # frozen_string_literal: false
4 # Copyright (c) 2009 Eric Wong
5 # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
6 # the GPLv2+ (GPLv3+ preferred)
8 # Ensure we stay sane in the face of signals being sent to us
10 require './test/test_helper'
12 include Unicorn
14 class Dd
15   def initialize(bs, count)
16     @count = count
17     @buf = ' ' * bs
18   end
20   def each(&block)
21     @count.times { yield @buf }
22   end
23 end
25 class SignalsTest < Test::Unit::TestCase
27   def setup
28     @bs = 1 * 1024 * 1024
29     @count = 100
30     @port = unused_port
31     @sock = Tempfile.new('unicorn.sock')
32     @tmp = Tempfile.new('unicorn.write')
33     @tmp.sync = true
34     File.unlink(@sock.path)
35     File.unlink(@tmp.path)
36     @server_opts = {
37       :listeners => [ "127.0.0.1:#@port", @sock.path ],
38       :after_fork => lambda { |server,worker|
39         trap(:HUP) { @tmp.syswrite('.') }
40       },
41     }
42     @server = nil
43   end
45   def teardown
46     reset_sig_handlers
47   end
49   def test_worker_dies_on_dead_master
50     pid = fork {
51       app = lambda { |env| [ 200, {'x-pid' => "#$$" }, [] ] }
52       opts = @server_opts.merge(:timeout => 3)
53       redirect_test_io { HttpServer.new(app, opts).start.join }
54     }
55     wait_workers_ready("test_stderr.#{pid}.log", 1)
56     sock = tcp_socket('127.0.0.1', @port)
57     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
58     buf = sock.readpartial(4096)
59     assert_nil sock.close
60     buf =~ /\bx-pid: (\d+)\b/ or raise Exception
61     child = $1.to_i
62     wait_master_ready("test_stderr.#{pid}.log")
63     wait_workers_ready("test_stderr.#{pid}.log", 1)
64     Process.kill(:KILL, pid)
65     Process.waitpid(pid)
66     File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
67     t0 = Time.now
68     assert child
69     assert t0
70     assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
71     assert((Time.now - t0) < 60)
72   end
74   def test_sleepy_kill
75     rd, wr = IO.pipe
76     pid = fork {
77       rd.close
78       app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
79       redirect_test_io { HttpServer.new(app, @server_opts).start.join }
80     }
81     wr.close
82     wait_workers_ready("test_stderr.#{pid}.log", 1)
83     sock = tcp_socket('127.0.0.1', @port)
84     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
85     buf = rd.readpartial(1)
86     wait_master_ready("test_stderr.#{pid}.log")
87     Process.kill(:INT, pid)
88     Process.waitpid(pid)
89     assert_equal '.', buf
90     buf = nil
91     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
92                   Errno::EBADF) do
93       buf = sock.sysread(4096)
94     end
95     assert_nil buf
96   end
98   def test_timeout_slow_response
99     pid = fork {
100       app = lambda { |env| sleep }
101       opts = @server_opts.merge(:timeout => 3)
102       redirect_test_io { HttpServer.new(app, opts).start.join }
103     }
104     t0 = Time.now
105     wait_workers_ready("test_stderr.#{pid}.log", 1)
106     sock = tcp_socket('127.0.0.1', @port)
107     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
109     buf = nil
110     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
111                   Errno::EBADF) do
112       buf = sock.sysread(4096)
113     end
114     diff = Time.now - t0
115     assert_nil buf
116     assert diff > 1.0, "diff was #{diff.inspect}"
117     assert diff < 60.0
118   ensure
119     Process.kill(:TERM, pid) rescue nil
120   end
122   def test_response_write
123     app = lambda { |env|
124       [ 200, { 'content-type' => 'text/plain', 'x-pid' => Process.pid.to_s },
125         Dd.new(@bs, @count) ]
126     }
127     redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
128     wait_workers_ready("test_stderr.#{$$}.log", 1)
129     sock = tcp_socket('127.0.0.1', @port)
130     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
131     buf = ''
132     header_len = pid = nil
133     buf = sock.sysread(16384, buf)
134     pid = buf[/\r\nx-pid: (\d+)\r\n/, 1].to_i
135     header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
136     assert pid > 0, "pid not positive: #{pid.inspect}"
137     read = buf.size
138     size_before = @tmp.stat.size
139     assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
140                   Errno::EBADF) do
141       loop do
142         3.times { Process.kill(:HUP, pid) }
143         sock.sysread(16384, buf)
144         read += buf.size
145         3.times { Process.kill(:HUP, pid) }
146       end
147     end
149     redirect_test_io { @server.stop(true) }
150     # can't check for == since pending signals get merged
151     assert size_before < @tmp.stat.size
152     got = read - header_len
153     expect = @bs * @count
154     assert_equal(expect, got, "expect=#{expect} got=#{got}")
155     assert_nil sock.close
156   end
158   def test_request_read
159     app = lambda { |env|
160       while env['rack.input'].read(4096)
161       end
162       [ 200, {'content-type'=>'text/plain', 'x-pid'=>Process.pid.to_s}, [] ]
163     }
164     redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
166     wait_workers_ready("test_stderr.#{$$}.log", 1)
167     sock = tcp_socket('127.0.0.1', @port)
168     sock.syswrite("GET / HTTP/1.0\r\n\r\n")
169     pid = sock.sysread(4096)[/\r\nx-pid: (\d+)\r\n/, 1].to_i
170     assert_nil sock.close
172     assert pid > 0, "pid not positive: #{pid.inspect}"
173     sock = tcp_socket('127.0.0.1', @port)
174     sock.syswrite("PUT / HTTP/1.0\r\n")
175     sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
176     1000.times { Process.kill(:HUP, pid) }
177     size_before = @tmp.stat.size
178     killer = fork { loop { Process.kill(:HUP, pid); sleep(0.01) } }
179     buf = ' ' * @bs
180     @count.times { sock.syswrite(buf) }
181     Process.kill(:KILL, killer)
182     Process.waitpid2(killer)
183     redirect_test_io { @server.stop(true) }
184     # can't check for == since pending signals get merged
185     assert size_before < @tmp.stat.size
186     assert_equal pid, sock.sysread(4096)[/\r\nx-pid: (\d+)\r\n/, 1].to_i
187     assert_nil sock.close
188   end