t/lib.perl: fix Perl integration tests w/o installation
[unicorn.git] / test / test_helper.rb
blob0bf3c90ee49000d78c51c9269568fae4d67b72ef
1 # -*- encoding: binary -*-
2 # frozen_string_literal: false
4 # Copyright (c) 2005 Zed A. Shaw
5 # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
6 # the GPLv2+ (GPLv3+ preferred)
8 # Additional work donated by contributors.  See git history
9 # for more information.
11 STDIN.sync = STDOUT.sync = STDERR.sync = true # buffering makes debugging hard
13 # FIXME: move curl-dependent tests into t/
14 ENV['NO_PROXY'] ||= ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
16 # Some tests watch a log file or a pid file to spring up to check state
17 # Can't rely on inotify on non-Linux and logging to a pipe makes things
18 # more complicated
19 DEFAULT_TRIES = 1000
20 DEFAULT_RES = 0.2
22 require 'test/unit'
23 require 'net/http'
24 require 'digest/sha1'
25 require 'uri'
26 require 'stringio'
27 require 'pathname'
28 require 'tempfile'
29 require 'fileutils'
30 require 'logger'
31 require 'unicorn'
32 require 'io/nonblock'
34 if ENV['DEBUG']
35   require 'ruby-debug'
36   Debugger.start
37 end
39 unless RUBY_VERSION < '3.1'
40   warn "Unicorn was only tested against MRI up to 3.0.\n" \
41        "It might not properly work with #{RUBY_VERSION}"
42 end
44 def redirect_test_io
45   orig_err = STDERR.dup
46   orig_out = STDOUT.dup
47   rdr_pid = $$
48   new_out = File.open("test_stdout.#$$.log", "a")
49   new_err = File.open("test_stderr.#$$.log", "a")
50   new_out.sync = new_err.sync = true
52   if tail = ENV['TAIL'] # "tail -F" if GNU, "tail -f" otherwise
53     require 'shellwords'
54     cmd = tail.shellsplit
55     cmd << new_out.path
56     cmd << new_err.path
57     pid = Process.spawn(*cmd, { 1 => 2, :pgroup => true })
58     sleep 0.1 # wait for tail(1) to startup
59   end
60   STDERR.reopen(new_err)
61   STDOUT.reopen(new_out)
62   STDERR.sync = STDOUT.sync = true
64   at_exit do
65     if rdr_pid == $$
66       File.unlink(new_out.path) rescue nil
67       File.unlink(new_err.path) rescue nil
68     end
69   end
71   begin
72     yield
73   ensure
74     STDERR.reopen(orig_err)
75     STDOUT.reopen(orig_out)
76     Process.kill(:TERM, pid) if pid
77   end
78 end
80 # which(1) exit codes cannot be trusted on some systems
81 # We use UNIX shell utilities in some tests because we don't trust
82 # ourselves to write Ruby 100% correctly :)
83 def which(bin)
84   ex = ENV['PATH'].split(/:/).detect do |x|
85     x << "/#{bin}"
86     File.executable?(x)
87   end or warn "`#{bin}' not found in PATH=#{ENV['PATH']}"
88   ex
89 end
91 # Either takes a string to do a get request against, or a tuple of [URI, HTTP] where
92 # HTTP is some kind of Net::HTTP request object (POST, HEAD, etc.)
93 def hit(uris)
94   results = []
95   uris.each do |u|
96     res = nil
98     if u.kind_of? String
99       u = 'http://127.0.0.1:8080/' if u == 'http://0.0.0.0:8080/'
100       res = Net::HTTP.get(URI.parse(u))
101     else
102       url = URI.parse(u[0])
103       res = Net::HTTP.new(url.host, url.port).start {|h| h.request(u[1]) }
104     end
106     assert res != nil, "Didn't get a response: #{u}"
107     results << res
108   end
110   return results
113 # unused_port provides an unused port on +addr+ usable for TCP that is
114 # guaranteed to be unused across all unicorn builds on that system.  It
115 # prevents race conditions by using a lock file other unicorn builds
116 # will see.  This is required if you perform several builds in parallel
117 # with a continuous integration system or run tests in parallel via
118 # gmake.  This is NOT guaranteed to be race-free if you run other
119 # processes that bind to random ports for testing (but the window
120 # for a race condition is very small).  You may also set UNICORN_TEST_ADDR
121 # to override the default test address (127.0.0.1).
122 def unused_port(addr = '127.0.0.1')
123   retries = 100
124   base = 5000
125   port = sock = nil
126   begin
127     begin
128       port = base + rand(32768 - base)
129       while port == Unicorn::Const::DEFAULT_PORT
130         port = base + rand(32768 - base)
131       end
133       sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
134       sock.bind(Socket.pack_sockaddr_in(port, addr))
135       sock.listen(5)
136     rescue Errno::EADDRINUSE, Errno::EACCES
137       sock.close rescue nil
138       retry if (retries -= 1) >= 0
139     end
141     # since we'll end up closing the random port we just got, there's a race
142     # condition could allow the random port we just chose to reselect itself
143     # when running tests in parallel with gmake.  Create a lock file while
144     # we have the port here to ensure that does not happen .
145     lock_path = "#{Dir::tmpdir}/unicorn_test.#{addr}:#{port}.lock"
146     File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600).close
147     at_exit { File.unlink(lock_path) rescue nil }
148   rescue Errno::EEXIST
149     sock.close rescue nil
150     retry
151   end
152   sock.close rescue nil
153   port
156 def try_require(lib)
157   begin
158     require lib
159     true
160   rescue LoadError
161     false
162   end
165 # sometimes the server may not come up right away
166 def retry_hit(uris = [])
167   tries = DEFAULT_TRIES
168   begin
169     hit(uris)
170   rescue Errno::EINVAL, Errno::ECONNREFUSED => err
171     if (tries -= 1) > 0
172       sleep DEFAULT_RES
173       retry
174     end
175     raise err
176   end
179 def assert_shutdown(pid)
180   wait_master_ready("test_stderr.#{pid}.log")
181   Process.kill(:QUIT, pid)
182   pid, status = Process.waitpid2(pid)
183   assert status.success?, "exited successfully"
186 def wait_workers_ready(path, nr_workers)
187   tries = DEFAULT_TRIES
188   lines = []
189   while (tries -= 1) > 0
190     begin
191       lines = File.readlines(path).grep(/worker=\d+ ready/)
192       lines.size == nr_workers and return
193     rescue Errno::ENOENT
194     end
195     sleep DEFAULT_RES
196   end
197   raise "#{nr_workers} workers never became ready:" \
198         "\n\t#{lines.join("\n\t")}\n"
201 def wait_master_ready(master_log)
202   tries = DEFAULT_TRIES
203   while (tries -= 1) > 0
204     begin
205       File.readlines(master_log).grep(/master process ready/)[0] and return
206     rescue Errno::ENOENT
207     end
208     sleep DEFAULT_RES
209   end
210   raise "master process never became ready"
213 def reexec_usr2_quit_test(pid, pid_file)
214   assert File.exist?(pid_file), "pid file OK"
215   assert ! File.exist?("#{pid_file}.oldbin"), "oldbin pid file"
216   Process.kill(:USR2, pid)
217   retry_hit(["http://#{@addr}:#{@port}/"])
218   wait_for_file("#{pid_file}.oldbin")
219   wait_for_file(pid_file)
221   old_pid = File.read("#{pid_file}.oldbin").to_i
222   new_pid = File.read(pid_file).to_i
224   # kill old master process
225   assert_not_equal pid, new_pid
226   assert_equal pid, old_pid
227   Process.kill(:QUIT, old_pid)
228   retry_hit(["http://#{@addr}:#{@port}/"])
229   wait_for_death(old_pid)
230   assert_equal new_pid, File.read(pid_file).to_i
231   retry_hit(["http://#{@addr}:#{@port}/"])
232   Process.kill(:QUIT, new_pid)
235 def reexec_basic_test(pid, pid_file)
236   results = retry_hit(["http://#{@addr}:#{@port}/"])
237   assert_equal String, results[0].class
238   Process.kill(0, pid)
239   master_log = "#{@tmpdir}/test_stderr.#{pid}.log"
240   wait_master_ready(master_log)
241   File.truncate(master_log, 0)
242   nr = 50
243   kill_point = 2
244   nr.times do |i|
245     hit(["http://#{@addr}:#{@port}/#{i}"])
246     i == kill_point and Process.kill(:HUP, pid)
247   end
248   wait_master_ready(master_log)
249   assert File.exist?(pid_file), "pid=#{pid_file} exists"
250   new_pid = File.read(pid_file).to_i
251   assert_not_equal pid, new_pid
252   Process.kill(0, new_pid)
253   Process.kill(:QUIT, new_pid)
256 def wait_for_file(path)
257   tries = DEFAULT_TRIES
258   while (tries -= 1) > 0 && ! File.exist?(path)
259     sleep DEFAULT_RES
260   end
261   assert File.exist?(path), "path=#{path} exists #{caller.inspect}"
264 def xfork(&block)
265   fork do
266     ObjectSpace.each_object(Tempfile) do |tmp|
267       ObjectSpace.undefine_finalizer(tmp)
268     end
269     yield
270   end
273 # can't waitpid on detached processes
274 def wait_for_death(pid)
275   tries = DEFAULT_TRIES
276   while (tries -= 1) > 0
277     begin
278       Process.kill(0, pid)
279       begin
280         Process.waitpid(pid, Process::WNOHANG)
281       rescue Errno::ECHILD
282       end
283       sleep(DEFAULT_RES)
284     rescue Errno::ESRCH
285       return
286     end
287   end
288   raise "PID:#{pid} never died!"
291 def reset_sig_handlers
292   %w(WINCH QUIT INT TERM USR1 USR2 HUP TTIN TTOU CHLD).each do |sig|
293     trap(sig, "DEFAULT")
294   end
297 def tcp_socket(*args)
298   sock = TCPSocket.new(*args)
299   sock.nonblock = false
300   sock
303 def unix_socket(*args)
304   sock = UNIXSocket.new(*args)
305   sock.nonblock = false
306   sock