minor cleanups and save a few variables
[unicorn.git] / test / test_helper.rb
blob55aa70c81bb41bda922779778c63604e44a84a43
1 # Copyright (c) 2005 Zed A. Shaw 
2 # You can redistribute it and/or modify it under the same terms as Ruby.
4 # Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html 
5 # for more information.
7 STDIN.sync = STDOUT.sync = STDERR.sync = true # buffering makes debugging hard
9 # Some tests watch a log file or a pid file to spring up to check state
10 # Can't rely on inotify on non-Linux and logging to a pipe makes things
11 # more complicated
12 DEFAULT_TRIES = 1000
13 DEFAULT_RES = 0.2
15 HERE = File.dirname(__FILE__) unless defined?(HERE)
16 %w(lib ext).each do |dir|
17   $LOAD_PATH.unshift "#{HERE}/../#{dir}"
18 end
20 require 'test/unit'
21 require 'net/http'
22 require 'digest/sha1'
23 require 'uri'
24 require 'stringio'
25 require 'pathname'
26 require 'tempfile'
27 require 'fileutils'
28 require 'unicorn'
30 if ENV['DEBUG']
31   require 'ruby-debug'
32   Debugger.start
33 end
35 def redirect_test_io
36   orig_err = STDERR.dup
37   orig_out = STDOUT.dup
38   STDERR.reopen("test_stderr.#{$$}.log", "a")
39   STDOUT.reopen("test_stdout.#{$$}.log", "a")
40   STDERR.sync = STDOUT.sync = true
42   at_exit do
43     File.unlink("test_stderr.#{$$}.log") rescue nil
44     File.unlink("test_stdout.#{$$}.log") rescue nil
45   end
47   begin
48     yield
49   ensure
50     STDERR.reopen(orig_err)
51     STDOUT.reopen(orig_out)
52   end
53 end
55 # which(1) exit codes cannot be trusted on some systems
56 # We use UNIX shell utilities in some tests because we don't trust
57 # ourselves to write Ruby 100% correctly :)
58 def which(bin)
59   ex = ENV['PATH'].split(/:/).detect do |x|
60     x << "/#{bin}"
61     File.executable?(x)
62   end or warn "`#{bin}' not found in PATH=#{ENV['PATH']}"
63   ex
64 end
66 # Either takes a string to do a get request against, or a tuple of [URI, HTTP] where
67 # HTTP is some kind of Net::HTTP request object (POST, HEAD, etc.)
68 def hit(uris)
69   results = []
70   uris.each do |u|
71     res = nil
73     if u.kind_of? String
74       res = Net::HTTP.get(URI.parse(u))
75     else
76       url = URI.parse(u[0])
77       res = Net::HTTP.new(url.host, url.port).start {|h| h.request(u[1]) }
78     end
80     assert res != nil, "Didn't get a response: #{u}"
81     results << res
82   end
84   return results
85 end
87 # unused_port provides an unused port on +addr+ usable for TCP that is
88 # guaranteed to be unused across all unicorn builds on that system.  It
89 # prevents race conditions by using a lock file other unicorn builds
90 # will see.  This is required if you perform several builds in parallel
91 # with a continuous integration system or run tests in parallel via
92 # gmake.  This is NOT guaranteed to be race-free if you run other
93 # processes that bind to random ports for testing (but the window
94 # for a race condition is very small).  You may also set UNICORN_TEST_ADDR
95 # to override the default test address (127.0.0.1).
96 def unused_port(addr = '127.0.0.1')
97   retries = 100
98   base = 5000
99   port = sock = nil
100   begin
101     begin
102       port = base + rand(32768 - base)
103       sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
104       sock.bind(Socket.pack_sockaddr_in(port, addr))
105       sock.listen(5)
106     rescue Errno::EADDRINUSE, Errno::EACCES
107       sock.close rescue nil
108       retry if (retries -= 1) >= 0
109     end
111     # since we'll end up closing the random port we just got, there's a race
112     # condition could allow the random port we just chose to reselect itself
113     # when running tests in parallel with gmake.  Create a lock file while
114     # we have the port here to ensure that does not happen .
115     lock_path = "#{Dir::tmpdir}/unicorn_test.#{addr}:#{port}.lock"
116     lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
117     at_exit { File.unlink(lock_path) rescue nil }
118   rescue Errno::EEXIST
119     sock.close rescue nil
120     retry
121   end
122   sock.close rescue nil
123   port
126 def try_require(lib)
127   begin
128     require lib
129     true
130   rescue LoadError
131     false
132   end
135 # sometimes the server may not come up right away
136 def retry_hit(uris = [])
137   tries = DEFAULT_TRIES
138   begin
139     hit(uris)
140   rescue Errno::ECONNREFUSED => err
141     if (tries -= 1) > 0
142       sleep DEFAULT_RES
143       retry
144     end
145     raise err
146   end
149 def assert_shutdown(pid)
150   wait_master_ready("test_stderr.#{pid}.log")
151   assert_nothing_raised { Process.kill(:QUIT, pid) }
152   status = nil
153   assert_nothing_raised { pid, status = Process.waitpid2(pid) }
154   assert status.success?, "exited successfully"
157 def wait_workers_ready(path, nr_workers)
158   tries = DEFAULT_TRIES
159   lines = []
160   while (tries -= 1) > 0
161     begin
162       lines = File.readlines(path).grep(/worker=\d+ ready/)
163       lines.size == nr_workers and return
164     rescue Errno::ENOENT
165     end
166     sleep DEFAULT_RES
167   end
168   raise "#{nr_workers} workers never became ready:" \
169         "\n\t#{lines.join("\n\t")}\n"
172 def wait_master_ready(master_log)
173   tries = DEFAULT_TRIES
174   while (tries -= 1) > 0
175     begin
176       File.readlines(master_log).grep(/master process ready/)[0] and return
177     rescue Errno::ENOENT
178     end
179     sleep DEFAULT_RES
180   end
181   raise "master process never became ready"
184 def reexec_usr2_quit_test(pid, pid_file)
185   assert File.exist?(pid_file), "pid file OK"
186   assert ! File.exist?("#{pid_file}.oldbin"), "oldbin pid file"
187   assert_nothing_raised { Process.kill(:USR2, pid) }
188   assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
189   wait_for_file("#{pid_file}.oldbin")
190   wait_for_file(pid_file)
192   old_pid = File.read("#{pid_file}.oldbin").to_i
193   new_pid = File.read(pid_file).to_i
195   # kill old master process
196   assert_not_equal pid, new_pid
197   assert_equal pid, old_pid
198   assert_nothing_raised { Process.kill(:QUIT, old_pid) }
199   assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
200   wait_for_death(old_pid)
201   assert_equal new_pid, File.read(pid_file).to_i
202   assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
203   assert_nothing_raised { Process.kill(:QUIT, new_pid) }
206 def reexec_basic_test(pid, pid_file)
207   results = retry_hit(["http://#{@addr}:#{@port}/"])
208   assert_equal String, results[0].class
209   assert_nothing_raised { Process.kill(0, pid) }
210   master_log = "#{@tmpdir}/test_stderr.#{pid}.log"
211   wait_master_ready(master_log)
212   File.truncate(master_log, 0)
213   nr = 50
214   kill_point = 2
215   assert_nothing_raised do
216     nr.times do |i|
217       hit(["http://#{@addr}:#{@port}/#{i}"])
218       i == kill_point and Process.kill(:HUP, pid)
219     end
220   end
221   wait_master_ready(master_log)
222   assert File.exist?(pid_file), "pid=#{pid_file} exists"
223   new_pid = File.read(pid_file).to_i
224   assert_not_equal pid, new_pid
225   assert_nothing_raised { Process.kill(0, new_pid) }
226   assert_nothing_raised { Process.kill(:QUIT, new_pid) }
229 def wait_for_file(path)
230   tries = DEFAULT_TRIES
231   while (tries -= 1) > 0 && ! File.exist?(path)
232     sleep DEFAULT_RES
233   end
234   assert File.exist?(path), "path=#{path} exists #{caller.inspect}"
237 def xfork(&block)
238   fork do
239     ObjectSpace.each_object(Tempfile) do |tmp|
240       ObjectSpace.undefine_finalizer(tmp)
241     end
242     yield
243   end
246 # can't waitpid on detached processes
247 def wait_for_death(pid)
248   tries = DEFAULT_TRIES
249   while (tries -= 1) > 0
250     begin
251       Process.kill(0, pid)
252       begin
253         Process.waitpid(pid, Process::WNOHANG)
254       rescue Errno::ECHILD
255       end
256       sleep(DEFAULT_RES)
257     rescue Errno::ESRCH
258       return
259     end
260   end
261   raise "PID:#{pid} never died!"