summaryrefslogtreecommitdiff
path: root/indra/llmessage/tests/testrunner.py
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2011-05-10 08:21:21 -0400
committerNat Goodspeed <nat@lindenlab.com>2011-05-10 08:21:21 -0400
commit8e8eb76eb9d0efabc82fec194f6edb4838c49955 (patch)
tree33b55d2c87c1c10e7136385d872e29d3fffc30fe /indra/llmessage/tests/testrunner.py
parenta5118ccd6721afdf4f8c71cba6007eb7be4d7c19 (diff)
CHOP-661: add and use code to listen on next available server port.
In indra/llmessage/tests/testrunner.py, introduce new freeport() function to try a caller-specified expression (such as instantiating an object that will listen on a server port) with a range of candidate port numbers until the expression produces a value instead of EADDRINUSE exception. Change test_llsdmessage_peer.py and test_llxmlrpc_peer.py to use freeport() to construct their server class inline BEFORE launching the thread that will run it, then pass that server's serve_forever method to daemon thread. Also set os.environ["PORT"] to selected environment variable before running subject test program. In indra/llmessage/tests/commtest.h, introduce commtest_data::getport() to read port number from specified environment variable, throwing exception if variable not set or non-numeric. Construct default LLHost from getport("PORT") instead of hardcoded constant. Change indra/newview/tests/llxmlrpclistener_test.cpp to use commtest_data:: getport("PORT") instead of hardcoded constant. Also use LLSD::with() rather than older LLSD::insert() syntax. HOWEVER -- I am irritated to discover that llxmlrpclistener_test IS NOT RUN or even built by newview/CMakeLists.txt! It's not even commented out -- it's entirely deleted! I am determined to restore this test. However, as it will take some fiddling with new link-time dependencies, that will be a separate commit.
Diffstat (limited to 'indra/llmessage/tests/testrunner.py')
-rw-r--r--indra/llmessage/tests/testrunner.py81
1 files changed, 81 insertions, 0 deletions
diff --git a/indra/llmessage/tests/testrunner.py b/indra/llmessage/tests/testrunner.py
index b70ce91ee7..8ff13e0426 100644
--- a/indra/llmessage/tests/testrunner.py
+++ b/indra/llmessage/tests/testrunner.py
@@ -29,6 +29,8 @@ $/LicenseInfo$
import os
import sys
+import errno
+import socket
def debug(*args):
sys.stdout.writelines(args)
@@ -36,6 +38,85 @@ def debug(*args):
# comment out the line below to enable debug output
debug = lambda *args: None
+def freeport(portlist, expr):
+ """
+ Find a free server port to use. Specifically, evaluate 'expr' (a
+ callable(port)) until it stops raising EADDRINUSE exception.
+
+ Pass:
+
+ portlist: an iterable (e.g. xrange()) of ports to try. If you exhaust the
+ range, freeport() lets the socket.error exception propagate. If you want
+ unbounded, you could pass itertools.count(baseport), though of course in
+ practice the ceiling is 2^16-1 anyway. But it seems prudent to constrain
+ the range much more sharply: if we're iterating an absurd number of times,
+ probably something else is wrong.
+
+ expr: a callable accepting a port number, specifically one of the items
+ from portlist. If calling that callable raises socket.error with
+ EADDRINUSE, freeport() retrieves the next item from portlist and retries.
+
+ Returns: (expr(port), port)
+
+ port: the value from portlist for which expr(port) succeeded
+
+ Raises:
+
+ Any exception raised by expr(port) other than EADDRINUSE.
+
+ socket.error if, for every item from portlist, expr(port) raises
+ socket.error. The exception you see is the one from the last item in
+ portlist.
+
+ StopIteration if portlist is completely empty.
+
+ Example:
+
+ server, port = freeport(xrange(8000, 8010),
+ lambda port: HTTPServer(("localhost", port),
+ MyRequestHandler))
+ # pass 'port' to client code
+ # call server.serve_forever()
+ """
+ # If portlist is completely empty, let StopIteration propagate: that's an
+ # error because we can't return meaningful values. We have no 'port',
+ # therefore no 'expr(port)'.
+ portiter = iter(portlist)
+ port = portiter.next()
+
+ while True:
+ try:
+ # If this value of port works, return as promised.
+ return expr(port), port
+
+ except socket.error, err:
+ # Anything other than 'Address already in use', propagate
+ if err.args[0] != errno.EADDRINUSE:
+ raise
+
+ # Here we want the next port from portiter. But on StopIteration,
+ # we want to raise the original exception rather than
+ # StopIteration. So save the original exc_info().
+ type, value, tb = sys.exc_info()
+ try:
+ try:
+ port = portiter.next()
+ except StopIteration:
+ raise type, value, tb
+ finally:
+ # Clean up local traceback, see docs for sys.exc_info()
+ del tb
+
+ # Recap of the control flow above:
+ # If expr(port) doesn't raise, return as promised.
+ # If expr(port) raises anything but EADDRINUSE, propagate that
+ # exception.
+ # If portiter.next() raises StopIteration -- that is, if the port
+ # value we just passed to expr(port) was the last available -- reraise
+ # the EADDRINUSE exception.
+ # If we've actually arrived at this point, portiter.next() delivered a
+ # new port value. Loop back to pass that to expr(port).
+
def run(*args, **kwds):
"""All positional arguments collectively form a command line, executed as
a synchronous child process.