Replace get_unused_port with bind_unused_port, which binds to port 0

to allow the OS to select a port for us.

Closes #590.
This commit is contained in:
Ben Darnell 2012-09-10 09:53:08 -07:00
parent 7610920151
commit 95414238f3
9 changed files with 53 additions and 34 deletions

View File

@ -8,10 +8,9 @@ from contextlib import closing
import functools
from tornado.escape import utf8
from tornado.httpclient import AsyncHTTPClient
from tornado.iostream import IOStream
from tornado import netutil
from tornado.testing import AsyncHTTPTestCase, get_unused_port
from tornado.testing import AsyncHTTPTestCase, bind_unused_port
from tornado.util import b, bytes_type
from tornado.web import Application, RequestHandler, url
@ -108,8 +107,7 @@ class HTTPClientCommonTestCase(AsyncHTTPTestCase):
def test_chunked_close(self):
# test case in which chunks spread read-callback processing
# over several ioloop iterations, but the connection is already closed.
port = get_unused_port()
(sock,) = netutil.bind_sockets(port, address="127.0.0.1")
sock, port = bind_unused_port()
with closing(sock):
def write_response(stream, request_data):
stream.write(b("""\

View File

@ -3,12 +3,10 @@
from __future__ import absolute_import, division, with_statement
import datetime
import socket
import time
from tornado.ioloop import IOLoop
from tornado.netutil import bind_sockets
from tornado.testing import AsyncTestCase, get_unused_port
from tornado.testing import AsyncTestCase, bind_unused_port
from tornado.test.util import unittest
@ -35,8 +33,7 @@ class TestIOLoop(AsyncTestCase):
self.wait()
def test_multiple_add(self):
[sock] = bind_sockets(get_unused_port(), '127.0.0.1',
family=socket.AF_INET)
sock, port = bind_unused_port()
try:
self.io_loop.add_handler(sock.fileno(), lambda fd, events: None,
IOLoop.READ)

View File

@ -3,7 +3,7 @@ from tornado import netutil
from tornado.ioloop import IOLoop
from tornado.iostream import IOStream, SSLIOStream
from tornado.log import gen_log
from tornado.testing import AsyncHTTPTestCase, AsyncHTTPSTestCase, AsyncTestCase, get_unused_port, ExpectLog
from tornado.testing import AsyncHTTPTestCase, AsyncHTTPSTestCase, AsyncTestCase, bind_unused_port, ExpectLog
from tornado.test.util import unittest
from tornado.util import b
from tornado.web import RequestHandler, Application
@ -120,9 +120,7 @@ class TestIOStreamMixin(object):
raise NotImplementedError()
def make_iostream_pair(self, **kwargs):
port = get_unused_port()
[listener] = netutil.bind_sockets(port, '127.0.0.1',
family=socket.AF_INET)
listener, port = bind_unused_port()
streams = [None, None]
def accept_callback(connection, address):
@ -168,7 +166,8 @@ class TestIOStreamMixin(object):
# When a connection is refused, the connect callback should not
# be run. (The kqueue IOLoop used to behave differently from the
# epoll IOLoop in this respect)
port = get_unused_port()
server_socket, port = bind_unused_port()
server_socket.close()
stream = IOStream(socket.socket(), self.io_loop)
self.connect_called = False

View File

@ -10,10 +10,9 @@ from tornado.httpclient import HTTPClient, HTTPError
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.log import gen_log
from tornado.netutil import bind_sockets
from tornado.process import fork_processes, task_id
from tornado.simple_httpclient import SimpleAsyncHTTPClient
from tornado.testing import get_unused_port, ExpectLog
from tornado.testing import bind_unused_port, ExpectLog
from tornado.test.util import unittest
from tornado.web import RequestHandler, Application
@ -50,11 +49,10 @@ class ProcessTest(unittest.TestCase):
def test_multi_process(self):
with ExpectLog(gen_log, "(Starting .* processes|child .* exited|uncaught exception)"):
self.assertFalse(IOLoop.initialized())
port = get_unused_port()
sock, port = bind_unused_port()
def get_url(path):
return "http://127.0.0.1:%d%s" % (port, path)
sockets = bind_sockets(port, "127.0.0.1")
# ensure that none of these processes live too long
signal.alarm(5) # master process
try:
@ -66,19 +64,17 @@ class ProcessTest(unittest.TestCase):
# finished with status 0
self.assertEqual(e.code, 0)
self.assertTrue(task_id() is None)
for sock in sockets:
sock.close()
sock.close()
return
try:
if id in (0, 1):
self.assertEqual(id, task_id())
server = HTTPServer(self.get_app())
server.add_sockets(sockets)
server.add_sockets([sock])
IOLoop.instance().start()
elif id == 2:
self.assertEqual(id, task_id())
for sock in sockets:
sock.close()
sock.close()
# Always use SimpleAsyncHTTPClient here; the curl
# version appears to get confused sometimes if the
# connection gets closed before it's had a chance to

View File

@ -17,7 +17,7 @@ from tornado.log import gen_log
from tornado.simple_httpclient import SimpleAsyncHTTPClient, _DEFAULT_CA_CERTS
from tornado.test.httpclient_test import ChunkHandler, CountdownHandler, HelloWorldHandler
from tornado.test import httpclient_test
from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, get_unused_port, ExpectLog
from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, bind_unused_port, ExpectLog
from tornado.test.util import unittest
from tornado.util import b
from tornado.web import RequestHandler, Application, asynchronous, url
@ -288,7 +288,8 @@ class SimpleHTTPClientTestCase(AsyncHTTPTestCase):
self.assertTrue(host_re.match(response.body), response.body)
def test_connection_refused(self):
port = get_unused_port()
server_socket, port = bind_unused_port()
server_socket.close()
with ExpectLog(gen_log, ".*"):
self.http_client.fetch("http://localhost:%d/" % port, self.stop)
response = self.wait()

View File

@ -42,9 +42,10 @@ except ImportError:
have_twisted = False
from tornado.httpclient import AsyncHTTPClient
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.platform.auto import set_close_exec
from tornado.testing import get_unused_port
from tornado.testing import bind_unused_port
from tornado.test.util import unittest
from tornado.util import import_object
from tornado.web import RequestHandler, Application
@ -344,8 +345,8 @@ class CompatibilityTests(unittest.TestCase):
def render_GET(self, request):
return "Hello from twisted!"
site = Site(HelloResource())
self.twisted_port = get_unused_port()
self.reactor.listenTCP(self.twisted_port, site, interface='127.0.0.1')
port = self.reactor.listenTCP(0, site, interface='127.0.0.1')
self.twisted_port = port.getHost().port
def start_tornado_server(self):
class HelloHandler(RequestHandler):
@ -353,8 +354,9 @@ class CompatibilityTests(unittest.TestCase):
self.write("Hello from tornado!")
app = Application([('/', HelloHandler)],
log_function=lambda x: None)
self.tornado_port = get_unused_port()
app.listen(self.tornado_port, address='127.0.0.1', io_loop=self.io_loop)
server = HTTPServer(app, io_loop=self.io_loop)
sock, self.tornado_port = bind_unused_port()
server.add_sockets([sock])
def run_ioloop(self):
self.stop_loop = self.io_loop.stop

View File

@ -26,12 +26,14 @@ try:
from tornado.httpserver import HTTPServer
from tornado.simple_httpclient import SimpleAsyncHTTPClient
from tornado.ioloop import IOLoop
from tornado import netutil
except ImportError:
# These modules are not importable on app engine. Parts of this module
# won't work, but e.g. LogTrapTestCase and main() will.
AsyncHTTPClient = None
HTTPServer = None
IOLoop = None
netutil = None
SimpleAsyncHTTPClient = None
from tornado.log import gen_log
from tornado.stack_context import StackContext, NullContext
@ -41,6 +43,7 @@ import logging
import os
import re
import signal
import socket
import sys
import time
@ -57,13 +60,31 @@ _next_port = 10000
def get_unused_port():
"""Returns a (hopefully) unused port number."""
"""Returns a (hopefully) unused port number.
This function does not guarantee that the port it returns is available,
only that a series of get_unused_port calls in a single process return
distinct ports.
**Deprecated**. Use bind_unused_port instead, which is guaranteed
to find an unused port.
"""
global _next_port
port = _next_port
_next_port = _next_port + 1
return port
def bind_unused_port():
"""Binds a server socket to an available port on localhost.
Returns a tuple (socket, port).
"""
[sock] = netutil.bind_sockets(0, 'localhost', family=socket.AF_INET)
port = sock.getsockname()[1]
return sock, port
class AsyncTestCase(unittest.TestCase):
"""TestCase subclass for testing IOLoop-based asynchronous code.
@ -248,7 +269,9 @@ class AsyncHTTPTestCase(AsyncTestCase):
self.http_client = self.get_http_client()
self._app = self.get_app()
self.http_server = self.get_http_server()
self.http_server.listen(self.get_http_port(), address="127.0.0.1")
sock, port = bind_unused_port()
self.http_server.add_sockets([sock])
self.__port = port
def get_http_client(self):
return AsyncHTTPClient(io_loop=self.io_loop)
@ -285,8 +308,6 @@ class AsyncHTTPTestCase(AsyncTestCase):
A new port is chosen for each test.
"""
if self.__port is None:
self.__port = get_unused_port()
return self.__port
def get_protocol(self):

View File

@ -30,3 +30,6 @@ In progress
* Empty HTTP request arguments are no longer ignored. This applies to
``HTTPRequest.arguments`` and ``RequestHandler.get_argument[s]``
in WSGI and non-WSGI modes.
* New function `tornado.testing.bind_unused_port` both chooses a port
and binds a socket to it, so there is no risk of another process
using the same port. ``get_unused_port`` is now deprecated.

View File

@ -32,4 +32,6 @@
Helper functions
----------------
.. autofunction:: bind_unused_port
.. autofunction:: get_unused_port