summaryrefslogtreecommitdiff
path: root/indra/llmessage/tests
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llmessage/tests')
-rw-r--r--indra/llmessage/tests/commtest.h20
-rw-r--r--indra/llmessage/tests/llavatarnamecache_test.cpp102
-rw-r--r--indra/llmessage/tests/llhost_test.cpp3
-rw-r--r--indra/llmessage/tests/llsdmessage_test.cpp1
-rw-r--r--indra/llmessage/tests/test_llsdmessage_peer.py28
-rw-r--r--indra/llmessage/tests/testrunner.py83
6 files changed, 225 insertions, 12 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/llavatarnamecache_test.cpp b/indra/llmessage/tests/llavatarnamecache_test.cpp
new file mode 100644
index 0000000000..ec6b65d483
--- /dev/null
+++ b/indra/llmessage/tests/llavatarnamecache_test.cpp
@@ -0,0 +1,102 @@
+/**
+ * @file llavatarnamecache_test.cpp
+ * @author James Cook
+ * @brief LLAvatarNameCache test cases.
+ *
+ * $LicenseInfo:firstyear=2010&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$
+ */
+
+#include "linden_common.h"
+
+#include "../llavatarnamecache.h"
+
+#include "../test/lltut.h"
+
+namespace tut
+{
+ struct avatarnamecache_data
+ {
+ };
+ typedef test_group<avatarnamecache_data> avatarnamecache_test;
+ typedef avatarnamecache_test::object avatarnamecache_object;
+ tut::avatarnamecache_test avatarnamecache_testcase("LLAvatarNameCache");
+
+ template<> template<>
+ void avatarnamecache_object::test<1>()
+ {
+ bool valid = false;
+ S32 max_age = 0;
+
+ valid = max_age_from_cache_control("max-age=3600", &max_age);
+ ensure("typical input valid", valid);
+ ensure_equals("typical input parsed", max_age, 3600);
+
+ valid = max_age_from_cache_control(
+ " max-age=600 , no-cache,private=\"stuff\" ", &max_age);
+ ensure("complex input valid", valid);
+ ensure_equals("complex input parsed", max_age, 600);
+
+ valid = max_age_from_cache_control(
+ "no-cache, max-age = 123 ", &max_age);
+ ensure("complex input 2 valid", valid);
+ ensure_equals("complex input 2 parsed", max_age, 123);
+ }
+
+ template<> template<>
+ void avatarnamecache_object::test<2>()
+ {
+ bool valid = false;
+ S32 max_age = -1;
+
+ valid = max_age_from_cache_control("", &max_age);
+ ensure("empty input returns invalid", !valid);
+ ensure_equals("empty input doesn't change val", max_age, -1);
+
+ valid = max_age_from_cache_control("no-cache", &max_age);
+ ensure("no max-age field returns invalid", !valid);
+
+ valid = max_age_from_cache_control("max", &max_age);
+ ensure("just 'max' returns invalid", !valid);
+
+ valid = max_age_from_cache_control("max-age", &max_age);
+ ensure("partial max-age is invalid", !valid);
+
+ valid = max_age_from_cache_control("max-age=", &max_age);
+ ensure("longer partial max-age is invalid", !valid);
+
+ valid = max_age_from_cache_control("max-age=FOO", &max_age);
+ ensure("invalid integer max-age is invalid", !valid);
+
+ valid = max_age_from_cache_control("max-age 234", &max_age);
+ ensure("space separated max-age is invalid", !valid);
+
+ valid = max_age_from_cache_control("max-age=0", &max_age);
+ ensure("zero max-age is valid", valid);
+
+ // *TODO: Handle "0000" as zero
+ //valid = max_age_from_cache_control("max-age=0000", &max_age);
+ //ensure("multi-zero max-age is valid", valid);
+
+ valid = max_age_from_cache_control("max-age=-123", &max_age);
+ ensure("less than zero max-age is invalid", !valid);
+ }
+}
diff --git a/indra/llmessage/tests/llhost_test.cpp b/indra/llmessage/tests/llhost_test.cpp
index b20bceae1d..eadf83c428 100644
--- a/indra/llmessage/tests/llhost_test.cpp
+++ b/indra/llmessage/tests/llhost_test.cpp
@@ -151,8 +151,9 @@ namespace tut
template<> template<>
void host_object::test<9>()
{
+ skip("this test is flaky, but we should figure out why...");
// skip("setHostByName(\"google.com\"); getHostName() -> (e.g.) \"yx-in-f100.1e100.net\"");
- std::string hostStr = "linux.org";
+ std::string hostStr = "lindenlab.com";
LLHost host;
host.setHostByName(hostStr);
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 7eb198bb34..cea5032111 100644
--- a/indra/llmessage/tests/test_llsdmessage_peer.py
+++ b/indra/llmessage/tests/test_llsdmessage_peer.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
"""\
@file test_llsdmessage_peer.py
@author Nat Goodspeed
@@ -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 4d58ef7130..8ff13e0426 100644
--- a/indra/llmessage/tests/testrunner.py
+++ b/indra/llmessage/tests/testrunner.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
"""\
@file testrunner.py
@author Nat Goodspeed
@@ -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.