about summary refs log tree commit homepage
path: root/test
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-02-13 00:08:32 -0800
committerEric Wong <normalperson@yhbt.net>2009-02-13 00:08:32 -0800
commit0bd2bf7c438eb4394b7a66ca0999ab896beccf95 (patch)
tree3523688db5a7e1b9c48e89e083a0665506b8131d /test
parent3be825a50f0d9f7aa443e277a1b6c6bb667c1fb1 (diff)
downloadunicorn-0bd2bf7c438eb4394b7a66ca0999ab896beccf95.tar.gz
unused_port is more reliable as it actually tries to bind
a port and retries if it fails.  This is also safe across
parallel unicorn tests running in different directories.
Diffstat (limited to 'test')
-rw-r--r--test/test_helper.rb44
-rw-r--r--test/unit/test_server.rb2
2 files changed, 39 insertions, 7 deletions
diff --git a/test/test_helper.rb b/test/test_helper.rb
index ae4f178..46fe2d1 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -21,6 +21,7 @@ require 'uri'
 require 'stringio'
 require 'pp'
 require 'unicorn'
+require 'tmpdir'
 
 if ENV['DEBUG']
   require 'ruby-debug'
@@ -67,10 +68,41 @@ def hit(uris)
   return results
 end
 
-# process_based_port provides a port number, usable for TCP and UDP  
-# connections based on $$ and with a 5000 as base.
-# this is required if you perform several builds of unicorn in parallel
-# (like continuous integration systems)
-def process_based_port
-  5000 + $$ % 1000
+# unused_port provides an unused port on +addr+ usable for TCP that is
+# guaranteed to be unused across all unicorn builds on that system.  It
+# prevents race conditions by using a lock file other unicorn builds
+# will see.  This is required if you perform several builds in parallel
+# with a continuous integration system or run tests in parallel via
+# gmake.  This is NOT guaranteed to be race-free if you run other
+# processes that bind to random ports for testing (but the window
+# for a race condition is very small).  You may also set UNICORN_TEST_ADDR
+# to override the default test address (127.0.0.1).
+def unused_port(addr = '127.0.0.1')
+  retries = 100
+  base = 5000
+  port = sock = nil
+  begin
+    begin
+      port = base + rand(32768 - base)
+      sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
+      sock.bind(Socket.pack_sockaddr_in(port, addr))
+      sock.listen(5)
+    rescue Errno::EADDRINUSE, Errno::EACCES
+      sock.close rescue nil
+      retry if (retries -= 1) >= 0
+    end
+
+    # since we'll end up closing the random port we just got, there's a race
+    # condition could allow the random port we just chose to reselect itself
+    # when running tests in parallel with gmake.  Create a lock file while
+    # we have the port here to ensure that does not happen .
+    lock_path = "#{Dir::tmpdir}/unicorn_test.#{addr}:#{port}.lock"
+    lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
+    at_exit { File.unlink(lock_path) rescue nil }
+  rescue Errno::EEXIST
+    sock.close rescue nil
+    retry
+  end
+  sock.close rescue nil
+  port
 end
diff --git a/test/unit/test_server.rb b/test/unit/test_server.rb
index e9bd487..d19064c 100644
--- a/test/unit/test_server.rb
+++ b/test/unit/test_server.rb
@@ -21,7 +21,7 @@ class WebServerTest < Test::Unit::TestCase
 
   def setup
     @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
-    @port = process_based_port
+    @port = unused_port
     @tester = TestHandler.new
     redirect_test_io do
       @server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )