summaryrefslogtreecommitdiff
path: root/indra/llcommon/tests/llleap_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/tests/llleap_test.cpp')
-rw-r--r--indra/llcommon/tests/llleap_test.cpp261
1 files changed, 261 insertions, 0 deletions
diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp
new file mode 100644
index 0000000000..0264442437
--- /dev/null
+++ b/indra/llcommon/tests/llleap_test.cpp
@@ -0,0 +1,261 @@
+/**
+ * @file llleap_test.cpp
+ * @author Nat Goodspeed
+ * @date 2012-02-21
+ * @brief Test for llleap.
+ *
+ * $LicenseInfo:firstyear=2012&license=viewerlgpl$
+ * Copyright (c) 2012, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llleap.h"
+// STL headers
+// std headers
+// external library headers
+#include <boost/assign/list_of.hpp>
+#include <boost/lambda/lambda.hpp>
+#include <boost/foreach.hpp>
+// other Linden headers
+#include "../test/lltut.h"
+#include "../test/namedtempfile.h"
+#include "../test/manageapr.h"
+#include "../test/catch_and_store_what_in.h"
+#include "wrapllerrs.h"
+#include "llevents.h"
+#include "llprocess.h"
+#include "stringize.h"
+#include "StringVec.h"
+
+using boost::assign::list_of;
+
+static ManageAPR manager;
+
+StringVec sv(const StringVec& listof) { return listof; }
+
+#if defined(LL_WINDOWS)
+#define sleep(secs) _sleep((secs) * 1000)
+#endif
+
+void waitfor(const std::vector<LLLeap*>& instances)
+{
+ int i, timeout = 60;
+ for (i = 0; i < timeout; ++i)
+ {
+ // Every iteration, test whether any of the passed LLLeap instances
+ // still exist (are still running).
+ std::vector<LLLeap*>::const_iterator vli(instances.begin()), vlend(instances.end());
+ for ( ; vli != vlend; ++vli)
+ {
+ // getInstance() returns NULL if it's terminated/gone, non-NULL if
+ // it's still running
+ if (LLLeap::getInstance(*vli))
+ break;
+ }
+ // If we made it through all of 'instances' without finding one that's
+ // still running, we're done.
+ if (vli == vlend)
+ return;
+ // Found an instance that's still running. Wait and pump LLProcess.
+ sleep(1);
+ LLEventPumps::instance().obtain("mainloop").post(LLSD());
+ }
+ tut::ensure("timed out without terminating", i < timeout);
+}
+
+void waitfor(LLLeap* instance)
+{
+ std::vector<LLLeap*> instances;
+ instances.push_back(instance);
+ waitfor(instances);
+}
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llleap_data
+ {
+ llleap_data():
+ reader(".py",
+ // This logic is adapted from vita.viewerclient.receiveEvent()
+ "import sys\n"
+ "LEFTOVER = ''\n"
+ "class ProtocolError(Exception):\n"
+ " pass\n"
+ "def get():\n"
+ " global LEFTOVER\n"
+ " hdr = LEFTOVER\n"
+ " if ':' not in hdr:\n"
+ " hdr += sys.stdin.read(20)\n"
+ " if not hdr:\n"
+ " sys.exit(0)\n"
+ " parts = hdr.split(':', 1)\n"
+ " if len(parts) != 2:\n"
+ " raise ProtocolError('Expected len:data, got %r' % hdr)\n"
+ " try:\n"
+ " length = int(parts[0])\n"
+ " except ValueError:\n"
+ " raise ProtocolError('Non-numeric len %r' % parts[0])\n"
+ " del parts[0]\n"
+ " received = len(parts[0])\n"
+ " while received < length:\n"
+ " parts.append(sys.stdin.read(length - received))\n"
+ " received += len(parts[-1])\n"
+ " if received > length:\n"
+ " excess = length - received\n"
+ " LEFTOVER = parts[-1][excess:]\n"
+ " parts[-1] = parts[-1][:excess]\n"
+ " data = ''.join(parts)\n"
+ " assert len(data) == length\n"
+ " return data\n"),
+ // Get the actual pathname of the NamedExtTempFile and trim off
+ // the ".py" extension. (We could cache reader.getName() in a
+ // separate member variable, but I happen to know getName() just
+ // returns a NamedExtTempFile member rather than performing any
+ // computation, so I don't mind calling it twice.) Then take the
+ // basename.
+ reader_module(LLProcess::basename(
+ reader.getName().substr(0, reader.getName().length()-3))),
+ pPYTHON(getenv("PYTHON")),
+ PYTHON(pPYTHON? pPYTHON : "")
+ {
+ ensure("Set PYTHON to interpreter pathname", pPYTHON);
+ }
+ NamedExtTempFile reader;
+ const std::string reader_module;
+ const char* pPYTHON;
+ const std::string PYTHON;
+ };
+ typedef test_group<llleap_data> llleap_group;
+ typedef llleap_group::object object;
+ llleap_group llleapgrp("llleap");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("multiple LLLeap instances");
+ NamedTempFile script("py",
+ "import time\n"
+ "time.sleep(1)\n");
+ std::vector<LLLeap*> instances;
+ instances.push_back(LLLeap::create(get_test_name(),
+ sv(list_of(PYTHON)(script.getName()))));
+ instances.push_back(LLLeap::create(get_test_name(),
+ sv(list_of(PYTHON)(script.getName()))));
+ // In this case we're simply establishing that two LLLeap instances
+ // can coexist without throwing exceptions or bombing in any other
+ // way. Wait for them to terminate.
+ waitfor(instances);
+ }
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("stderr to log");
+ NamedTempFile script("py",
+ "import sys\n"
+ "sys.stderr.write('''Hello from Python!\n"
+ "note partial line''')\n");
+ CaptureLog log(LLError::LEVEL_INFO);
+ waitfor(LLLeap::create(get_test_name(),
+ sv(list_of(PYTHON)(script.getName()))));
+ log.messageWith("Hello from Python!");
+ log.messageWith("note partial line");
+ }
+
+ template<> template<>
+ void object::test<3>()
+ {
+ set_test_name("empty plugin vector");
+ std::string threw;
+ try
+ {
+ LLLeap::create("empty", StringVec());
+ }
+ CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error)
+ ensure_contains("LLLeap::Error", threw, "no plugin");
+ // try the suppress-exception variant
+ ensure("bad launch returned non-NULL", ! LLLeap::create("empty", StringVec(), false));
+ }
+
+ template<> template<>
+ void object::test<4>()
+ {
+ set_test_name("bad launch");
+ // Synthesize bogus executable name
+ std::string BADPYTHON(PYTHON.substr(0, PYTHON.length()-1) + "x");
+ CaptureLog log;
+ std::string threw;
+ try
+ {
+ LLLeap::create("bad exe", BADPYTHON);
+ }
+ CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error)
+ ensure_contains("LLLeap::create() didn't throw", threw, "failed");
+ log.messageWith("failed");
+ log.messageWith(BADPYTHON);
+ // try the suppress-exception variant
+ ensure("bad launch returned non-NULL", ! LLLeap::create("bad exe", BADPYTHON, false));
+ }
+
+ // Mimic a dummy little LLEventAPI that merely sends a reply back to its
+ // requester on the "reply" pump.
+ struct API
+ {
+ API():
+ mPump("API", true)
+ {
+ mPump.listen("API", boost::bind(&API::entry, this, _1));
+ }
+
+ bool entry(const LLSD& request)
+ {
+ LLEventPumps::instance().obtain(request["reply"]).post("ack");
+ return false;
+ }
+
+ LLEventStream mPump;
+ };
+
+ template<> template<>
+ void object::test<5>()
+ {
+ set_test_name("round trip");
+ API api;
+ NamedTempFile script("py",
+ boost::lambda::_1 <<
+ "import re\n"
+ "import sys\n"
+ "from " << reader_module << " import get\n"
+ // this will throw if the initial write to stdin
+ // doesn't follow len:data protocol
+ "initial = get()\n"
+ "match = re.search(r\"'pump':'(.*?)'\", initial)\n"
+ // this will throw if we couldn't find
+ // 'pump':'etc.' in the initial write
+ "reply = match.group(1)\n"
+ "req = '''\\\n"
+ "{'pump':'" << api.mPump.getName() << "','data':{'reply':'%s'}}\\\n"
+ "''' % reply\n"
+ // make a request on our little API
+ "sys.stdout.write(':'.join((str(len(req)), req)))\n"
+ "sys.stdout.flush()\n"
+ // wait for its response
+ "resp = get()\n"
+ // it would be cleverer to be order-insensitive
+ // about 'data' and 'pump'; hopefully the C++
+ // serializer doesn't change its rules soon
+ "result = 'good' if (resp == \"{'data':'ack','pump':'%s'}\" % reply)\\\n"
+ " else 'bad: ' + resp\n"
+ // write 'good' or 'bad' to the log so we can observe
+ "sys.stderr.write(result)\n");
+ CaptureLog log(LLError::LEVEL_INFO);
+ waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))));
+ log.messageWith("good");
+ }
+} // namespace tut