Bring in the testrunner/http server scaffold for better integration testing.
This brings in a copy of llmessage's llsdmessage testing server. We run a mocked HTTP service to handle requests and the integration tests run against it by picking up the LL_TEST_PORT environment variable when running. Add some checks and output to produce useful info when run in the wrong environment and when bad status is received. Later will add a dead port as well so we can test that rather than use 'localhost:2'.meow-7.2.2
parent
267ab5b417
commit
75242eab8f
|
|
@ -125,6 +125,8 @@ if (LL_TESTS)
|
|||
LL_ADD_INTEGRATION_TEST(llcorehttp
|
||||
"${llcorehttp_TEST_SOURCE_FILES}"
|
||||
"${test_libs}"
|
||||
${PYTHON_EXECUTABLE}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llcorehttp_peer.py"
|
||||
)
|
||||
|
||||
endif (LL_TESTS)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include "llcorehttp_test.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
// These are not the right way in viewer for some reason:
|
||||
// #include <tut/tut.hpp>
|
||||
|
|
@ -130,3 +131,38 @@ void ssl_locking_callback(int mode, int type, const char * /* file */, int /* li
|
|||
}
|
||||
|
||||
|
||||
std::string get_base_url()
|
||||
{
|
||||
const char * env(getenv("LL_TEST_PORT"));
|
||||
|
||||
if (! env)
|
||||
{
|
||||
std::cerr << "LL_TEST_PORT environment variable missing." << std::endl;
|
||||
std::cerr << "Test expects to run in test_llcorehttp_peer.py script." << std::endl;
|
||||
tut::ensure("LL_TEST_PORT set in environment", NULL != env);
|
||||
}
|
||||
|
||||
int port(atoi(env));
|
||||
std::ostringstream out;
|
||||
out << "http://localhost:" << port << "/";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
|
||||
void stop_thread(LLCore::HttpRequest * req)
|
||||
{
|
||||
if (req)
|
||||
{
|
||||
req->requestStopThread(NULL);
|
||||
|
||||
int count = 0;
|
||||
int limit = 10;
|
||||
while (count++ < limit && ! HttpService::isStopped())
|
||||
{
|
||||
req->update(1000);
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@
|
|||
|
||||
#include <curl/curl.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <string>
|
||||
|
||||
#include "httprequest.h"
|
||||
|
||||
// Initialization and cleanup for libcurl. Mainly provides
|
||||
// a mutex callback for SSL and a thread ID hash for libcurl.
|
||||
|
|
@ -40,6 +43,8 @@
|
|||
// operations.
|
||||
extern void init_curl();
|
||||
extern void term_curl();
|
||||
extern std::string get_base_url();
|
||||
extern void stop_thread(LLCore::HttpRequest * req);
|
||||
|
||||
class ScopedCurlInit
|
||||
{
|
||||
|
|
|
|||
|
|
@ -84,8 +84,10 @@ public:
|
|||
if (response && mState)
|
||||
{
|
||||
const HttpStatus actual_status(response->getStatus());
|
||||
|
||||
ensure("Expected HttpStatus received in response", actual_status == mState->mStatus);
|
||||
std::ostringstream test;
|
||||
test << "Expected HttpStatus received in response. Wanted: "
|
||||
<< mState->mStatus.toHex() << " Received: " << actual_status.toHex();
|
||||
ensure(test.str().c_str(), actual_status == mState->mStatus);
|
||||
}
|
||||
if (mState)
|
||||
{
|
||||
|
|
@ -184,6 +186,7 @@ void HttpRequestTestObjectType::test<2>()
|
|||
}
|
||||
catch (...)
|
||||
{
|
||||
stop_thread(req);
|
||||
delete req;
|
||||
HttpRequest::destroyService();
|
||||
throw;
|
||||
|
|
@ -275,6 +278,7 @@ void HttpRequestTestObjectType::test<3>()
|
|||
}
|
||||
catch (...)
|
||||
{
|
||||
stop_thread(req);
|
||||
delete req;
|
||||
HttpRequest::destroyService();
|
||||
throw;
|
||||
|
|
@ -377,6 +381,7 @@ void HttpRequestTestObjectType::test<4>()
|
|||
}
|
||||
catch (...)
|
||||
{
|
||||
stop_thread(req1);
|
||||
delete req1;
|
||||
delete req2;
|
||||
HttpRequest::destroyService();
|
||||
|
|
@ -483,6 +488,116 @@ void HttpRequestTestObjectType::test<5>()
|
|||
}
|
||||
catch (...)
|
||||
{
|
||||
stop_thread(req);
|
||||
delete req;
|
||||
HttpRequest::destroyService();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template <> template <>
|
||||
void HttpRequestTestObjectType::test<6>()
|
||||
{
|
||||
ScopedCurlInit ready;
|
||||
|
||||
std::string url_base(get_base_url());
|
||||
std::cerr << "Base: " << url_base << std::endl;
|
||||
|
||||
set_test_name("HttpRequest GET to real service");
|
||||
|
||||
// Handler can be stack-allocated *if* there are no dangling
|
||||
// references to it after completion of this method.
|
||||
// Create before memory record as the string copy will bump numbers.
|
||||
TestHandler2 handler(this, "handler");
|
||||
|
||||
// record the total amount of dynamically allocated memory
|
||||
mMemTotal = GetMemTotal();
|
||||
mHandlerCalls = 0;
|
||||
|
||||
HttpRequest * req = NULL;
|
||||
|
||||
try
|
||||
{
|
||||
// Get singletons created
|
||||
HttpRequest::createService();
|
||||
|
||||
// Start threading early so that thread memory is invariant
|
||||
// over the test.
|
||||
HttpRequest::startThread();
|
||||
|
||||
// create a new ref counted object with an implicit reference
|
||||
req = new HttpRequest();
|
||||
ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
|
||||
|
||||
// Issue a GET that *can* connect
|
||||
mStatus = HttpStatus(200);
|
||||
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base,
|
||||
0,
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
&handler);
|
||||
ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID);
|
||||
|
||||
// Run the notification pump.
|
||||
int count(0);
|
||||
int limit(10);
|
||||
while (count++ < limit && mHandlerCalls < 1)
|
||||
{
|
||||
req->update(1000);
|
||||
usleep(100000);
|
||||
}
|
||||
ensure("Request executed in reasonable time", count < limit);
|
||||
ensure("One handler invocation for request", mHandlerCalls == 1);
|
||||
|
||||
// Okay, request a shutdown of the servicing thread
|
||||
mStatus = HttpStatus();
|
||||
handle = req->requestStopThread(&handler);
|
||||
ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID);
|
||||
|
||||
// Run the notification pump again
|
||||
count = 0;
|
||||
limit = 10;
|
||||
while (count++ < limit && mHandlerCalls < 2)
|
||||
{
|
||||
req->update(1000);
|
||||
usleep(100000);
|
||||
}
|
||||
ensure("Second request executed in reasonable time", count < limit);
|
||||
ensure("Second handler invocation", mHandlerCalls == 2);
|
||||
|
||||
// See that we actually shutdown the thread
|
||||
count = 0;
|
||||
limit = 10;
|
||||
while (count++ < limit && ! HttpService::isStopped())
|
||||
{
|
||||
usleep(100000);
|
||||
}
|
||||
ensure("Thread actually stopped running", HttpService::isStopped());
|
||||
|
||||
// release the request object
|
||||
delete req;
|
||||
req = NULL;
|
||||
|
||||
// Shut down service
|
||||
HttpRequest::destroyService();
|
||||
|
||||
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
|
||||
|
||||
#if defined(WIN32)
|
||||
// Can only do this memory test on Windows. On other platforms,
|
||||
// the LL logging system holds on to memory and produces what looks
|
||||
// like memory leaks...
|
||||
|
||||
// printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
|
||||
ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
|
||||
#endif
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
stop_thread(req);
|
||||
delete req;
|
||||
HttpRequest::destroyService();
|
||||
throw;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
#!/usr/bin/env python
|
||||
"""\
|
||||
@file test_llsdmessage_peer.py
|
||||
@author Nat Goodspeed
|
||||
@date 2008-10-09
|
||||
@brief This script asynchronously runs the executable (with args) specified on
|
||||
the command line, returning its result code. While that executable is
|
||||
running, we provide dummy local services for use by C++ tests.
|
||||
|
||||
$LicenseInfo:firstyear=2008&license=viewerlgpl$
|
||||
Second Life Viewer Source Code
|
||||
Copyright (C) 2010, Linden Research, Inc.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation;
|
||||
version 2.1 of the License only.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
$/LicenseInfo$
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from threading import Thread
|
||||
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
mydir = os.path.dirname(__file__) # expected to be .../indra/llmessage/tests/
|
||||
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, VERBOSE
|
||||
|
||||
class TestHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
"""This subclass of BaseHTTPRequestHandler is to receive and echo
|
||||
LLSD-flavored messages sent by the C++ LLHTTPClient.
|
||||
"""
|
||||
def read(self):
|
||||
# The following logic is adapted from the library module
|
||||
# SimpleXMLRPCServer.py.
|
||||
# Get arguments by reading body of request.
|
||||
# We read this in chunks to avoid straining
|
||||
# socket.read(); around the 10 or 15Mb mark, some platforms
|
||||
# begin to have problems (bug #792570).
|
||||
try:
|
||||
size_remaining = int(self.headers["content-length"])
|
||||
except (KeyError, ValueError):
|
||||
return ""
|
||||
max_chunk_size = 10*1024*1024
|
||||
L = []
|
||||
while size_remaining:
|
||||
chunk_size = min(size_remaining, max_chunk_size)
|
||||
chunk = self.rfile.read(chunk_size)
|
||||
L.append(chunk)
|
||||
size_remaining -= len(chunk)
|
||||
return ''.join(L)
|
||||
# end of swiped read() logic
|
||||
|
||||
def read_xml(self):
|
||||
# This approach reads the entire POST data into memory first
|
||||
return llsd.parse(self.read())
|
||||
## # This approach attempts to stream in the LLSD XML from self.rfile,
|
||||
## # 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")
|
||||
## 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):
|
||||
# Of course, don't attempt to read data.
|
||||
self.answer(dict(reply="success", status=200,
|
||||
reason="Your GET operation worked"))
|
||||
|
||||
def do_POST(self):
|
||||
# Read the provided POST data.
|
||||
self.answer(self.read())
|
||||
|
||||
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)))
|
||||
self.end_headers()
|
||||
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",
|
||||
"Your request specified failure status %s "
|
||||
"without providing a reason" % status))[1])
|
||||
debug("fail requested: %s: %r", status, reason)
|
||||
self.send_error(status, reason)
|
||||
|
||||
if not VERBOSE:
|
||||
# When VERBOSE is set, skip both these overrides because they exist to
|
||||
# suppress output.
|
||||
|
||||
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 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: 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["LL_TEST_PORT"] = str(port)
|
||||
debug("$LL_TEST_PORT = %s", port)
|
||||
sys.exit(run(server=Thread(name="httpd", target=httpd.serve_forever), *sys.argv[1:]))
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
#!/usr/bin/env python
|
||||
"""\
|
||||
@file testrunner.py
|
||||
@author Nat Goodspeed
|
||||
@date 2009-03-20
|
||||
@brief Utilities for writing wrapper scripts for ADD_COMM_BUILD_TEST unit tests
|
||||
|
||||
$LicenseInfo:firstyear=2009&license=viewerlgpl$
|
||||
Second Life Viewer Source Code
|
||||
Copyright (C) 2010, Linden Research, Inc.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation;
|
||||
version 2.1 of the License only.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
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
|
||||
|
||||
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):
|
||||
"""
|
||||
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:
|
||||
|
||||
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: Server(("localhost", port),
|
||||
MyRequestHandler))
|
||||
# pass 'port' to client code
|
||||
# call server.serve_forever()
|
||||
"""
|
||||
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.
|
||||
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:
|
||||
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
|
||||
a synchronous child process.
|
||||
In addition, pass server=new_thread_instance as an explicit keyword (to
|
||||
differentiate it from an additional command-line argument).
|
||||
new_thread_instance should be an instantiated but not yet started Thread
|
||||
subclass instance, e.g.:
|
||||
run("python", "-c", 'print "Hello, world!"', server=TestHTTPServer(name="httpd"))
|
||||
"""
|
||||
# If there's no server= keyword arg, don't start a server thread: simply
|
||||
# run a child process.
|
||||
try:
|
||||
thread = kwds.pop("server")
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
# Start server thread. Note that this and all other comm server
|
||||
# threads should be daemon threads: we'll let them run "forever,"
|
||||
# confident that the whole process will terminate when the main thread
|
||||
# terminates, which will be when the child process terminates.
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
# choice of os.spawnv():
|
||||
# - [v vs. l] pass a list of args vs. individual arguments,
|
||||
# - [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...", " ".join(args))
|
||||
rc = os.spawnv(os.P_WAIT, args[0], args)
|
||||
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()
|
||||
Loading…
Reference in New Issue