SIGTT{IN,OU} {in,de}crements worker_processes
[unicorn.git] / test / exec / test_exec.rb
blob014b270ab5c8e741950ff246e6ea58ec535e6852
1 # Copyright (c) 2009 Eric Wong
2 require 'test/test_helper'
4 do_test = true
5 $unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
6 redirect_test_io do
7   do_test = system($unicorn_bin, '-v')
8 end
10 unless do_test
11   warn "#{$unicorn_bin} not found in PATH=#{ENV['PATH']}, " \
12        "skipping this test"
13 end
15 unless try_require('rack')
16   warn "Unable to load Rack, skipping this test"
17   do_test = false
18 end
20 class ExecTest < Test::Unit::TestCase
21   trap(:QUIT, 'IGNORE')
23   HI = <<-EOS
24 use Rack::ContentLength
25 run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ] }
26   EOS
28   HELLO = <<-EOS
29 class Hello
30   def call(env)
31     [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ]
32   end
33 end
34   EOS
36   COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
38   HEAVY_CFG = <<-EOS
39 worker_processes 4
40 timeout 30
41 logger Logger.new('#{COMMON_TMP.path}')
42 before_fork do |server, worker|
43   server.logger.info "before_fork: worker=\#{worker.nr}"
44 end
45   EOS
47   def setup
48     @pwd = Dir.pwd
49     @tmpfile = Tempfile.new('unicorn_exec_test')
50     @tmpdir = @tmpfile.path
51     @tmpfile.close!
52     Dir.mkdir(@tmpdir)
53     Dir.chdir(@tmpdir)
54     @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
55     @port = unused_port(@addr)
56     @sockets = []
57     @start_pid = $$
58   end
60   def teardown
61     return if @start_pid != $$
62     Dir.chdir(@pwd)
63     FileUtils.rmtree(@tmpdir)
64     @sockets.each { |path| File.unlink(path) rescue nil }
65     loop do
66       Process.kill('-QUIT', 0)
67       begin
68         Process.waitpid(-1, Process::WNOHANG) or break
69       rescue Errno::ECHILD
70         break
71       end
72     end
73   end
75   def test_exit_signals
76     %w(INT TERM QUIT).each do |sig|
77       File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
78       pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
79       wait_master_ready("test_stderr.#{pid}.log")
80       status = nil
81       assert_nothing_raised do
82         Process.kill(sig, pid)
83         pid, status = Process.waitpid2(pid)
84       end
85       reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
86       assert_equal 1, reaped.size
87       assert status.exited?
88     end
89   end
91   def test_basic
92     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
93     pid = fork do
94       redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
95     end
96     results = retry_hit(["http://#{@addr}:#{@port}/"])
97     assert_equal String, results[0].class
98     assert_shutdown(pid)
99   end
101   def test_ttin_ttou
102     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
103     pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
104     log = "test_stderr.#{pid}.log"
105     wait_master_ready(log)
106     [ 2, 3].each { |i|
107       assert_nothing_raised { Process.kill(:TTIN, pid) }
108       wait_workers_ready(log, i)
109     }
110     File.truncate(log, 0)
111     reaped = nil
112     [ 2, 1, 0].each { |i|
113       assert_nothing_raised { Process.kill(:TTOU, pid) }
114       DEFAULT_TRIES.times {
115         sleep DEFAULT_RES
116         reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
117         break if reaped.size == 1
118       }
119       assert_equal 1, reaped.size
120     }
121   end
123   def test_help
124     redirect_test_io do
125       assert(system($unicorn_bin, "-h"), "help text returns true")
126     end
127     assert_equal 0, File.stat("test_stderr.#$$.log").size
128     assert_not_equal 0, File.stat("test_stdout.#$$.log").size
129     lines = File.readlines("test_stdout.#$$.log")
131     # Be considerate of the on-call technician working from their
132     # mobile phone or netbook on a slow connection :)
133     assert lines.size <= 24, "help height fits in an ANSI terminal window"
134     lines.each do |line|
135       assert line.size <= 80, "help width fits in an ANSI terminal window"
136     end
137   end
139   def test_broken_reexec_config
140     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
141     pid_file = "#{@tmpdir}/test.pid"
142     old_file = "#{pid_file}.oldbin"
143     ucfg = Tempfile.new('unicorn_test_config')
144     ucfg.syswrite("listen %(#@addr:#@port)\n")
145     ucfg.syswrite("pid %(#{pid_file})\n")
146     ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
147     pid = xfork do
148       redirect_test_io do
149         exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
150       end
151     end
152     results = retry_hit(["http://#{@addr}:#{@port}/"])
153     assert_equal String, results[0].class
155     wait_for_file(pid_file)
156     Process.waitpid(pid)
157     Process.kill(:USR2, File.read(pid_file).to_i)
158     wait_for_file(old_file)
159     wait_for_file(pid_file)
160     old_pid = File.read(old_file).to_i
161     Process.kill(:QUIT, old_pid)
162     wait_for_death(old_pid)
164     ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
165     current_pid = File.read(pid_file).to_i
166     Process.kill(:USR2, current_pid)
168     # wait for pid_file to restore itself
169     tries = DEFAULT_TRIES
170     begin
171       while current_pid != File.read(pid_file).to_i
172         sleep(DEFAULT_RES) and (tries -= 1) > 0
173       end
174     rescue Errno::ENOENT
175       (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
176     end
177     assert_equal current_pid, File.read(pid_file).to_i
179     tries = DEFAULT_TRIES
180     while File.exist?(old_file)
181       (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
182     end
183     assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
184     port2 = unused_port(@addr)
186     # fix the bug
187     ucfg.sysseek(0)
188     ucfg.truncate(0)
189     ucfg.syswrite("listen %(#@addr:#@port)\n")
190     ucfg.syswrite("listen %(#@addr:#{port2})\n")
191     ucfg.syswrite("pid %(#{pid_file})\n")
192     assert_nothing_raised { Process.kill(:USR2, current_pid) }
194     wait_for_file(old_file)
195     wait_for_file(pid_file)
196     new_pid = File.read(pid_file).to_i
197     assert_not_equal current_pid, new_pid
198     assert_equal current_pid, File.read(old_file).to_i
199     results = retry_hit(["http://#{@addr}:#{@port}/",
200                          "http://#{@addr}:#{port2}/"])
201     assert_equal String, results[0].class
202     assert_equal String, results[1].class
204     assert_nothing_raised do
205       Process.kill(:QUIT, current_pid)
206       Process.kill(:QUIT, new_pid)
207     end
208   end
210   def test_broken_reexec_ru
211     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
212     pid_file = "#{@tmpdir}/test.pid"
213     old_file = "#{pid_file}.oldbin"
214     ucfg = Tempfile.new('unicorn_test_config')
215     ucfg.syswrite("pid %(#{pid_file})\n")
216     ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
217     pid = xfork do
218       redirect_test_io do
219         exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
220       end
221     end
222     results = retry_hit(["http://#{@addr}:#{@port}/"])
223     assert_equal String, results[0].class
225     wait_for_file(pid_file)
226     Process.waitpid(pid)
227     Process.kill(:USR2, File.read(pid_file).to_i)
228     wait_for_file(old_file)
229     wait_for_file(pid_file)
230     old_pid = File.read(old_file).to_i
231     Process.kill(:QUIT, old_pid)
232     wait_for_death(old_pid)
234     File.unlink("config.ru") # break reloading
235     current_pid = File.read(pid_file).to_i
236     Process.kill(:USR2, current_pid)
238     # wait for pid_file to restore itself
239     tries = DEFAULT_TRIES
240     begin
241       while current_pid != File.read(pid_file).to_i
242         sleep(DEFAULT_RES) and (tries -= 1) > 0
243       end
244     rescue Errno::ENOENT
245       (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
246     end
248     tries = DEFAULT_TRIES
249     while File.exist?(old_file)
250       (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
251     end
252     assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
253     assert_equal current_pid, File.read(pid_file).to_i
255     # fix the bug
256     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
257     assert_nothing_raised { Process.kill(:USR2, current_pid) }
258     wait_for_file(old_file)
259     wait_for_file(pid_file)
260     new_pid = File.read(pid_file).to_i
261     assert_not_equal current_pid, new_pid
262     assert_equal current_pid, File.read(old_file).to_i
263     results = retry_hit(["http://#{@addr}:#{@port}/"])
264     assert_equal String, results[0].class
266     assert_nothing_raised do
267       Process.kill(:QUIT, current_pid)
268       Process.kill(:QUIT, new_pid)
269     end
270   end
272   def test_unicorn_config_listener_swap
273     port_cli = unused_port
274     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
275     ucfg = Tempfile.new('unicorn_test_config')
276     ucfg.syswrite("listen '#@addr:#@port'\n")
277     pid = xfork do
278       redirect_test_io do
279         exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
280       end
281     end
282     results = retry_hit(["http://#@addr:#{port_cli}/"])
283     assert_equal String, results[0].class
284     results = retry_hit(["http://#@addr:#@port/"])
285     assert_equal String, results[0].class
287     port2 = unused_port(@addr)
288     ucfg.sysseek(0)
289     ucfg.truncate(0)
290     ucfg.syswrite("listen '#@addr:#{port2}'\n")
291     Process.kill(:HUP, pid)
293     results = retry_hit(["http://#@addr:#{port2}/"])
294     assert_equal String, results[0].class
295     results = retry_hit(["http://#@addr:#{port_cli}/"])
296     assert_equal String, results[0].class
297     assert_nothing_raised do
298       reuse = TCPServer.new(@addr, @port)
299       reuse.close
300     end
301     assert_shutdown(pid)
302   end
304   def test_unicorn_config_listen_with_options
305     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
306     ucfg = Tempfile.new('unicorn_test_config')
307     ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
308     ucfg.syswrite("                            :rcvbuf => 4096,\n")
309     ucfg.syswrite("                            :sndbuf => 4096\n")
310     pid = xfork do
311       redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
312     end
313     results = retry_hit(["http://#{@addr}:#{@port}/"])
314     assert_equal String, results[0].class
315     assert_shutdown(pid)
316   end
318   def test_unicorn_config_per_worker_listen
319     port2 = unused_port
320     pid_spit = 'use Rack::ContentLength;' \
321       'run proc { |e| [ 200, {"Content-Type"=>"text/plain"}, ["#$$\\n"] ] }'
322     File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
323     tmp = Tempfile.new('test.socket')
324     File.unlink(tmp.path)
325     ucfg = Tempfile.new('unicorn_test_config')
326     ucfg.syswrite("listen '#@addr:#@port'\n")
327     ucfg.syswrite("before_fork { |s,w|\n")
328     ucfg.syswrite("  s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
329     ucfg.syswrite("  s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
330     ucfg.syswrite("\n}\n")
331     pid = xfork do
332       redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
333     end
334     results = retry_hit(["http://#{@addr}:#{@port}/"])
335     assert_equal String, results[0].class
336     worker_pid = results[0].to_i
337     assert_not_equal pid, worker_pid
338     s = UNIXSocket.new(tmp.path)
339     s.syswrite("GET / HTTP/1.0\r\n\r\n")
340     results = ''
341     loop { results << s.sysread(4096) } rescue nil
342     assert_nothing_raised { s.close }
343     assert_equal worker_pid, results.split(/\r\n/).last.to_i
344     results = hit(["http://#@addr:#{port2}/"])
345     assert_equal String, results[0].class
346     assert_equal worker_pid, results[0].to_i
347     assert_shutdown(pid)
348   end
350   def test_unicorn_config_listen_augments_cli
351     port2 = unused_port(@addr)
352     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
353     ucfg = Tempfile.new('unicorn_test_config')
354     ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
355     pid = xfork do
356       redirect_test_io do
357         exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
358       end
359     end
360     uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
361     results = retry_hit(uris)
362     assert_equal results.size, uris.size
363     assert_equal String, results[0].class
364     assert_equal String, results[1].class
365     assert_shutdown(pid)
366   end
368   def test_weird_config_settings
369     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
370     ucfg = Tempfile.new('unicorn_test_config')
371     ucfg.syswrite(HEAVY_CFG)
372     pid = xfork do
373       redirect_test_io do
374         exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
375       end
376     end
378     results = retry_hit(["http://#{@addr}:#{@port}/"])
379     assert_equal String, results[0].class
380     wait_master_ready(COMMON_TMP.path)
381     wait_workers_ready(COMMON_TMP.path, 4)
382     bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
383     assert_equal 4, bf.size
384     rotate = Tempfile.new('unicorn_rotate')
385     assert_nothing_raised do
386       File.rename(COMMON_TMP.path, rotate.path)
387       Process.kill(:USR1, pid)
388     end
389     wait_for_file(COMMON_TMP.path)
390     assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
391     # USR1 should've been passed to all workers
392     tries = DEFAULT_TRIES
393     log = File.readlines(rotate.path)
394     while (tries -= 1) > 0 &&
395           log.grep(/reopening logs\.\.\./).size < 5
396       sleep DEFAULT_RES
397       log = File.readlines(rotate.path)
398     end
399     assert_equal 5, log.grep(/reopening logs\.\.\./).size
400     assert_equal 0, log.grep(/done reopening logs/).size
402     tries = DEFAULT_TRIES
403     log = File.readlines(COMMON_TMP.path)
404     while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
405       sleep DEFAULT_RES
406       log = File.readlines(COMMON_TMP.path)
407     end
408     assert_equal 5, log.grep(/done reopening logs/).size
409     assert_equal 0, log.grep(/reopening logs\.\.\./).size
410     assert_nothing_raised { Process.kill(:QUIT, pid) }
411     status = nil
412     assert_nothing_raised { pid, status = Process.waitpid2(pid) }
413     assert status.success?, "exited successfully"
414   end
416   def test_read_embedded_cli_switches
417     File.open("config.ru", "wb") do |fp|
418       fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
419       fp.syswrite(HI)
420     end
421     pid = fork { redirect_test_io { exec($unicorn_bin) } }
422     results = retry_hit(["http://#{@addr}:#{@port}/"])
423     assert_equal String, results[0].class
424     assert_shutdown(pid)
425   end
427   def test_config_ru_alt_path
428     config_path = "#{@tmpdir}/foo.ru"
429     File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
430     pid = fork do
431       redirect_test_io do
432         Dir.chdir("/")
433         exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
434       end
435     end
436     results = retry_hit(["http://#{@addr}:#{@port}/"])
437     assert_equal String, results[0].class
438     assert_shutdown(pid)
439   end
441   def test_load_module
442     libdir = "#{@tmpdir}/lib"
443     FileUtils.mkpath([ libdir ])
444     config_path = "#{libdir}/hello.rb"
445     File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
446     pid = fork do
447       redirect_test_io do
448         Dir.chdir("/")
449         exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
450       end
451     end
452     results = retry_hit(["http://#{@addr}:#{@port}/"])
453     assert_equal String, results[0].class
454     assert_shutdown(pid)
455   end
457   def test_reexec
458     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
459     pid_file = "#{@tmpdir}/test.pid"
460     pid = fork do
461       redirect_test_io do
462         exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
463       end
464     end
465     reexec_basic_test(pid, pid_file)
466   end
468   def test_reexec_alt_config
469     config_file = "#{@tmpdir}/foo.ru"
470     File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
471     pid_file = "#{@tmpdir}/test.pid"
472     pid = fork do
473       redirect_test_io do
474         exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
475       end
476     end
477     reexec_basic_test(pid, pid_file)
478   end
480   def test_socket_unlinked_restore
481     results = nil
482     sock = Tempfile.new('unicorn_test_sock')
483     sock_path = sock.path
484     @sockets << sock_path
485     sock.close!
486     ucfg = Tempfile.new('unicorn_test_config')
487     ucfg.syswrite("listen \"#{sock_path}\"\n")
489     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
490     pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
491     wait_for_file(sock_path)
492     assert File.socket?(sock_path)
493     assert_nothing_raised do
494       sock = UNIXSocket.new(sock_path)
495       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
496       results = sock.sysread(4096)
497     end
498     assert_equal String, results.class
499     assert_nothing_raised do
500       File.unlink(sock_path)
501       Process.kill(:HUP, pid)
502     end
503     wait_for_file(sock_path)
504     assert File.socket?(sock_path)
505     assert_nothing_raised do
506       sock = UNIXSocket.new(sock_path)
507       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
508       results = sock.sysread(4096)
509     end
510     assert_equal String, results.class
511   end
513   def test_unicorn_config_file
514     pid_file = "#{@tmpdir}/test.pid"
515     sock = Tempfile.new('unicorn_test_sock')
516     sock_path = sock.path
517     sock.close!
518     @sockets << sock_path
520     log = Tempfile.new('unicorn_test_log')
521     ucfg = Tempfile.new('unicorn_test_config')
522     ucfg.syswrite("listen \"#{sock_path}\"\n")
523     ucfg.syswrite("pid \"#{pid_file}\"\n")
524     ucfg.syswrite("logger Logger.new('#{log.path}')\n")
525     ucfg.close
527     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
528     pid = xfork do
529       redirect_test_io do
530         exec($unicorn_bin, "-l#{@addr}:#{@port}",
531              "-P#{pid_file}", "-c#{ucfg.path}")
532       end
533     end
534     results = retry_hit(["http://#{@addr}:#{@port}/"])
535     assert_equal String, results[0].class
536     wait_master_ready(log.path)
537     assert File.exist?(pid_file), "pid_file created"
538     assert_equal pid, File.read(pid_file).to_i
539     assert File.socket?(sock_path), "socket created"
540     assert_nothing_raised do
541       sock = UNIXSocket.new(sock_path)
542       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
543       results = sock.sysread(4096)
544     end
545     assert_equal String, results.class
547     # try reloading the config
548     sock = Tempfile.new('new_test_sock')
549     new_sock_path = sock.path
550     @sockets << new_sock_path
551     sock.close!
552     new_log = Tempfile.new('unicorn_test_log')
553     new_log.sync = true
554     assert_equal 0, new_log.size
556     assert_nothing_raised do
557       ucfg = File.open(ucfg.path, "wb")
558       ucfg.syswrite("listen \"#{sock_path}\"\n")
559       ucfg.syswrite("listen \"#{new_sock_path}\"\n")
560       ucfg.syswrite("pid \"#{pid_file}\"\n")
561       ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
562       ucfg.close
563       Process.kill(:HUP, pid)
564     end
566     wait_for_file(new_sock_path)
567     assert File.socket?(new_sock_path), "socket exists"
568     @sockets.each do |path|
569       assert_nothing_raised do
570         sock = UNIXSocket.new(path)
571         sock.syswrite("GET / HTTP/1.0\r\n\r\n")
572         results = sock.sysread(4096)
573       end
574       assert_equal String, results.class
575     end
577     assert_not_equal 0, new_log.size
578     reexec_usr2_quit_test(pid, pid_file)
579   end
581   def test_daemonize_reexec
582     pid_file = "#{@tmpdir}/test.pid"
583     log = Tempfile.new('unicorn_test_log')
584     ucfg = Tempfile.new('unicorn_test_config')
585     ucfg.syswrite("pid \"#{pid_file}\"\n")
586     ucfg.syswrite("logger Logger.new('#{log.path}')\n")
587     ucfg.close
589     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
590     pid = xfork do
591       redirect_test_io do
592         exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
593       end
594     end
595     results = retry_hit(["http://#{@addr}:#{@port}/"])
596     assert_equal String, results[0].class
597     wait_for_file(pid_file)
598     new_pid = File.read(pid_file).to_i
599     assert_not_equal pid, new_pid
600     pid, status = Process.waitpid2(pid)
601     assert status.success?, "original process exited successfully"
602     assert_nothing_raised { Process.kill(0, new_pid) }
603     reexec_usr2_quit_test(new_pid, pid_file)
604   end
606   def test_reexec_fd_leak
607     unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
608       warn "FD leak test only works on Linux at the moment"
609       return
610     end
611     pid_file = "#{@tmpdir}/test.pid"
612     log = Tempfile.new('unicorn_test_log')
613     log.sync = true
614     ucfg = Tempfile.new('unicorn_test_config')
615     ucfg.syswrite("pid \"#{pid_file}\"\n")
616     ucfg.syswrite("logger Logger.new('#{log.path}')\n")
617     ucfg.syswrite("stderr_path '#{log.path}'\n")
618     ucfg.syswrite("stdout_path '#{log.path}'\n")
619     ucfg.close
621     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
622     pid = xfork do
623       redirect_test_io do
624         exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
625       end
626     end
628     wait_master_ready(log.path)
629     File.truncate(log.path, 0)
630     wait_for_file(pid_file)
631     orig_pid = pid = File.read(pid_file).to_i
632     orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
633     assert $?.success?
634     expect_size = orig_fds.size
636     assert_nothing_raised do
637       Process.kill(:USR2, pid)
638       wait_for_file("#{pid_file}.oldbin")
639       Process.kill(:QUIT, pid)
640     end
641     wait_for_death(pid)
643     wait_master_ready(log.path)
644     File.truncate(log.path, 0)
645     wait_for_file(pid_file)
646     pid = File.read(pid_file).to_i
647     assert_not_equal orig_pid, pid
648     curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
649     assert $?.success?
651     # we could've inherited descriptors the first time around
652     assert expect_size >= curr_fds.size, curr_fds.inspect
653     expect_size = curr_fds.size
655     assert_nothing_raised do
656       Process.kill(:USR2, pid)
657       wait_for_file("#{pid_file}.oldbin")
658       Process.kill(:QUIT, pid)
659     end
660     wait_for_death(pid)
662     wait_master_ready(log.path)
663     File.truncate(log.path, 0)
664     wait_for_file(pid_file)
665     pid = File.read(pid_file).to_i
666     curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
667     assert $?.success?
668     assert_equal expect_size, curr_fds.size, curr_fds.inspect
670     Process.kill(:QUIT, pid)
671     wait_for_death(pid)
672   end
674 end if do_test