diff options
Diffstat (limited to 'indra/llmessage/tests')
| -rw-r--r-- | indra/llmessage/tests/commtest.h | 20 | ||||
| -rw-r--r-- | indra/llmessage/tests/llsdmessage_test.cpp | 1 | ||||
| -rw-r--r-- | indra/llmessage/tests/test_llsdmessage_peer.py | 26 | ||||
| -rw-r--r-- | indra/llmessage/tests/testrunner.py | 81 | 
4 files changed, 119 insertions, 9 deletions
| diff --git a/indra/llmessage/tests/commtest.h b/indra/llmessage/tests/commtest.h index 32035783e2..0fef596df2 100644 --- a/indra/llmessage/tests/commtest.h +++ b/indra/llmessage/tests/commtest.h @@ -35,6 +35,13 @@  #include "llhost.h"  #include "stringize.h"  #include <string> +#include <stdexcept> +#include <boost/lexical_cast.hpp> + +struct CommtestError: public std::runtime_error +{ +    CommtestError(const std::string& what): std::runtime_error(what) {} +};  /**   * This struct is shared by a couple of standalone comm tests (ADD_COMM_BUILD_TEST). @@ -55,13 +62,24 @@ struct commtest_data          replyPump("reply"),          errorPump("error"),          success(false), -        host("127.0.0.1", 8000), +        host("127.0.0.1", getport("PORT")),          server(STRINGIZE("http://" << host.getString() << "/"))      {          replyPump.listen("self", boost::bind(&commtest_data::outcome, this, _1, true));          errorPump.listen("self", boost::bind(&commtest_data::outcome, this, _1, false));      } +    static int getport(const std::string& var) +    { +        const char* port = getenv(var.c_str()); +        if (! port) +        { +            throw CommtestError("missing $PORT environment variable"); +        } +        // This will throw, too, if the value of PORT isn't numeric. +        return boost::lexical_cast<int>(port); +    } +      bool outcome(const LLSD& _result, bool _success)      {  //      std::cout << "commtest_data::outcome(" << _result << ", " << _success << ")\n"; diff --git a/indra/llmessage/tests/llsdmessage_test.cpp b/indra/llmessage/tests/llsdmessage_test.cpp index 9998a1b8bb..0f2c069303 100644 --- a/indra/llmessage/tests/llsdmessage_test.cpp +++ b/indra/llmessage/tests/llsdmessage_test.cpp @@ -61,6 +61,7 @@ namespace tut          llsdmessage_data():              httpPump(pumps.obtain("LLHTTPClient"))          { +            LLCurl::initClass();              LLSDMessage::link();          }      }; diff --git a/indra/llmessage/tests/test_llsdmessage_peer.py b/indra/llmessage/tests/test_llsdmessage_peer.py index 580ee7f8b4..cea5032111 100644 --- a/indra/llmessage/tests/test_llsdmessage_peer.py +++ b/indra/llmessage/tests/test_llsdmessage_peer.py @@ -38,7 +38,7 @@ mydir = os.path.dirname(__file__)       # expected to be .../indra/llmessage/tes  sys.path.insert(0, os.path.join(mydir, os.pardir, os.pardir, "lib", "python"))  from indra.util.fastest_elementtree import parse as xml_parse  from indra.base import llsd -from testrunner import run, debug +from testrunner import freeport, run, debug  class TestHTTPRequestHandler(BaseHTTPRequestHandler):      """This subclass of BaseHTTPRequestHandler is to receive and echo @@ -97,6 +97,10 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):              self.wfile.write(response)          else:                           # fail requested              status = data.get("status", 500) +            # self.responses maps an int status to a (short, long) pair of +            # strings. We want the longer string. That's why we pass a string +            # pair to get(): the [1] will select the second string, whether it +            # came from self.responses or from our default pair.              reason = data.get("reason",                                 self.responses.get(status,                                                    ("fail requested", @@ -113,11 +117,17 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):          # Suppress error output as well          pass -class TestHTTPServer(Thread): -    def run(self): -        httpd = HTTPServer(('127.0.0.1', 8000), TestHTTPRequestHandler) -        debug("Starting HTTP server...\n") -        httpd.serve_forever() -  if __name__ == "__main__": -    sys.exit(run(server=TestHTTPServer(name="httpd"), *sys.argv[1:])) +    # Instantiate an HTTPServer(TestHTTPRequestHandler) on the first free port +    # in the specified port range. Doing this inline is better than in a +    # daemon thread: if it blows up here, we'll get a traceback. If it blew up +    # in some other thread, the traceback would get eaten and we'd run the +    # subject test program anyway. +    httpd, port = freeport(xrange(8000, 8020), +                           lambda port: HTTPServer(('127.0.0.1', port), TestHTTPRequestHandler)) +    # Pass the selected port number to the subject test program via the +    # environment. We don't want to impose requirements on the test program's +    # command-line parsing -- and anyway, for C++ integration tests, that's +    # performed in TUT code rather than our own. +    os.environ["PORT"] = str(port) +    sys.exit(run(server=Thread(name="httpd", target=httpd.serve_forever), *sys.argv[1:])) 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. | 
