Skip to content
Snippets Groups Projects
Commit 0b5f662c authored by Oz Linden's avatar Oz Linden
Browse files

storm-1249 and chop-661

parents 1e6d1879 8e8eb76e
No related branches found
No related tags found
No related merge requests found
......@@ -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 @@
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 @@ def answer(self, data):
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 @@ def log_error(self, format, *args):
# 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 @@
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 @@
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 @@ def log_error(self, format, *args):
# 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:]))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment