test_exec: fix potential races in fd leak test
[unicorn.git] / test / exec / test_exec.rb
blob67a189c3737756ba959b50580fb3489e1f860731
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_nr|
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_basic
76     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
77     pid = fork do
78       redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
79     end
80     results = retry_hit(["http://#{@addr}:#{@port}/"])
81     assert_equal String, results[0].class
82     assert_shutdown(pid)
83   end
85   def test_help
86     redirect_test_io do
87       assert(system($unicorn_bin, "-h"), "help text returns true")
88     end
89     assert_equal 0, File.stat("test_stderr.#$$.log").size
90     assert_not_equal 0, File.stat("test_stdout.#$$.log").size
91     lines = File.readlines("test_stdout.#$$.log")
93     # Be considerate of the on-call technician working from their
94     # mobile phone or netbook on a slow connection :)
95     assert lines.size <= 24, "help height fits in an ANSI terminal window"
96     lines.each do |line|
97       assert line.size <= 80, "help width fits in an ANSI terminal window"
98     end
99   end
101   def test_broken_reexec_config
102     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
103     pid_file = "#{@tmpdir}/test.pid"
104     old_file = "#{pid_file}.oldbin"
105     ucfg = Tempfile.new('unicorn_test_config')
106     ucfg.syswrite("listen %(#@addr:#@port)\n")
107     ucfg.syswrite("pid %(#{pid_file})\n")
108     ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
109     pid = xfork do
110       redirect_test_io do
111         exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
112       end
113     end
114     results = retry_hit(["http://#{@addr}:#{@port}/"])
115     assert_equal String, results[0].class
117     wait_for_file(pid_file)
118     Process.waitpid(pid)
119     Process.kill(:USR2, File.read(pid_file).to_i)
120     wait_for_file(old_file)
121     wait_for_file(pid_file)
122     old_pid = File.read(old_file).to_i
123     Process.kill(:QUIT, old_pid)
124     wait_for_death(old_pid)
126     ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
127     current_pid = File.read(pid_file).to_i
128     Process.kill(:USR2, current_pid)
130     # wait for pid_file to restore itself
131     tries = DEFAULT_TRIES
132     begin
133       while current_pid != File.read(pid_file).to_i
134         sleep(DEFAULT_RES) and (tries -= 1) > 0
135       end
136     rescue Errno::ENOENT
137       (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
138     end
139     assert_equal current_pid, File.read(pid_file).to_i
141     tries = DEFAULT_TRIES
142     while File.exist?(old_file)
143       (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
144     end
145     assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
146     port2 = unused_port(@addr)
148     # fix the bug
149     ucfg.sysseek(0)
150     ucfg.truncate(0)
151     ucfg.syswrite("listen %(#@addr:#@port)\n")
152     ucfg.syswrite("listen %(#@addr:#{port2})\n")
153     ucfg.syswrite("pid %(#{pid_file})\n")
154     assert_nothing_raised { Process.kill(:USR2, current_pid) }
156     wait_for_file(old_file)
157     wait_for_file(pid_file)
158     new_pid = File.read(pid_file).to_i
159     assert_not_equal current_pid, new_pid
160     assert_equal current_pid, File.read(old_file).to_i
161     results = retry_hit(["http://#{@addr}:#{@port}/",
162                          "http://#{@addr}:#{port2}/"])
163     assert_equal String, results[0].class
164     assert_equal String, results[1].class
166     assert_nothing_raised do
167       Process.kill(:QUIT, current_pid)
168       Process.kill(:QUIT, new_pid)
169     end
170   end
172   def test_broken_reexec_ru
173     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
174     pid_file = "#{@tmpdir}/test.pid"
175     old_file = "#{pid_file}.oldbin"
176     ucfg = Tempfile.new('unicorn_test_config')
177     ucfg.syswrite("pid %(#{pid_file})\n")
178     ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
179     pid = xfork do
180       redirect_test_io do
181         exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
182       end
183     end
184     results = retry_hit(["http://#{@addr}:#{@port}/"])
185     assert_equal String, results[0].class
187     wait_for_file(pid_file)
188     Process.waitpid(pid)
189     Process.kill(:USR2, File.read(pid_file).to_i)
190     wait_for_file(old_file)
191     wait_for_file(pid_file)
192     old_pid = File.read(old_file).to_i
193     Process.kill(:QUIT, old_pid)
194     wait_for_death(old_pid)
196     File.unlink("config.ru") # break reloading
197     current_pid = File.read(pid_file).to_i
198     Process.kill(:USR2, current_pid)
200     # wait for pid_file to restore itself
201     tries = DEFAULT_TRIES
202     begin
203       while current_pid != File.read(pid_file).to_i
204         sleep(DEFAULT_RES) and (tries -= 1) > 0
205       end
206     rescue Errno::ENOENT
207       (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
208     end
210     tries = DEFAULT_TRIES
211     while File.exist?(old_file)
212       (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
213     end
214     assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
215     assert_equal current_pid, File.read(pid_file).to_i
217     # fix the bug
218     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
219     assert_nothing_raised { Process.kill(:USR2, current_pid) }
220     wait_for_file(old_file)
221     wait_for_file(pid_file)
222     new_pid = File.read(pid_file).to_i
223     assert_not_equal current_pid, new_pid
224     assert_equal current_pid, File.read(old_file).to_i
225     results = retry_hit(["http://#{@addr}:#{@port}/"])
226     assert_equal String, results[0].class
228     assert_nothing_raised do
229       Process.kill(:QUIT, current_pid)
230       Process.kill(:QUIT, new_pid)
231     end
232   end
234   def test_unicorn_config_listener_swap
235     port_cli = unused_port
236     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
237     ucfg = Tempfile.new('unicorn_test_config')
238     ucfg.syswrite("listen '#@addr:#@port'\n")
239     pid = xfork do
240       redirect_test_io do
241         exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
242       end
243     end
244     results = retry_hit(["http://#@addr:#{port_cli}/"])
245     assert_equal String, results[0].class
246     results = retry_hit(["http://#@addr:#@port/"])
247     assert_equal String, results[0].class
249     port2 = unused_port(@addr)
250     ucfg.sysseek(0)
251     ucfg.truncate(0)
252     ucfg.syswrite("listen '#@addr:#{port2}'\n")
253     Process.kill(:HUP, pid)
255     results = retry_hit(["http://#@addr:#{port2}/"])
256     assert_equal String, results[0].class
257     results = retry_hit(["http://#@addr:#{port_cli}/"])
258     assert_equal String, results[0].class
259     assert_nothing_raised do
260       reuse = TCPServer.new(@addr, @port)
261       reuse.close
262     end
263     assert_shutdown(pid)
264   end
266   def test_unicorn_config_listen_with_options
267     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
268     ucfg = Tempfile.new('unicorn_test_config')
269     ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
270     ucfg.syswrite("                            :rcvbuf => 4096,\n")
271     ucfg.syswrite("                            :sndbuf => 4096\n")
272     pid = xfork do
273       redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
274     end
275     results = retry_hit(["http://#{@addr}:#{@port}/"])
276     assert_equal String, results[0].class
277     assert_shutdown(pid)
278   end
280   def test_unicorn_config_per_worker_listen
281     port2 = unused_port
282     pid_spit = 'use Rack::ContentLength;' \
283       'run proc { |e| [ 200, {"Content-Type"=>"text/plain"}, ["#$$\\n"] ] }'
284     File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
285     tmp = Tempfile.new('test.socket')
286     File.unlink(tmp.path)
287     ucfg = Tempfile.new('unicorn_test_config')
288     ucfg.syswrite("listen '#@addr:#@port'\n")
289     ucfg.syswrite("before_fork { |s,nr|\n")
290     ucfg.syswrite("  s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
291     ucfg.syswrite("  s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
292     ucfg.syswrite("\n}\n")
293     pid = xfork do
294       redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
295     end
296     results = retry_hit(["http://#{@addr}:#{@port}/"])
297     assert_equal String, results[0].class
298     worker_pid = results[0].to_i
299     assert_not_equal pid, worker_pid
300     s = UNIXSocket.new(tmp.path)
301     s.syswrite("GET / HTTP/1.0\r\n\r\n")
302     results = ''
303     loop { results << s.sysread(4096) } rescue nil
304     assert_nothing_raised { s.close }
305     assert_equal worker_pid, results.split(/\r\n/).last.to_i
306     results = hit(["http://#@addr:#{port2}/"])
307     assert_equal String, results[0].class
308     assert_equal worker_pid, results[0].to_i
309     assert_shutdown(pid)
310   end
312   def test_unicorn_config_listen_augments_cli
313     port2 = unused_port(@addr)
314     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
315     ucfg = Tempfile.new('unicorn_test_config')
316     ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
317     pid = xfork do
318       redirect_test_io do
319         exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
320       end
321     end
322     uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
323     results = retry_hit(uris)
324     assert_equal results.size, uris.size
325     assert_equal String, results[0].class
326     assert_equal String, results[1].class
327     assert_shutdown(pid)
328   end
330   def test_weird_config_settings
331     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
332     ucfg = Tempfile.new('unicorn_test_config')
333     ucfg.syswrite(HEAVY_CFG)
334     pid = xfork do
335       redirect_test_io do
336         exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
337       end
338     end
340     results = retry_hit(["http://#{@addr}:#{@port}/"])
341     assert_equal String, results[0].class
342     wait_master_ready(COMMON_TMP.path)
343     wait_workers_ready(COMMON_TMP.path, 4)
344     bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
345     assert_equal 4, bf.size
346     rotate = Tempfile.new('unicorn_rotate')
347     assert_nothing_raised do
348       File.rename(COMMON_TMP.path, rotate.path)
349       Process.kill(:USR1, pid)
350     end
351     wait_for_file(COMMON_TMP.path)
352     assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
353     # USR1 should've been passed to all workers
354     tries = DEFAULT_TRIES
355     log = File.readlines(rotate.path)
356     while (tries -= 1) > 0 &&
357           log.grep(/rotating logs\.\.\./).size < 5
358       sleep DEFAULT_RES
359       log = File.readlines(rotate.path)
360     end
361     assert_equal 5, log.grep(/rotating logs\.\.\./).size
362     assert_equal 0, log.grep(/done rotating logs/).size
364     tries = DEFAULT_TRIES
365     log = File.readlines(COMMON_TMP.path)
366     while (tries -= 1) > 0 && log.grep(/done rotating logs/).size < 5
367       sleep DEFAULT_RES
368       log = File.readlines(COMMON_TMP.path)
369     end
370     assert_equal 5, log.grep(/done rotating logs/).size
371     assert_equal 0, log.grep(/rotating logs\.\.\./).size
372     assert_nothing_raised { Process.kill(:QUIT, pid) }
373     status = nil
374     assert_nothing_raised { pid, status = Process.waitpid2(pid) }
375     assert status.success?, "exited successfully"
376   end
378   def test_read_embedded_cli_switches
379     File.open("config.ru", "wb") do |fp|
380       fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
381       fp.syswrite(HI)
382     end
383     pid = fork { redirect_test_io { exec($unicorn_bin) } }
384     results = retry_hit(["http://#{@addr}:#{@port}/"])
385     assert_equal String, results[0].class
386     assert_shutdown(pid)
387   end
389   def test_config_ru_alt_path
390     config_path = "#{@tmpdir}/foo.ru"
391     File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
392     pid = fork do
393       redirect_test_io do
394         Dir.chdir("/")
395         exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
396       end
397     end
398     results = retry_hit(["http://#{@addr}:#{@port}/"])
399     assert_equal String, results[0].class
400     assert_shutdown(pid)
401   end
403   def test_load_module
404     libdir = "#{@tmpdir}/lib"
405     FileUtils.mkpath([ libdir ])
406     config_path = "#{libdir}/hello.rb"
407     File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
408     pid = fork do
409       redirect_test_io do
410         Dir.chdir("/")
411         exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
412       end
413     end
414     results = retry_hit(["http://#{@addr}:#{@port}/"])
415     assert_equal String, results[0].class
416     assert_shutdown(pid)
417   end
419   def test_reexec
420     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
421     pid_file = "#{@tmpdir}/test.pid"
422     pid = fork do
423       redirect_test_io do
424         exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
425       end
426     end
427     reexec_basic_test(pid, pid_file)
428   end
430   def test_reexec_alt_config
431     config_file = "#{@tmpdir}/foo.ru"
432     File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
433     pid_file = "#{@tmpdir}/test.pid"
434     pid = fork do
435       redirect_test_io do
436         exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
437       end
438     end
439     reexec_basic_test(pid, pid_file)
440   end
442   def test_socket_unlinked_restore
443     results = nil
444     sock = Tempfile.new('unicorn_test_sock')
445     sock_path = sock.path
446     sock.close!
447     ucfg = Tempfile.new('unicorn_test_config')
448     ucfg.syswrite("listen \"#{sock_path}\"\n")
450     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
451     pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
452     wait_for_file(sock_path)
453     assert File.socket?(sock_path)
454     assert_nothing_raised do
455       sock = UNIXSocket.new(sock_path)
456       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
457       results = sock.sysread(4096)
458     end
459     assert_equal String, results.class
460     assert_nothing_raised do
461       File.unlink(sock_path)
462       Process.kill(:HUP, pid)
463     end
464     wait_for_file(sock_path)
465     assert File.socket?(sock_path)
466     assert_nothing_raised do
467       sock = UNIXSocket.new(sock_path)
468       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
469       results = sock.sysread(4096)
470     end
471     assert_equal String, results.class
472   end
474   def test_unicorn_config_file
475     pid_file = "#{@tmpdir}/test.pid"
476     sock = Tempfile.new('unicorn_test_sock')
477     sock_path = sock.path
478     sock.close!
479     @sockets << sock_path
481     log = Tempfile.new('unicorn_test_log')
482     ucfg = Tempfile.new('unicorn_test_config')
483     ucfg.syswrite("listen \"#{sock_path}\"\n")
484     ucfg.syswrite("pid \"#{pid_file}\"\n")
485     ucfg.syswrite("logger Logger.new('#{log.path}')\n")
486     ucfg.close
488     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
489     pid = xfork do
490       redirect_test_io do
491         exec($unicorn_bin, "-l#{@addr}:#{@port}",
492              "-P#{pid_file}", "-c#{ucfg.path}")
493       end
494     end
495     results = retry_hit(["http://#{@addr}:#{@port}/"])
496     assert_equal String, results[0].class
497     wait_master_ready(log.path)
498     assert File.exist?(pid_file), "pid_file created"
499     assert_equal pid, File.read(pid_file).to_i
500     assert File.socket?(sock_path), "socket created"
501     assert_nothing_raised do
502       sock = UNIXSocket.new(sock_path)
503       sock.syswrite("GET / HTTP/1.0\r\n\r\n")
504       results = sock.sysread(4096)
505     end
506     assert_equal String, results.class
508     # try reloading the config
509     sock = Tempfile.new('new_test_sock')
510     new_sock_path = sock.path
511     @sockets << new_sock_path
512     sock.close!
513     new_log = Tempfile.new('unicorn_test_log')
514     new_log.sync = true
515     assert_equal 0, new_log.size
517     assert_nothing_raised do
518       ucfg = File.open(ucfg.path, "wb")
519       ucfg.syswrite("listen \"#{sock_path}\"\n")
520       ucfg.syswrite("listen \"#{new_sock_path}\"\n")
521       ucfg.syswrite("pid \"#{pid_file}\"\n")
522       ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
523       ucfg.close
524       Process.kill(:HUP, pid)
525     end
527     wait_for_file(new_sock_path)
528     assert File.socket?(new_sock_path), "socket exists"
529     @sockets.each do |path|
530       assert_nothing_raised do
531         sock = UNIXSocket.new(path)
532         sock.syswrite("GET / HTTP/1.0\r\n\r\n")
533         results = sock.sysread(4096)
534       end
535       assert_equal String, results.class
536     end
538     assert_not_equal 0, new_log.size
539     reexec_usr2_quit_test(pid, pid_file)
540   end
542   def test_daemonize_reexec
543     pid_file = "#{@tmpdir}/test.pid"
544     log = Tempfile.new('unicorn_test_log')
545     ucfg = Tempfile.new('unicorn_test_config')
546     ucfg.syswrite("pid \"#{pid_file}\"\n")
547     ucfg.syswrite("logger Logger.new('#{log.path}')\n")
548     ucfg.close
550     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
551     pid = xfork do
552       redirect_test_io do
553         exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
554       end
555     end
556     results = retry_hit(["http://#{@addr}:#{@port}/"])
557     assert_equal String, results[0].class
558     wait_for_file(pid_file)
559     new_pid = File.read(pid_file).to_i
560     assert_not_equal pid, new_pid
561     pid, status = Process.waitpid2(pid)
562     assert status.success?, "original process exited successfully"
563     assert_nothing_raised { Process.kill(0, new_pid) }
564     reexec_usr2_quit_test(new_pid, pid_file)
565   end
567   def test_reexec_fd_leak
568     unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
569       warn "FD leak test only works on Linux at the moment"
570       return
571     end
572     pid_file = "#{@tmpdir}/test.pid"
573     log = Tempfile.new('unicorn_test_log')
574     log.sync = true
575     ucfg = Tempfile.new('unicorn_test_config')
576     ucfg.syswrite("pid \"#{pid_file}\"\n")
577     ucfg.syswrite("logger Logger.new('#{log.path}')\n")
578     ucfg.syswrite("stderr_path '#{log.path}'\n")
579     ucfg.syswrite("stdout_path '#{log.path}'\n")
580     ucfg.close
582     File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
583     pid = xfork do
584       redirect_test_io do
585         exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
586       end
587     end
589     wait_master_ready(log.path)
590     File.truncate(log.path, 0)
591     wait_for_file(pid_file)
592     orig_pid = pid = File.read(pid_file).to_i
593     orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
594     assert $?.success?
595     expect_size = orig_fds.size
597     assert_nothing_raised do
598       Process.kill(:USR2, pid)
599       wait_for_file("#{pid_file}.oldbin")
600       Process.kill(:QUIT, pid)
601     end
602     wait_for_death(pid)
604     wait_master_ready(log.path)
605     File.truncate(log.path, 0)
606     wait_for_file(pid_file)
607     pid = File.read(pid_file).to_i
608     assert_not_equal orig_pid, pid
609     curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
610     assert $?.success?
612     # we could've inherited descriptors the first time around
613     assert expect_size >= curr_fds.size, curr_fds.inspect
614     expect_size = curr_fds.size
616     assert_nothing_raised do
617       Process.kill(:USR2, pid)
618       wait_for_file("#{pid_file}.oldbin")
619       Process.kill(:QUIT, pid)
620     end
621     wait_for_death(pid)
623     wait_master_ready(log.path)
624     File.truncate(log.path, 0)
625     wait_for_file(pid_file)
626     pid = File.read(pid_file).to_i
627     curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
628     assert $?.success?
629     assert_equal expect_size, curr_fds.size, curr_fds.inspect
631     Process.kill(:QUIT, pid)
632     wait_for_death(pid)
633   end
635 end if do_test