PageRenderTime 57ms CodeModel.GetById 15ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/llmessage/tests/testrunner.py

https://bitbucket.org/lindenlab/viewer-beta/
Python | 262 lines | 180 code | 21 blank | 61 comment | 9 complexity | 8f3adf06d3bd1c66d01f87be3f42add5 MD5 | raw file
  1#!/usr/bin/env python
  2"""\
  3@file   testrunner.py
  4@author Nat Goodspeed
  5@date   2009-03-20
  6@brief  Utilities for writing wrapper scripts for ADD_COMM_BUILD_TEST unit tests
  7
  8$LicenseInfo:firstyear=2009&license=viewerlgpl$
  9Second Life Viewer Source Code
 10Copyright (C) 2010, Linden Research, Inc.
 11
 12This library is free software; you can redistribute it and/or
 13modify it under the terms of the GNU Lesser General Public
 14License as published by the Free Software Foundation;
 15version 2.1 of the License only.
 16
 17This library is distributed in the hope that it will be useful,
 18but WITHOUT ANY WARRANTY; without even the implied warranty of
 19MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 20Lesser General Public License for more details.
 21
 22You should have received a copy of the GNU Lesser General Public
 23License along with this library; if not, write to the Free Software
 24Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 25
 26Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 27$/LicenseInfo$
 28"""
 29
 30from __future__ import with_statement
 31
 32import os
 33import sys
 34import re
 35import errno
 36import socket
 37
 38VERBOSE = os.environ.get("INTEGRATION_TEST_VERBOSE", "1") # default to verbose
 39# Support usage such as INTEGRATION_TEST_VERBOSE=off -- distressing to user if
 40# that construct actually turns on verbosity...
 41VERBOSE = not re.match(r"(0|off|false|quiet)$", VERBOSE, re.IGNORECASE)
 42
 43if VERBOSE:
 44    def debug(fmt, *args):
 45        print fmt % args
 46        sys.stdout.flush()
 47else:
 48    debug = lambda *args: None
 49
 50def freeport(portlist, expr):
 51    """
 52    Find a free server port to use. Specifically, evaluate 'expr' (a
 53    callable(port)) until it stops raising EADDRINUSE exception.
 54
 55    Pass:
 56
 57    portlist: an iterable (e.g. xrange()) of ports to try. If you exhaust the
 58    range, freeport() lets the socket.error exception propagate. If you want
 59    unbounded, you could pass itertools.count(baseport), though of course in
 60    practice the ceiling is 2^16-1 anyway. But it seems prudent to constrain
 61    the range much more sharply: if we're iterating an absurd number of times,
 62    probably something else is wrong.
 63
 64    expr: a callable accepting a port number, specifically one of the items
 65    from portlist. If calling that callable raises socket.error with
 66    EADDRINUSE, freeport() retrieves the next item from portlist and retries.
 67
 68    Returns: (expr(port), port)
 69
 70    port: the value from portlist for which expr(port) succeeded
 71
 72    Raises:
 73
 74    Any exception raised by expr(port) other than EADDRINUSE.
 75
 76    socket.error if, for every item from portlist, expr(port) raises
 77    socket.error. The exception you see is the one from the last item in
 78    portlist.
 79
 80    StopIteration if portlist is completely empty.
 81
 82    Example:
 83
 84    class Server(HTTPServer):
 85        # If you use BaseHTTPServer.HTTPServer, turning off this flag is
 86        # essential for proper operation of freeport()!
 87        allow_reuse_address = False
 88    # ...
 89    server, port = freeport(xrange(8000, 8010),
 90                            lambda port: Server(("localhost", port),
 91                                                MyRequestHandler))
 92    # pass 'port' to client code
 93    # call server.serve_forever()
 94    """
 95    try:
 96        # If portlist is completely empty, let StopIteration propagate: that's an
 97        # error because we can't return meaningful values. We have no 'port',
 98        # therefore no 'expr(port)'.
 99        portiter = iter(portlist)
100        port = portiter.next()
101
102        while True:
103            try:
104                # If this value of port works, return as promised.
105                value = expr(port)
106
107            except socket.error, err:
108                # Anything other than 'Address already in use', propagate
109                if err.args[0] != errno.EADDRINUSE:
110                    raise
111
112                # Here we want the next port from portiter. But on StopIteration,
113                # we want to raise the original exception rather than
114                # StopIteration. So save the original exc_info().
115                type, value, tb = sys.exc_info()
116                try:
117                    try:
118                        port = portiter.next()
119                    except StopIteration:
120                        raise type, value, tb
121                finally:
122                    # Clean up local traceback, see docs for sys.exc_info()
123                    del tb
124
125            else:
126                debug("freeport() returning %s on port %s", value, port)
127                return value, port
128
129            # Recap of the control flow above:
130            # If expr(port) doesn't raise, return as promised.
131            # If expr(port) raises anything but EADDRINUSE, propagate that
132            # exception.
133            # If portiter.next() raises StopIteration -- that is, if the port
134            # value we just passed to expr(port) was the last available -- reraise
135            # the EADDRINUSE exception.
136            # If we've actually arrived at this point, portiter.next() delivered a
137            # new port value. Loop back to pass that to expr(port).
138
139    except Exception, err:
140        debug("*** freeport() raising %s: %s", err.__class__.__name__, err)
141        raise
142
143def run(*args, **kwds):
144    """All positional arguments collectively form a command line, executed as
145    a synchronous child process.
146    In addition, pass server=new_thread_instance as an explicit keyword (to
147    differentiate it from an additional command-line argument).
148    new_thread_instance should be an instantiated but not yet started Thread
149    subclass instance, e.g.:
150    run("python", "-c", 'print "Hello, world!"', server=TestHTTPServer(name="httpd"))
151    """
152    # If there's no server= keyword arg, don't start a server thread: simply
153    # run a child process.
154    try:
155        thread = kwds.pop("server")
156    except KeyError:
157        pass
158    else:
159        # Start server thread. Note that this and all other comm server
160        # threads should be daemon threads: we'll let them run "forever,"
161        # confident that the whole process will terminate when the main thread
162        # terminates, which will be when the child process terminates.
163        thread.setDaemon(True)
164        thread.start()
165    # choice of os.spawnv():
166    # - [v vs. l] pass a list of args vs. individual arguments,
167    # - [no p] don't use the PATH because we specifically want to invoke the
168    #   executable passed as our first arg,
169    # - [no e] child should inherit this process's environment.
170    debug("Running %s...", " ".join(args))
171    rc = os.spawnv(os.P_WAIT, args[0], args)
172    debug("%s returned %s", args[0], rc)
173    return rc
174
175# ****************************************************************************
176#   test code -- manual at this point, see SWAT-564
177# ****************************************************************************
178def test_freeport():
179    # ------------------------------- Helpers --------------------------------
180    from contextlib import contextmanager
181    # helper Context Manager for expecting an exception
182    # with exc(SomeError):
183    #     raise SomeError()
184    # raises AssertionError otherwise.
185    @contextmanager
186    def exc(exception_class, *args):
187        try:
188            yield
189        except exception_class, err:
190            for i, expected_arg in enumerate(args):
191                assert expected_arg == err.args[i], \
192                       "Raised %s, but args[%s] is %r instead of %r" % \
193                       (err.__class__.__name__, i, err.args[i], expected_arg)
194            print "Caught expected exception %s(%s)" % \
195                  (err.__class__.__name__, ', '.join(repr(arg) for arg in err.args))
196        else:
197            assert False, "Failed to raise " + exception_class.__class__.__name__
198
199    # helper to raise specified exception
200    def raiser(exception):
201        raise exception
202
203    # the usual
204    def assert_equals(a, b):
205        assert a == b, "%r != %r" % (a, b)
206
207    # ------------------------ Sanity check the above ------------------------
208    class SomeError(Exception): pass
209    # Without extra args, accept any err.args value
210    with exc(SomeError):
211        raiser(SomeError("abc"))
212    # With extra args, accept only the specified value
213    with exc(SomeError, "abc"):
214        raiser(SomeError("abc"))
215    with exc(AssertionError):
216        with exc(SomeError, "abc"):
217            raiser(SomeError("def"))
218    with exc(AssertionError):
219        with exc(socket.error, errno.EADDRINUSE):
220            raiser(socket.error(errno.ECONNREFUSED, 'Connection refused'))
221
222    # ----------- freeport() without engaging socket functionality -----------
223    # If portlist is empty, freeport() raises StopIteration.
224    with exc(StopIteration):
225        freeport([], None)
226
227    assert_equals(freeport([17], str), ("17", 17))
228
229    # This is the magic exception that should prompt us to retry
230    inuse = socket.error(errno.EADDRINUSE, 'Address already in use')
231    # Get the iterator to our ports list so we can check later if we've used all
232    ports = iter(xrange(5))
233    with exc(socket.error, errno.EADDRINUSE):
234        freeport(ports, lambda port: raiser(inuse))
235    # did we entirely exhaust 'ports'?
236    with exc(StopIteration):
237        ports.next()
238
239    ports = iter(xrange(2))
240    # Any exception but EADDRINUSE should quit immediately
241    with exc(SomeError):
242        freeport(ports, lambda port: raiser(SomeError()))
243    assert_equals(ports.next(), 1)
244
245    # ----------- freeport() with platform-dependent socket stuff ------------
246    # This is what we should've had unit tests to begin with (see CHOP-661).
247    def newbind(port):
248        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
249        sock.bind(('127.0.0.1', port))
250        return sock
251
252    bound0, port0 = freeport(xrange(7777, 7780), newbind)
253    assert_equals(port0, 7777)
254    bound1, port1 = freeport(xrange(7777, 7780), newbind)
255    assert_equals(port1, 7778)
256    bound2, port2 = freeport(xrange(7777, 7780), newbind)
257    assert_equals(port2, 7779)
258    with exc(socket.error, errno.EADDRINUSE):
259        bound3, port3 = freeport(xrange(7777, 7780), newbind)
260
261if __name__ == "__main__":
262    test_freeport()