summaryrefslogtreecommitdiff
path: root/indra/llmessage
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llmessage')
-rw-r--r--indra/llmessage/llcurl.cpp14
-rw-r--r--indra/llmessage/llcurl.h4
-rw-r--r--indra/llmessage/lldatapacker.h5
-rw-r--r--indra/llmessage/lliosocket.cpp26
-rw-r--r--indra/llmessage/lliosocket.h11
-rw-r--r--indra/llmessage/message_prehash.cpp4
-rw-r--r--indra/llmessage/message_prehash.h4
-rw-r--r--indra/llmessage/tests/commtest.h64
-rw-r--r--indra/llmessage/tests/test_llsdmessage_peer.py41
-rw-r--r--indra/llmessage/tests/testrunner.py201
10 files changed, 292 insertions, 82 deletions
diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp
index 7c8b7e3584..44330cf8ff 100644
--- a/indra/llmessage/llcurl.cpp
+++ b/indra/llmessage/llcurl.cpp
@@ -244,7 +244,7 @@ public:
U32 report(CURLcode);
void getTransferInfo(LLCurl::TransferInfo* info);
- void prepRequest(const std::string& url, const std::vector<std::string>& headers, ResponderPtr, bool post = false);
+ void prepRequest(const std::string& url, const std::vector<std::string>& headers, ResponderPtr, S32 time_out = 0, bool post = false);
const char* getErrorBuffer();
@@ -525,7 +525,7 @@ size_t curlHeaderCallback(void* data, size_t size, size_t nmemb, void* user_data
void LLCurl::Easy::prepRequest(const std::string& url,
const std::vector<std::string>& headers,
- ResponderPtr responder, bool post)
+ ResponderPtr responder, S32 time_out, bool post)
{
resetState();
@@ -558,7 +558,7 @@ void LLCurl::Easy::prepRequest(const std::string& url,
//don't verify host name so urls with scrubbed host names will work (improves DNS performance)
setopt(CURLOPT_SSL_VERIFYHOST, 0);
- setopt(CURLOPT_TIMEOUT, CURL_REQUEST_TIMEOUT);
+ setopt(CURLOPT_TIMEOUT, llmax(time_out, CURL_REQUEST_TIMEOUT));
setoptString(CURLOPT_URL, url);
@@ -855,14 +855,14 @@ bool LLCurlRequest::getByteRange(const std::string& url,
bool LLCurlRequest::post(const std::string& url,
const headers_t& headers,
const LLSD& data,
- LLCurl::ResponderPtr responder)
+ LLCurl::ResponderPtr responder, S32 time_out)
{
LLCurl::Easy* easy = allocEasy();
if (!easy)
{
return false;
}
- easy->prepRequest(url, headers, responder);
+ easy->prepRequest(url, headers, responder, time_out);
LLSDSerialize::toXML(data, easy->getInput());
S32 bytes = easy->getInput().str().length();
@@ -882,14 +882,14 @@ bool LLCurlRequest::post(const std::string& url,
bool LLCurlRequest::post(const std::string& url,
const headers_t& headers,
const std::string& data,
- LLCurl::ResponderPtr responder)
+ LLCurl::ResponderPtr responder, S32 time_out)
{
LLCurl::Easy* easy = allocEasy();
if (!easy)
{
return false;
}
- easy->prepRequest(url, headers, responder);
+ easy->prepRequest(url, headers, responder, time_out);
easy->getInput().write(data.data(), data.size());
S32 bytes = easy->getInput().str().length();
diff --git a/indra/llmessage/llcurl.h b/indra/llmessage/llcurl.h
index 4ce3fa1078..5833a27b84 100644
--- a/indra/llmessage/llcurl.h
+++ b/indra/llmessage/llcurl.h
@@ -201,8 +201,8 @@ public:
void get(const std::string& url, LLCurl::ResponderPtr responder);
bool getByteRange(const std::string& url, const headers_t& headers, S32 offset, S32 length, LLCurl::ResponderPtr responder);
- bool post(const std::string& url, const headers_t& headers, const LLSD& data, LLCurl::ResponderPtr responder);
- bool post(const std::string& url, const headers_t& headers, const std::string& data, LLCurl::ResponderPtr responder);
+ bool post(const std::string& url, const headers_t& headers, const LLSD& data, LLCurl::ResponderPtr responder, S32 time_out = 0);
+ bool post(const std::string& url, const headers_t& headers, const std::string& data, LLCurl::ResponderPtr responder, S32 time_out = 0);
S32 process();
S32 getQueued();
diff --git a/indra/llmessage/lldatapacker.h b/indra/llmessage/lldatapacker.h
index dd9c4eaa38..b0a638c16e 100644
--- a/indra/llmessage/lldatapacker.h
+++ b/indra/llmessage/lldatapacker.h
@@ -168,10 +168,15 @@ public:
S32 getCurrentSize() const { return (S32)(mCurBufferp - mBufferp); }
S32 getBufferSize() const { return mBufferSize; }
+ const U8* getBuffer() const { return mBufferp; }
void reset() { mCurBufferp = mBufferp; mWriteEnabled = (mCurBufferp != NULL); }
void freeBuffer() { delete [] mBufferp; mBufferp = mCurBufferp = NULL; mBufferSize = 0; mWriteEnabled = FALSE; }
void assignBuffer(U8 *bufferp, S32 size)
{
+ if(mBufferp && mBufferp != bufferp)
+ {
+ freeBuffer() ;
+ }
mBufferp = bufferp;
mCurBufferp = bufferp;
mBufferSize = size;
diff --git a/indra/llmessage/lliosocket.cpp b/indra/llmessage/lliosocket.cpp
index ca84fa8bb8..8c752fbe30 100644
--- a/indra/llmessage/lliosocket.cpp
+++ b/indra/llmessage/lliosocket.cpp
@@ -191,7 +191,7 @@ LLSocket::ptr_t LLSocket::create(apr_pool_t* pool, EType type, U16 port)
port = PORT_EPHEMERAL;
}
rv->mPort = port;
- rv->setOptions();
+ rv->setNonBlocking();
return rv;
}
@@ -206,7 +206,7 @@ LLSocket::ptr_t LLSocket::create(apr_socket_t* socket, apr_pool_t* pool)
}
rv = ptr_t(new LLSocket(socket, pool));
rv->mPort = PORT_EPHEMERAL;
- rv->setOptions();
+ rv->setNonBlocking();
return rv;
}
@@ -227,10 +227,10 @@ bool LLSocket::blockingConnect(const LLHost& host)
{
return false;
}
- apr_socket_timeout_set(mSocket, 1000);
+ setBlocking(1000);
ll_debug_socket("Blocking connect", mSocket);
if(ll_apr_warn_status(apr_socket_connect(mSocket, sa))) return false;
- setOptions();
+ setNonBlocking();
return true;
}
@@ -258,11 +258,27 @@ LLSocket::~LLSocket()
}
}
-void LLSocket::setOptions()
+// See http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-13.html#ss13.4
+// for an explanation of how to get non-blocking sockets and timeouts with
+// consistent behavior across platforms.
+
+void LLSocket::setBlocking(S32 timeout)
+{
+ LLMemType m1(LLMemType::MTYPE_IO_TCP);
+ // set up the socket options
+ ll_apr_warn_status(apr_socket_timeout_set(mSocket, timeout));
+ ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_NONBLOCK, 0));
+ ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_SNDBUF, LL_SEND_BUFFER_SIZE));
+ ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_RCVBUF, LL_RECV_BUFFER_SIZE));
+
+}
+
+void LLSocket::setNonBlocking()
{
LLMemType m1(LLMemType::MTYPE_IO_TCP);
// set up the socket options
ll_apr_warn_status(apr_socket_timeout_set(mSocket, 0));
+ ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_NONBLOCK, 1));
ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_SNDBUF, LL_SEND_BUFFER_SIZE));
ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_RCVBUF, LL_RECV_BUFFER_SIZE));
diff --git a/indra/llmessage/lliosocket.h b/indra/llmessage/lliosocket.h
index 6806e5084a..e0f6c1e34d 100644
--- a/indra/llmessage/lliosocket.h
+++ b/indra/llmessage/lliosocket.h
@@ -153,9 +153,16 @@ protected:
LLSocket(apr_socket_t* socket, apr_pool_t* pool);
/**
- * @brief Set default socket options.
+ * @brief Set default socket options, with SO_NONBLOCK = 0 and a timeout in us.
+ * @param timeout Number of microseconds to wait on this socket. Any
+ * negative number means block-forever. TIMEOUT OF 0 IS NON-PORTABLE.
*/
- void setOptions();
+ void setBlocking(S32 timeout);
+
+ /**
+ * @brief Set default socket options, with SO_NONBLOCK = 1 and timeout = 0.
+ */
+ void setNonBlocking();
public:
/**
diff --git a/indra/llmessage/message_prehash.cpp b/indra/llmessage/message_prehash.cpp
index 5d03615e53..e71fb96540 100644
--- a/indra/llmessage/message_prehash.cpp
+++ b/indra/llmessage/message_prehash.cpp
@@ -742,6 +742,7 @@ char const* const _PREHASH_MoneyData = LLMessageStringTable::getInstance()->getS
char const* const _PREHASH_ObjectDeselect = LLMessageStringTable::getInstance()->getString("ObjectDeselect");
char const* const _PREHASH_NewAssetID = LLMessageStringTable::getInstance()->getString("NewAssetID");
char const* const _PREHASH_ObjectAdd = LLMessageStringTable::getInstance()->getString("ObjectAdd");
+char const* const _PREHASH_SimulatorFeatures = LLMessageStringTable::getInstance()->getString("SimulatorFeatures");
char const* const _PREHASH_RayEndIsIntersection = LLMessageStringTable::getInstance()->getString("RayEndIsIntersection");
char const* const _PREHASH_CompleteAuction = LLMessageStringTable::getInstance()->getString("CompleteAuction");
char const* const _PREHASH_CircuitCode = LLMessageStringTable::getInstance()->getString("CircuitCode");
@@ -1375,3 +1376,6 @@ char const* const _PREHASH_VCoord = LLMessageStringTable::getInstance()->getStri
char const* const _PREHASH_FaceIndex = LLMessageStringTable::getInstance()->getString("FaceIndex");
char const* const _PREHASH_StatusData = LLMessageStringTable::getInstance()->getString("StatusData");
char const* const _PREHASH_ProductSKU = LLMessageStringTable::getInstance()->getString("ProductSKU");
+char const* const _PREHASH_SeeAVs = LLMessageStringTable::getInstance()->getString("SeeAVs");
+char const* const _PREHASH_AnyAVSounds = LLMessageStringTable::getInstance()->getString("AnyAVSounds");
+char const* const _PREHASH_GroupAVSounds = LLMessageStringTable::getInstance()->getString("GroupAVSounds");
diff --git a/indra/llmessage/message_prehash.h b/indra/llmessage/message_prehash.h
index 8dc86601e6..dd2c2dbd64 100644
--- a/indra/llmessage/message_prehash.h
+++ b/indra/llmessage/message_prehash.h
@@ -742,6 +742,7 @@ extern char const* const _PREHASH_MoneyData;
extern char const* const _PREHASH_ObjectDeselect;
extern char const* const _PREHASH_NewAssetID;
extern char const* const _PREHASH_ObjectAdd;
+extern char const* const _PREHASH_SimulatorFeatures;
extern char const* const _PREHASH_RayEndIsIntersection;
extern char const* const _PREHASH_CompleteAuction;
extern char const* const _PREHASH_CircuitCode;
@@ -1375,4 +1376,7 @@ extern char const* const _PREHASH_VCoord;
extern char const* const _PREHASH_FaceIndex;
extern char const* const _PREHASH_StatusData;
extern char const* const _PREHASH_ProductSKU;
+extern char const* const _PREHASH_SeeAVs;
+extern char const* const _PREHASH_AnyAVSounds;
+extern char const* const _PREHASH_GroupAVSounds;
#endif
diff --git a/indra/llmessage/tests/commtest.h b/indra/llmessage/tests/commtest.h
index 0fef596df2..0d149b5258 100644
--- a/indra/llmessage/tests/commtest.h
+++ b/indra/llmessage/tests/commtest.h
@@ -34,6 +34,7 @@
#include "llsd.h"
#include "llhost.h"
#include "stringize.h"
+#include <map>
#include <string>
#include <stdexcept>
#include <boost/lexical_cast.hpp>
@@ -43,6 +44,58 @@ struct CommtestError: public std::runtime_error
CommtestError(const std::string& what): std::runtime_error(what) {}
};
+static bool query_verbose()
+{
+ const char* cbose = getenv("INTEGRATION_TEST_VERBOSE");
+ if (! cbose)
+ {
+ cbose = "1";
+ }
+ std::string strbose(cbose);
+ return (! (strbose == "0" || strbose == "off" ||
+ strbose == "false" || strbose == "quiet"));
+}
+
+bool verbose()
+{
+ // This should only be initialized once.
+ static bool vflag = query_verbose();
+ return vflag;
+}
+
+static int query_port(const std::string& var)
+{
+ const char* cport = getenv(var.c_str());
+ if (! cport)
+ {
+ throw CommtestError(STRINGIZE("missing environment variable" << var));
+ }
+ // This will throw, too, if the value of PORT isn't numeric.
+ int port(boost::lexical_cast<int>(cport));
+ if (verbose())
+ {
+ std::cout << "getport('" << var << "') = " << port << std::endl;
+ }
+ return port;
+}
+
+static int getport(const std::string& var)
+{
+ typedef std::map<std::string, int> portsmap;
+ static portsmap ports;
+ // We can do this with a single map lookup with map::insert(). Either it
+ // returns an existing entry and 'false' (not newly inserted), or it
+ // inserts the specified value and 'true'.
+ std::pair<portsmap::iterator, bool> inserted(ports.insert(portsmap::value_type(var, 0)));
+ if (inserted.second)
+ {
+ // We haven't yet seen this var. Remember its value.
+ inserted.first->second = query_port(var);
+ }
+ // Return the (existing or new) iterator's value.
+ return inserted.first->second;
+}
+
/**
* This struct is shared by a couple of standalone comm tests (ADD_COMM_BUILD_TEST).
*/
@@ -71,13 +124,10 @@ struct commtest_data
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);
+ // We have a couple consumers of commtest_data::getport(). But we've
+ // since moved it out to the global namespace. So this is just a
+ // facade.
+ return ::getport(var);
}
bool outcome(const LLSD& _result, bool _success)
diff --git a/indra/llmessage/tests/test_llsdmessage_peer.py b/indra/llmessage/tests/test_llsdmessage_peer.py
index cea5032111..22edd9dad8 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 freeport, run, debug
+from testrunner import freeport, run, debug, VERBOSE
class TestHTTPRequestHandler(BaseHTTPRequestHandler):
"""This subclass of BaseHTTPRequestHandler is to receive and echo
@@ -72,10 +72,10 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):
## # assuming that the underlying XML parser reads its input file
## # incrementally. Unfortunately I haven't been able to make it work.
## tree = xml_parse(self.rfile)
-## debug("Finished raw parse\n")
-## debug("parsed XML tree %s\n" % tree)
-## debug("parsed root node %s\n" % tree.getroot())
-## debug("root node tag %s\n" % tree.getroot().tag)
+## debug("Finished raw parse")
+## debug("parsed XML tree %s", tree)
+## debug("parsed root node %s", tree.getroot())
+## debug("root node tag %s", tree.getroot().tag)
## return llsd.to_python(tree.getroot())
def do_GET(self):
@@ -88,8 +88,10 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):
self.answer(self.read_xml())
def answer(self, data):
+ debug("%s.answer(%s): self.path = %r", self.__class__.__name__, data, self.path)
if "fail" not in self.path:
response = llsd.format_xml(data.get("reply", llsd.LLSD("success")))
+ debug("success: %s", response)
self.send_response(200)
self.send_header("Content-type", "application/llsd+xml")
self.send_header("Content-Length", str(len(response)))
@@ -106,28 +108,39 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):
("fail requested",
"Your request specified failure status %s "
"without providing a reason" % status))[1])
+ debug("fail requested: %s: %r", status, reason)
self.send_error(status, reason)
- def log_request(self, code, size=None):
- # For present purposes, we don't want the request splattered onto
- # stderr, as it would upset devs watching the test run
- pass
+ if not VERBOSE:
+ # When VERBOSE is set, skip both these overrides because they exist to
+ # suppress output.
- def log_error(self, format, *args):
- # Suppress error output as well
- pass
+ def log_request(self, code, size=None):
+ # For present purposes, we don't want the request splattered onto
+ # stderr, as it would upset devs watching the test run
+ pass
+
+ def log_error(self, format, *args):
+ # Suppress error output as well
+ pass
+
+class Server(HTTPServer):
+ # This pernicious flag is on by default in HTTPServer. But proper
+ # operation of freeport() absolutely depends on it being off.
+ allow_reuse_address = False
if __name__ == "__main__":
- # Instantiate an HTTPServer(TestHTTPRequestHandler) on the first free port
+ # Instantiate a Server(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))
+ lambda port: Server(('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)
+ debug("$PORT = %s", 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 8ff13e0426..f2c841532a 100644
--- a/indra/llmessage/tests/testrunner.py
+++ b/indra/llmessage/tests/testrunner.py
@@ -27,16 +27,25 @@ Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
+from __future__ import with_statement
+
import os
import sys
+import re
import errno
import socket
-def debug(*args):
- sys.stdout.writelines(args)
- sys.stdout.flush()
-# comment out the line below to enable debug output
-debug = lambda *args: None
+VERBOSE = os.environ.get("INTEGRATION_TEST_VERBOSE", "1") # default to verbose
+# Support usage such as INTEGRATION_TEST_VERBOSE=off -- distressing to user if
+# that construct actually turns on verbosity...
+VERBOSE = not re.match(r"(0|off|false|quiet)$", VERBOSE, re.IGNORECASE)
+
+if VERBOSE:
+ def debug(fmt, *args):
+ print fmt % args
+ sys.stdout.flush()
+else:
+ debug = lambda *args: None
def freeport(portlist, expr):
"""
@@ -72,50 +81,64 @@ def freeport(portlist, expr):
Example:
+ class Server(HTTPServer):
+ # If you use BaseHTTPServer.HTTPServer, turning off this flag is
+ # essential for proper operation of freeport()!
+ allow_reuse_address = False
+ # ...
server, port = freeport(xrange(8000, 8010),
- lambda port: HTTPServer(("localhost", port),
- MyRequestHandler))
+ lambda port: Server(("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()
+ try:
+ # 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()
+ while True:
try:
+ # If this value of port works, return as promised.
+ value = expr(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:
- 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).
+ try:
+ port = portiter.next()
+ except StopIteration:
+ raise type, value, tb
+ finally:
+ # Clean up local traceback, see docs for sys.exc_info()
+ del tb
+
+ else:
+ debug("freeport() returning %s on port %s", value, port)
+ return value, port
+
+ # 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).
+
+ except Exception, err:
+ debug("*** freeport() raising %s: %s", err.__class__.__name__, err)
+ raise
def run(*args, **kwds):
"""All positional arguments collectively form a command line, executed as
@@ -144,8 +167,96 @@ def run(*args, **kwds):
# - [no p] don't use the PATH because we specifically want to invoke the
# executable passed as our first arg,
# - [no e] child should inherit this process's environment.
- debug("Running %s...\n" % (" ".join(args)))
- sys.stdout.flush()
+ debug("Running %s...", " ".join(args))
rc = os.spawnv(os.P_WAIT, args[0], args)
- debug("%s returned %s\n" % (args[0], rc))
+ debug("%s returned %s", args[0], rc)
return rc
+
+# ****************************************************************************
+# test code -- manual at this point, see SWAT-564
+# ****************************************************************************
+def test_freeport():
+ # ------------------------------- Helpers --------------------------------
+ from contextlib import contextmanager
+ # helper Context Manager for expecting an exception
+ # with exc(SomeError):
+ # raise SomeError()
+ # raises AssertionError otherwise.
+ @contextmanager
+ def exc(exception_class, *args):
+ try:
+ yield
+ except exception_class, err:
+ for i, expected_arg in enumerate(args):
+ assert expected_arg == err.args[i], \
+ "Raised %s, but args[%s] is %r instead of %r" % \
+ (err.__class__.__name__, i, err.args[i], expected_arg)
+ print "Caught expected exception %s(%s)" % \
+ (err.__class__.__name__, ', '.join(repr(arg) for arg in err.args))
+ else:
+ assert False, "Failed to raise " + exception_class.__class__.__name__
+
+ # helper to raise specified exception
+ def raiser(exception):
+ raise exception
+
+ # the usual
+ def assert_equals(a, b):
+ assert a == b, "%r != %r" % (a, b)
+
+ # ------------------------ Sanity check the above ------------------------
+ class SomeError(Exception): pass
+ # Without extra args, accept any err.args value
+ with exc(SomeError):
+ raiser(SomeError("abc"))
+ # With extra args, accept only the specified value
+ with exc(SomeError, "abc"):
+ raiser(SomeError("abc"))
+ with exc(AssertionError):
+ with exc(SomeError, "abc"):
+ raiser(SomeError("def"))
+ with exc(AssertionError):
+ with exc(socket.error, errno.EADDRINUSE):
+ raiser(socket.error(errno.ECONNREFUSED, 'Connection refused'))
+
+ # ----------- freeport() without engaging socket functionality -----------
+ # If portlist is empty, freeport() raises StopIteration.
+ with exc(StopIteration):
+ freeport([], None)
+
+ assert_equals(freeport([17], str), ("17", 17))
+
+ # This is the magic exception that should prompt us to retry
+ inuse = socket.error(errno.EADDRINUSE, 'Address already in use')
+ # Get the iterator to our ports list so we can check later if we've used all
+ ports = iter(xrange(5))
+ with exc(socket.error, errno.EADDRINUSE):
+ freeport(ports, lambda port: raiser(inuse))
+ # did we entirely exhaust 'ports'?
+ with exc(StopIteration):
+ ports.next()
+
+ ports = iter(xrange(2))
+ # Any exception but EADDRINUSE should quit immediately
+ with exc(SomeError):
+ freeport(ports, lambda port: raiser(SomeError()))
+ assert_equals(ports.next(), 1)
+
+ # ----------- freeport() with platform-dependent socket stuff ------------
+ # This is what we should've had unit tests to begin with (see CHOP-661).
+ def newbind(port):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.bind(('127.0.0.1', port))
+ return sock
+
+ bound0, port0 = freeport(xrange(7777, 7780), newbind)
+ assert_equals(port0, 7777)
+ bound1, port1 = freeport(xrange(7777, 7780), newbind)
+ assert_equals(port1, 7778)
+ bound2, port2 = freeport(xrange(7777, 7780), newbind)
+ assert_equals(port2, 7779)
+ with exc(socket.error, errno.EADDRINUSE):
+ bound3, port3 = freeport(xrange(7777, 7780), newbind)
+
+if __name__ == "__main__":
+ test_freeport()