summaryrefslogtreecommitdiff
path: root/indra/llcommon/tests/llleap_test.cpp
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2012-03-01 17:46:44 -0500
committerNat Goodspeed <nat@lindenlab.com>2012-03-01 17:46:44 -0500
commit0ef99cd33b4b450712e105e8ab448c2dab7081d2 (patch)
tree78fafe7dc94b10d3183fb068ba63cd9f44ae9b27 /indra/llcommon/tests/llleap_test.cpp
parentf0612f6fc424ab4726ef187f41a257c0abdd1d34 (diff)
Add LLLeap class, initial implementation, initial unit tests.
Instantiating LLLeap with a command to execute a particular child process sets up machinery to speak LLSD Event API Plugin protocol with that child process. LLLeap is an LLInstanceTracker subclass, so the code that instantiates need not hold the pointer. LLLeap monitors child-process termination and deletes itself when done.
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