storm-1249 and chop-661
commit
0b5f662c3b
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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:]))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -40,8 +40,10 @@
|
|||
#include "llevents.h"
|
||||
#include "lleventfilter.h"
|
||||
#include "llsd.h"
|
||||
#include "llhost.h"
|
||||
#include "llcontrol.h"
|
||||
#include "tests/wrapllerrs.h"
|
||||
#include "tests/commtest.h"
|
||||
|
||||
LLControlGroup gSavedSettings("Global");
|
||||
|
||||
|
|
@ -54,7 +56,8 @@ namespace tut
|
|||
{
|
||||
data():
|
||||
pumps(LLEventPumps::instance()),
|
||||
uri("http://127.0.0.1:8000")
|
||||
uri(std::string("http://") +
|
||||
LLHost("127.0.0.1", commtest_data::getport("PORT")).getString())
|
||||
{
|
||||
// These variables are required by machinery used by
|
||||
// LLXMLRPCTransaction. The values reflect reality for this test
|
||||
|
|
@ -145,7 +148,7 @@ namespace tut
|
|||
pumps.obtain("LLXMLRPCTransaction").post(request);
|
||||
// Set the timer
|
||||
F32 timeout(10);
|
||||
watchdog.eventAfter(timeout, LLSD().insert("timeout", 0));
|
||||
watchdog.eventAfter(timeout, LLSD().with("timeout", 0));
|
||||
// and pump "mainloop" until we get something, whether from
|
||||
// LLXMLRPCListener or from the watchdog filter.
|
||||
LLTimer timer;
|
||||
|
|
@ -182,7 +185,7 @@ namespace tut
|
|||
pumps.obtain("LLXMLRPCTransaction").post(request);
|
||||
// Set the timer
|
||||
F32 timeout(10);
|
||||
watchdog.eventAfter(timeout, LLSD().insert("timeout", 0));
|
||||
watchdog.eventAfter(timeout, LLSD().with("timeout", 0));
|
||||
// and pump "mainloop" until we get something, whether from
|
||||
// LLXMLRPCListener or from the watchdog filter.
|
||||
LLTimer timer;
|
||||
|
|
@ -218,7 +221,7 @@ namespace tut
|
|||
pumps.obtain("LLXMLRPCTransaction").post(request);
|
||||
// Set the timer
|
||||
F32 timeout(10);
|
||||
watchdog.eventAfter(timeout, LLSD().insert("timeout", 0));
|
||||
watchdog.eventAfter(timeout, LLSD().with("timeout", 0));
|
||||
// and pump "mainloop" until we get something, whether from
|
||||
// LLXMLRPCListener or from the watchdog filter.
|
||||
LLTimer timer;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ from SimpleXMLRPCServer import SimpleXMLRPCServer
|
|||
mydir = os.path.dirname(__file__) # expected to be .../indra/newview/tests/
|
||||
sys.path.insert(0, os.path.join(mydir, os.pardir, os.pardir, "lib", "python"))
|
||||
sys.path.insert(1, os.path.join(mydir, os.pardir, os.pardir, "llmessage", "tests"))
|
||||
from testrunner import run, debug
|
||||
from testrunner import freeport, run, debug
|
||||
|
||||
class TestServer(SimpleXMLRPCServer):
|
||||
def _dispatch(self, method, params):
|
||||
|
|
@ -66,11 +66,16 @@ class TestServer(SimpleXMLRPCServer):
|
|||
# Suppress error output as well
|
||||
pass
|
||||
|
||||
class ServerRunner(Thread):
|
||||
def run(self):
|
||||
server = TestServer(('127.0.0.1', 8000))
|
||||
debug("Starting XMLRPC server...\n")
|
||||
server.serve_forever()
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run(server=ServerRunner(name="xmlrpc"), *sys.argv[1:]))
|
||||
# Instantiate a TestServer 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.
|
||||
xmlrpcd, port = freeport(xrange(8000, 8020),
|
||||
lambda port: TestServer(('127.0.0.1', port)))
|
||||
# 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="xmlrpc", target=xmlrpcd.serve_forever), *sys.argv[1:]))
|
||||
|
|
|
|||
Loading…
Reference in New Issue