From 0bd2bf7c438eb4394b7a66ca0999ab896beccf95 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 13 Feb 2009 00:08:32 -0800 Subject: tests: replace process_based_port with unused_port 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. --- test/test_helper.rb | 44 ++++++++++++++++++++++++++++++++++++++------ test/unit/test_server.rb | 2 +- 2 files changed, 39 insertions(+), 7 deletions(-) (limited to 'test') 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}" ] ) -- cgit v1.2.3-24-ge0c7