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
15 HERE = File.dirname(__FILE__) unless defined?(HERE)
16 %w(lib ext).each do |dir|
17 $LOAD_PATH.unshift "#{HERE}/../#{dir}"
38 STDERR.reopen("test_stderr.#{$$}.log", "a")
39 STDOUT.reopen("test_stdout.#{$$}.log", "a")
40 STDERR.sync = STDOUT.sync = true
43 File.unlink("test_stderr.#{$$}.log") rescue nil
44 File.unlink("test_stdout.#{$$}.log") rescue nil
50 STDERR.reopen(orig_err)
51 STDOUT.reopen(orig_out)
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 :)
59 ex = ENV['PATH'].split(/:/).detect do |x|
62 end or warn "`#{bin}' not found in PATH=#{ENV['PATH']}"
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.)
74 res = Net::HTTP.get(URI.parse(u))
77 res = Net::HTTP.new(url.host, url.port).start {|h| h.request(u[1]) }
80 assert res != nil, "Didn't get a response: #{u}"
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')
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))
106 rescue Errno::EADDRINUSE, Errno::EACCES
107 sock.close rescue nil
108 retry if (retries -= 1) >= 0
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 }
119 sock.close rescue nil
122 sock.close rescue nil
135 # sometimes the server may not come up right away
136 def retry_hit(uris = [])
137 tries = DEFAULT_TRIES
140 rescue Errno::ECONNREFUSED => err
149 def assert_shutdown(pid)
150 wait_master_ready("test_stderr.#{pid}.log")
151 assert_nothing_raised { Process.kill(:QUIT, pid) }
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
160 while (tries -= 1) > 0
162 lines = File.readlines(path).grep(/worker=\d+ ready/)
163 lines.size == nr_workers and return
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
176 File.readlines(master_log).grep(/master process ready/)[0] and return
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)
215 assert_nothing_raised do
217 hit(["http://#{@addr}:#{@port}/#{i}"])
218 i == kill_point and Process.kill(:HUP, pid)
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)
234 assert File.exist?(path), "path=#{path} exists #{caller.inspect}"
239 ObjectSpace.each_object(Tempfile) do |tmp|
240 ObjectSpace.undefine_finalizer(tmp)
246 # can't waitpid on detached processes
247 def wait_for_death(pid)
248 tries = DEFAULT_TRIES
249 while (tries -= 1) > 0
253 Process.waitpid(pid, Process::WNOHANG)
261 raise "PID:#{pid} never died!"